diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..afbdab3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..dac9346 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +TUIAndroidApplication \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..217af47 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..e7bedf3 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..e206d70 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..c733fdd --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,17 @@ + + + + + + + diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..d299f23 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,23 @@ + + + + + + + + + + Android API 7 Platform + + + + + + + + + diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..8b95d11 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/.idea/scopes/scope_settings.xml b/.idea/scopes/scope_settings.xml new file mode 100644 index 0000000..922003b --- /dev/null +++ b/.idea/scopes/scope_settings.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..def6a6a --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/README.md b/README.md index 8aca9f8..a4c4c40 100644 --- a/README.md +++ b/README.md @@ -1,116 +1,10 @@ -The Challenge - Clean India -============================ +TUIApp +====== -Inspired by [The Ugly Indian](http://theuglyindian.com) and the [community](https://www.facebook.com/theugl.yindian) who have been transforming public spaces in the country over the past few years, create a software application that empowers people to take charge and solve problems in their neighborhood. - - -###Why? - - -> "I beg every citizen of the country to pledge 100 hours a year towards cleanliness. This work cannot be done by the PM, CMs or ministers alone. Every citizen of the nation has to pledge to keep the country clean" - Prime Minister Narendra Modi on 24th Sep 2014 - -If you have been following work inspired by [The Ugly Indian](https://www.facebook.com/theugl.yindian) on facebook, you will see that it takes only a few people to go out and bring about a change. - - - -###What? - - -![PM's message to dedicate 2 hours per week to spotfixing](https://fbcdn-sphotos-b-a.akamaihd.net/hphotos-ak-xfp1/t31.0-8/r90/10623637_758609274198330_1043844814865936707_o.jpg?dl=1) - -In short do a spot fix - A spotfix is transforming a public space by solving the basic underlying problems - -![An example of spotfix](http://33.media.tumblr.com/7ca066c821c7ef7ca41a1154fb072405/tumblr_n7w439SmZN1tw6339o1_1280.jpg) - - -The photos [here](http://theuglyindian.tumblr.com/) and this [Reddit AMA](http://www.reddit.com/r/india/comments/24uw33/hi_rindia_i_am_anamik_nagrik_an_ugly_indian_ama_r/?sort=top) cover most questions you will have about this approach and proactive style of working. - -There is even a book on this [here](http://theuglyindian.com/books/) - - - -###How? - - -Create an application (mobile or web) that enables people from across the country to plan, organize and execute a spot fix. - -###Who and Where? - - -Anyone from any part of the country can participate. The entire process is online. You can do this as an individual or find a team to build this. - -###When? - - -September 24 - Oct 1 - -We need you to complete your application and submit it by 11:00 PM on 1st October 2014 IST. - - -###What do you get out of this? - -You can create an application that can inspire an entire generation of Indians to *see the change they want to be*. - -The best entries will be showcased, promoted and used by people who have been inspired by 'The Ugly Indian' - -###Submission - -- You will have to email indiarisingtech@gmail.com with a link to the working version of your application along with brief details on how to use it and some screenshots. -- If you have your code on github, submit a pull request to this repository. - - -###Requirements - - -- You can build either a mobile or a web application. There are no restrictions on the platform you can build. All we care about is that whatever you build works, is easy to use and has the potential to have mass appeal. - -The primary functional requirements for the app include -- **Planning a spot fix** | Any person who is interested to do a spot fix should be able to upload a photo, location and description of the spot along with a planned date she or he intends to fix it on. -- **Joining a spot fix** | Any person should be able to browse through planned spot fixes in their vicinity and be able to join the spotfix and share the fix across social media. -- **Reporting a spot fix** | Any person should be able to report a spot fix after it has been completed. The user should be able to upload a before/after picture, a geotagged location, a date and a description of the fix. -- **Map based dashboard** | The primary interface should be map based that shows spots that are available for a fix and spots that have been fixed. The dashboard should let users navigate to each of the spots and browse the details. The dashboard should also report metrics like - Total number of hours volunteered, total number of spot fixes and other interesting aggregated metrics. - - - -###Frequently Asked Questions - -- **Do I need submit a complete application?** - - -We understand that the time period might not be sufficient to complete the entire project. We would like a functional prototype with the possibility of extension. It should be usable. We recommend you build upon the skills you have. If you are a designer, build the aspects your are familiar wih first (Screen mockups, UI/UX flows). If you are developer, write things that work while providing a usable UI. We want to use whatever you make and build on it in the future by contribution from the community. So don't let the enormity of the task let you down. Just do and submit your best. - -- **What kind of submissions have people been sending you?** - -We have had one person sending us an API built in `nodejs` that could be used by any application developer to build the frontend of their application. [Here is the pull request he sent](https://github.com/indiarising/hackathon/pull/1). We had someone else contributing to the existing documentation. We have had people working end to end on finishing a working prototype of the application. We have had people sending in some tools they have built that could be used for this project. We have had designers and agencies sending in mockups and UX flows. And several other things. As you can see, people are doing whatever they are best at while trying to achieve the goal of the hackathon. We intend to use the things learnt from this process for a more long term project that will involve you and your work. - - - -- **Why do we have to create a github page and submit a pull request?** - -Having a github page lets us easily share your work with the community. Submitting the pull request lets us easily track your work while keeping things transparent. Even if you do not finish your submission this time, we know you have tried something and you will be kept in the loop when we build on this further. - -- **What if I am not a programmer but a designer or an agency?** - -As mentioned earlier, focus on what you do best. If you are a design agency willing to build design concepts around the UI/UX flows or assets (logos/banners/iconography/typography) we could use in the future, go ahead and build it. The submission process remains the same. Creating a github page and submitting a pull request is not very hard. Drop us a mail if you get stuck. - - -- **I don't have the necessary skills to finish everything and would know if there is a way to find a team** - -We do not currently have a process to form team. We would like you to find someone know and try to build things. It is perfectly okay to build only the part you are good at. You can also send us an email and we could maybe figure out something. - -- **What if I am interested but can't do anything this time or before the 1st Oct deadline?** - -Create a github page and send it as a pull request telling us about how you can build this and write a bit about how you can contribute. That way we know you are serious about this. Send it as a pull request and we will keep you in the loop in the future. Also send us an email. - -- **What is github and how do I use it? (Skip if you are already familiar with using git)** - -Github is a version control and collaboration platform for projects, mostly projects that involve code but can also be used for documents and even sharing and collaborating on 3D models. To start with github register on https://github.com/ and, [Fork the hackathon Repository](https://help.github.com/articles/fork-a-repo) by hitting the fork button on [the repo](https://github.com/indiarising/hackathon). Then [create a new file](https://github.com/blog/1327-creating-files-on-github) adding whatever you want to it. You can add images, links and pretty much anything when you create the page. Read more [here](https://github.com/blog/1327-creating-files-on-github). Then [submit a Pull Request](https://help.github.com/articles/using-pull-requests) to our repository. - - - - - -### For any questions email *indiarisingtech@gmail.com* +An android app being developed as part of TheUglyIndian hackathon. +Requires Google Maps Android API version 2 Key and Parse.com API key. +![Alt text](https://raw.githubusercontent.com/janardhannallapati/TUIAndroidApplication/master/screenshots/device-2014-10-01-224009.png + "Spotfixes Map") diff --git a/TUIAndroidApplication.iml b/TUIAndroidApplication.iml new file mode 100644 index 0000000..0bb6048 --- /dev/null +++ b/TUIAndroidApplication.iml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/.idea/.name b/app/.idea/.name new file mode 100644 index 0000000..7a0b7f0 --- /dev/null +++ b/app/.idea/.name @@ -0,0 +1 @@ +app \ No newline at end of file diff --git a/app/.idea/compiler.xml b/app/.idea/compiler.xml new file mode 100644 index 0000000..217af47 --- /dev/null +++ b/app/.idea/compiler.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/app/.idea/copyright/profiles_settings.xml b/app/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..e7bedf3 --- /dev/null +++ b/app/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/.idea/encodings.xml b/app/.idea/encodings.xml new file mode 100644 index 0000000..e206d70 --- /dev/null +++ b/app/.idea/encodings.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/.idea/gradle.xml b/app/.idea/gradle.xml new file mode 100644 index 0000000..33b7888 --- /dev/null +++ b/app/.idea/gradle.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/app/.idea/misc.xml b/app/.idea/misc.xml new file mode 100644 index 0000000..7624c26 --- /dev/null +++ b/app/.idea/misc.xml @@ -0,0 +1,23 @@ + + + + + + + + + + Android API 7 Platform + + + + + + + + + diff --git a/app/.idea/modules.xml b/app/.idea/modules.xml new file mode 100644 index 0000000..0287129 --- /dev/null +++ b/app/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/app/.idea/scopes/scope_settings.xml b/app/.idea/scopes/scope_settings.xml new file mode 100644 index 0000000..922003b --- /dev/null +++ b/app/.idea/scopes/scope_settings.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/.idea/vcs.xml b/app/.idea/vcs.xml new file mode 100644 index 0000000..def6a6a --- /dev/null +++ b/app/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/.idea/workspace.xml b/app/.idea/workspace.xml new file mode 100644 index 0000000..3dbee04 --- /dev/null +++ b/app/.idea/workspace.xml @@ -0,0 +1,251 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + localhost + 5050 + + + + + + + 1412237884622 + 1412237884622 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/app.iml b/app/app.iml new file mode 100644 index 0000000..e686105 --- /dev/null +++ b/app/app.iml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..664c081 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,29 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 19 + buildToolsVersion "20.0.0" + + defaultConfig { + applicationId "com.inoss.tuiapp" + minSdkVersion 10 + targetSdkVersion 19 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + runProguard false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['**/*.jar']) + + + compile 'com.android.support:appcompat-v7:20.0.0' + compile 'com.google.android.gms:play-services:5.2.08' + compile 'com.google.maps.android:android-maps-utils:0.3.+' +} diff --git a/app/libs/Parse-1.6.0/Parse-1.6.0.jar b/app/libs/Parse-1.6.0/Parse-1.6.0.jar new file mode 100644 index 0000000..f22d9de Binary files /dev/null and b/app/libs/Parse-1.6.0/Parse-1.6.0.jar differ diff --git a/app/libs/Parse-1.6.0/Parse-1.6.0.jar.properties b/app/libs/Parse-1.6.0/Parse-1.6.0.jar.properties new file mode 100644 index 0000000..1fe2a58 --- /dev/null +++ b/app/libs/Parse-1.6.0/Parse-1.6.0.jar.properties @@ -0,0 +1 @@ +doc=Parse-1.6.0-javadoc diff --git a/app/libs/Parse-1.6.0/third_party_licenses.txt b/app/libs/Parse-1.6.0/third_party_licenses.txt new file mode 100644 index 0000000..aa6d346 --- /dev/null +++ b/app/libs/Parse-1.6.0/third_party_licenses.txt @@ -0,0 +1,622 @@ +THE FOLLOWING SETS FORTH ATTRIBUTION NOTICES FOR THIRD PARTY SOFTWARE THAT MAY BE CONTAINED IN PORTIONS OF THE PARSE PRODUCT. + +----- + +The following software may be included in this product: Apache Jakarta Commons Codec. This software contains the following license and notice below: + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +----- + +The following software may be included in this product: oauth-signpost. This software contains the following license and notice below: + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +----- + +The following software may be included in this product: Apache Commons IO. This software contains the following license and notice below: + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/app/local.properties b/app/local.properties new file mode 100644 index 0000000..ce01e26 --- /dev/null +++ b/app/local.properties @@ -0,0 +1,11 @@ +## This file is automatically generated by Android Studio. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must *NOT* be checked into Version Control Systems, +# as it contains information specific to your local configuration. +# +# Location of the SDK. This is only used by Gradle. +# For customization when using a Version Control System, please read the +# header note. +#Thu Oct 02 13:47:57 IST 2014 +sdk.dir=C\:\\Applications\\android-sdks diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..2637572 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in C:\Applications\android-sdks/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/app/src/androidTest/java/com/inoss/tuiandroidapplication/ApplicationTest.java b/app/src/androidTest/java/com/inoss/tuiandroidapplication/ApplicationTest.java new file mode 100644 index 0000000..77bb986 --- /dev/null +++ b/app/src/androidTest/java/com/inoss/tuiandroidapplication/ApplicationTest.java @@ -0,0 +1,13 @@ +package com.inoss.tuiandroidapplication; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/inoss/tuiapp/ApplicationTest.java b/app/src/androidTest/java/com/inoss/tuiapp/ApplicationTest.java new file mode 100644 index 0000000..74a43fb --- /dev/null +++ b/app/src/androidTest/java/com/inoss/tuiapp/ApplicationTest.java @@ -0,0 +1,13 @@ +package com.inoss.tuiapp; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/app/src/debug/res/values/google_maps_api.xml b/app/src/debug/res/values/google_maps_api.xml new file mode 100644 index 0000000..2fc9784 --- /dev/null +++ b/app/src/debug/res/values/google_maps_api.xml @@ -0,0 +1,18 @@ + + + + Google Maps API key here + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..1031dd2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/com/inoss/tuiapp/MapsActivity.java b/app/src/main/java/com/inoss/tuiapp/MapsActivity.java new file mode 100644 index 0000000..0d17af8 --- /dev/null +++ b/app/src/main/java/com/inoss/tuiapp/MapsActivity.java @@ -0,0 +1,126 @@ +package com.inoss.tuiapp; + +import android.os.Bundle; +import android.support.v4.app.FragmentActivity; + +import com.google.android.gms.maps.CameraUpdateFactory; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.MarkerOptions; +import com.google.maps.android.clustering.ClusterItem; +import com.google.maps.android.clustering.ClusterManager; + +public class MapsActivity extends FragmentActivity { + + private GoogleMap mMap; // Might be null if Google Play services APK is not available. + // Declare a variable for the cluster manager. + private ClusterManager mClusterManager; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_maps); + setUpMapIfNeeded(); + } + + @Override + protected void onResume() { + super.onResume(); + setUpMapIfNeeded(); + } + + /** + * Sets up the map if it is possible to do so (i.e., the Google Play services APK is correctly + * installed) and the map has not already been instantiated.. This will ensure that we only ever + * call {@link #setUpMap()} once when {@link #mMap} is not null. + *

+ * If it isn't installed {@link SupportMapFragment} (and + * {@link com.google.android.gms.maps.MapView MapView}) will show a prompt for the user to + * install/update the Google Play services APK on their device. + *

+ * A user can return to this FragmentActivity after following the prompt and correctly + * installing/updating/enabling the Google Play services. Since the FragmentActivity may not + * have been completely destroyed during this process (it is likely that it would only be + * stopped or paused), {@link #onCreate(Bundle)} may not be called again so we should call this + * method in {@link #onResume()} to guarantee that it will be called. + */ + private void setUpMapIfNeeded() { + // Do a null check to confirm that we have not already instantiated the map. + if (mMap == null) { + // Try to obtain the map from the SupportMapFragment. + mMap = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map)) + .getMap(); + // Check if we were successful in obtaining the map. + if (mMap != null) { + setUpMap(); + } + } + } + + /** + * This is where we can add markers or lines, add listeners or move the camera. In this case, we + * just add a marker near Africa. + *

+ * This should only be called once and when we are sure that {@link #mMap} is not null. + */ + private void setUpMap() { + LatLng sydney=new LatLng(-33.867, 151.206); + mMap.setMyLocationEnabled(true); + mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(sydney, 13)); + mMap.addMarker(new MarkerOptions().position(sydney).title("Marker").draggable(true)); + mMap.setMapType(GoogleMap.MAP_TYPE_NORMAL); + setUpClusterer(); + } + + private void setUpClusterer() { + + // Position the map. + mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(51.503186, -0.126446), 10)); + + // Initialize the manager with the context and the map. + // (Activity extends context, so we can pass 'this' in the constructor.) + mClusterManager = new ClusterManager(this, mMap); + + // Point the map's listeners at the listeners implemented by the cluster + // manager. + mMap.setOnCameraChangeListener(mClusterManager); + mMap.setOnMarkerClickListener(mClusterManager); + + // Add cluster items (markers) to the cluster manager. + addItems(); + } + + private void addItems() { + + // Set some lat/lng coordinates to start with. + double lat = 51.5145160; + double lng = -0.1270060; + + // Add ten cluster items in close proximity, for purposes of this example. + for (int i = 0; i < 10; i++) { + double offset = i / 60d; + lat = lat + offset; + lng = lng + offset; + MyItem offsetItem = new MyItem(lat, lng); + mClusterManager.addItem(offsetItem); + } + } + + + class MyItem implements ClusterItem { + private final LatLng mPosition; + + public MyItem(double lat, double lng) { + mPosition = new LatLng(lat, lng); + } + + @Override + public LatLng getPosition() { + return mPosition; + } + } + +} + diff --git a/app/src/main/java/com/inoss/tuiapp/ParseApplication.java b/app/src/main/java/com/inoss/tuiapp/ParseApplication.java new file mode 100644 index 0000000..eaeed9a --- /dev/null +++ b/app/src/main/java/com/inoss/tuiapp/ParseApplication.java @@ -0,0 +1,54 @@ +package com.inoss.tuiapp; + +import android.app.Application; +import android.util.Log; +import android.widget.Toast; + +import com.parse.FindCallback; +import com.parse.Parse; +import com.parse.ParseACL; +import com.parse.ParseException; +import com.parse.ParseObject; +import com.parse.ParseQuery; +import com.parse.ParseUser; + +import java.util.List; + +public class ParseApplication extends Application { + + @Override + public void onCreate() { + super.onCreate(); + + // Add your initialization code here + //TODO remove hardcoding + + Parse.initialize(this, "GfHv7S0Fmuj4SrgJAasJrM13TwZBWBIKM4qr1qeJ", "gBvZhOKXkDxfQkl0fyFuelHwhUXcRIR3v102CHh4"); + + ParseUser.enableAutomaticUser(); + ParseACL defaultACL = new ParseACL(); + + // If you would like all objects to be private by default, remove this line. + //defaultACL.setPublicReadAccess(true); + + ParseACL.setDefaultACL(defaultACL, true); + + ParseObject object = new ParseObject("TestObject"); + object.put("name","sharathkumar"); + object.put("job","actor"); + object.saveInBackground(); + ParseQuery query = new ParseQuery("TestObject"); + query.findInBackground(new FindCallback() { + @Override + public void done(List parseObjects, ParseException e) { + if (e != null) { + Log.d("ParseApplication", "Exception in retrieving:" + e); + } else { + Toast.makeText(ParseApplication.this, String.format("Done %d",parseObjects.size()), Toast.LENGTH_LONG).show(); + } + + } + }); + } + +} diff --git a/app/src/main/java/com/inoss/tuiapp/SignUpActivity.java b/app/src/main/java/com/inoss/tuiapp/SignUpActivity.java new file mode 100644 index 0000000..046acf2 --- /dev/null +++ b/app/src/main/java/com/inoss/tuiapp/SignUpActivity.java @@ -0,0 +1,39 @@ +package com.inoss.tuiapp; + +import android.support.v7.app.ActionBarActivity; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; + + +public class SignUpActivity extends ActionBarActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_sign_up); + } + + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.sign_up, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + + //noinspection SimplifiableIfStatement + if (id == R.id.action_settings) { + return true; + } + + return super.onOptionsItemSelected(item); + } +} diff --git a/app/src/main/java/com/inoss/tuiapp/ViewSpotFixListActivity.java b/app/src/main/java/com/inoss/tuiapp/ViewSpotFixListActivity.java new file mode 100644 index 0000000..9774d8f --- /dev/null +++ b/app/src/main/java/com/inoss/tuiapp/ViewSpotFixListActivity.java @@ -0,0 +1,51 @@ +package com.inoss.tuiapp; + +import android.support.v7.app.ActionBarActivity; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.widget.ListView; + +import com.parse.ParseAnalytics; +import com.parse.ParseObject; +import com.parse.ParseQueryAdapter; + + +public class ViewSpotFixListActivity extends ActionBarActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_view_spot_fix_list); + ParseAnalytics.trackAppOpened(getIntent()); + ListView listView = (ListView) findViewById(R.id.listView); + ParseQueryAdapter queryAdapter = new ParseQueryAdapter(this,"TestObject"); + queryAdapter.setTextKey("foo"); + queryAdapter.setImageKey("Image"); + queryAdapter.setObjectsPerPage(10); + listView.setAdapter(queryAdapter); + } + + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.create_spot_fix, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + + //noinspection SimplifiableIfStatement + if (id == R.id.action_settings) { + return true; + } + + return super.onOptionsItemSelected(item); + } +} diff --git a/app/src/main/java/com/parse/inoss/AnywallPost.java b/app/src/main/java/com/parse/inoss/AnywallPost.java new file mode 100644 index 0000000..9066d0d --- /dev/null +++ b/app/src/main/java/com/parse/inoss/AnywallPost.java @@ -0,0 +1,41 @@ +package com.parse.inoss; + +import com.parse.ParseClassName; +import com.parse.ParseGeoPoint; +import com.parse.ParseObject; +import com.parse.ParseQuery; +import com.parse.ParseUser; + +/** + * Data model for a post. + */ +@ParseClassName("Posts") +public class AnywallPost extends ParseObject { + public String getText() { + return getString("text"); + } + + public void setText(String value) { + put("text", value); + } + + public ParseUser getUser() { + return getParseUser("user"); + } + + public void setUser(ParseUser value) { + put("user", value); + } + + public ParseGeoPoint getLocation() { + return getParseGeoPoint("location"); + } + + public void setLocation(ParseGeoPoint value) { + put("location", value); + } + + public static ParseQuery getQuery() { + return ParseQuery.getQuery(AnywallPost.class); + } +} diff --git a/app/src/main/java/com/parse/inoss/Application.java b/app/src/main/java/com/parse/inoss/Application.java new file mode 100644 index 0000000..fe5426a --- /dev/null +++ b/app/src/main/java/com/parse/inoss/Application.java @@ -0,0 +1,56 @@ +package com.parse.inoss; + +import android.content.Context; +import android.content.SharedPreferences; + +import com.parse.Parse; +import com.parse.ParseObject; + +public class Application extends android.app.Application { + // Debugging switch + public static final boolean APPDEBUG = false; + + // Debugging tag for the application + public static final String APPTAG = "AnyWall"; + + // Used to pass location from MainActivity to PostActivity + public static final String INTENT_EXTRA_LOCATION = "location"; + + // Key for saving the search distance preference + private static final String KEY_SEARCH_DISTANCE = "searchDistance"; + + private static final float DEFAULT_SEARCH_DISTANCE = 250.0f; + + private static SharedPreferences preferences; + + private static ConfigHelper configHelper; + + public Application() { + } + + @Override + public void onCreate() { + super.onCreate(); + + ParseObject.registerSubclass(AnywallPost.class); + Parse.initialize(this, "ApplicationId", "Client Key"); + + preferences = getSharedPreferences("com.parse.anywall", Context.MODE_PRIVATE); + + configHelper = new ConfigHelper(); + configHelper.fetchConfigIfNeeded(); + } + + public static float getSearchDistance() { + return preferences.getFloat(KEY_SEARCH_DISTANCE, DEFAULT_SEARCH_DISTANCE); + } + + public static ConfigHelper getConfigHelper() { + return configHelper; + } + + public static void setSearchDistance(float value) { + preferences.edit().putFloat(KEY_SEARCH_DISTANCE, value).commit(); + } + +} diff --git a/app/src/main/java/com/parse/inoss/ConfigHelper.java b/app/src/main/java/com/parse/inoss/ConfigHelper.java new file mode 100644 index 0000000..c896add --- /dev/null +++ b/app/src/main/java/com/parse/inoss/ConfigHelper.java @@ -0,0 +1,60 @@ +package com.parse.inoss; + +import com.parse.ConfigCallback; +import com.parse.ParseConfig; +import com.parse.ParseException; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class ConfigHelper { + private ParseConfig config; + private long configLastFetchedTime; + + public void fetchConfigIfNeeded() { + final long configRefreshInterval = 60 * 60; // 1 hour + + if (config == null || + System.currentTimeMillis() - configLastFetchedTime > configRefreshInterval) { + // Set the config to current, just to load the cache + config = ParseConfig.getCurrentConfig(); + + // Set the current time, to flag that the operation started and prevent double fetch + ParseConfig.getInBackground(new ConfigCallback() { + @Override + public void done(ParseConfig parseConfig, ParseException e) { + if (e == null) { + // Yay, retrieved successfully + config = parseConfig; + configLastFetchedTime = System.currentTimeMillis(); + } else { + // Fetch failed, reset the time + configLastFetchedTime = 0; + } + } + }); + } + } + + public List getSearchDistanceAvailableOptions() { + final List defaultOptions = Arrays.asList(250.0f, 1000.0f, 2000.0f, 5000.0f); + + List options = config.getList("availableFilterDistances"); + if (options == null) { + return defaultOptions; + } + + List typedOptions = new ArrayList(); + for (Number option : options) { + typedOptions.add(option.floatValue()); + } + + return typedOptions; + } + + public int getPostMaxCharacterCount () { + int value = config.getInt("postMaxCharacterCount", 140); + return value; + } +} diff --git a/app/src/main/java/com/parse/inoss/DispatchActivity.java b/app/src/main/java/com/parse/inoss/DispatchActivity.java new file mode 100644 index 0000000..0d58e0b --- /dev/null +++ b/app/src/main/java/com/parse/inoss/DispatchActivity.java @@ -0,0 +1,31 @@ +package com.parse.inoss; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; + +import com.parse.ParseUser; + +/** + * Activity which starts an intent for either the logged in (MainActivity) or logged out + * (SignUpOrLoginActivity) activity. + */ +public class DispatchActivity extends Activity { + + public DispatchActivity() { + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // Check if there is current user info + if (ParseUser.getCurrentUser() != null) { + // Start an intent for the logged in activity + startActivity(new Intent(this, MainActivity.class)); + } else { + // Start and intent for the logged out activity + startActivity(new Intent(this, WelcomeActivity.class)); + } + } + +} diff --git a/app/src/main/java/com/parse/inoss/LoginActivity.java b/app/src/main/java/com/parse/inoss/LoginActivity.java new file mode 100644 index 0000000..84cf3a0 --- /dev/null +++ b/app/src/main/java/com/parse/inoss/LoginActivity.java @@ -0,0 +1,106 @@ +package com.parse.inoss; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Intent; +import android.os.Bundle; +import android.view.KeyEvent; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; + +import com.inoss.tuiapp.R; +import com.parse.LogInCallback; +import com.parse.ParseException; +import com.parse.ParseUser; + +/** + * Activity which displays a login screen to the user, offering registration as well. + */ +public class LoginActivity extends Activity { + // UI references. + private EditText usernameEditText; + private EditText passwordEditText; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_login); + + // Set up the login form. + usernameEditText = (EditText) findViewById(R.id.username); + passwordEditText = (EditText) findViewById(R.id.password); + passwordEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if (actionId == R.id.edittext_action_login || + actionId == EditorInfo.IME_ACTION_UNSPECIFIED) { + login(); + return true; + } + return false; + } + }); + + // Set up the submit button click handler + Button actionButton = (Button) findViewById(R.id.action_button); + actionButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View view) { + login(); + } + }); + } + + private void login() { + String username = usernameEditText.getText().toString().trim(); + String password = passwordEditText.getText().toString().trim(); + + // Validate the log in data + boolean validationError = false; + StringBuilder validationErrorMessage = new StringBuilder(getString(R.string.error_intro)); + if (username.length() == 0) { + validationError = true; + validationErrorMessage.append(getString(R.string.error_blank_username)); + } + if (password.length() == 0) { + if (validationError) { + validationErrorMessage.append(getString(R.string.error_join)); + } + validationError = true; + validationErrorMessage.append(getString(R.string.error_blank_password)); + } + validationErrorMessage.append(getString(R.string.error_end)); + + // If there is a validation error, display the error + if (validationError) { + Toast.makeText(LoginActivity.this, validationErrorMessage.toString(), Toast.LENGTH_LONG) + .show(); + return; + } + + // Set up a progress dialog + final ProgressDialog dialog = new ProgressDialog(LoginActivity.this); + dialog.setMessage(getString(R.string.progress_login)); + dialog.show(); + // Call the Parse login method + ParseUser.logInInBackground(username, password, new LogInCallback() { + @Override + public void done(ParseUser user, ParseException e) { + dialog.dismiss(); + if (e != null) { + // Show the error message + Toast.makeText(LoginActivity.this, e.getMessage(), Toast.LENGTH_LONG).show(); + } else { + // Start an intent for the dispatch activity + Intent intent = new Intent(LoginActivity.this, DispatchActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } + } + }); + } +} diff --git a/app/src/main/java/com/parse/inoss/MainActivity.java b/app/src/main/java/com/parse/inoss/MainActivity.java new file mode 100644 index 0000000..bccc8f4 --- /dev/null +++ b/app/src/main/java/com/parse/inoss/MainActivity.java @@ -0,0 +1,785 @@ +package com.parse.inoss; + +import android.app.Activity; +import android.app.Dialog; +import android.content.Intent; +import android.content.IntentSender; +import android.graphics.Color; +import android.location.Location; +import android.os.Bundle; +import android.support.v4.app.DialogFragment; +import android.support.v4.app.FragmentActivity; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MenuItem.OnMenuItemClickListener; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.Button; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; + +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.GooglePlayServicesClient; +import com.google.android.gms.common.GooglePlayServicesUtil; +import com.google.android.gms.location.LocationClient; +import com.google.android.gms.location.LocationListener; +import com.google.android.gms.location.LocationRequest; +import com.google.android.gms.maps.CameraUpdateFactory; +import com.google.android.gms.maps.GoogleMap.CancelableCallback; +import com.google.android.gms.maps.GoogleMap.OnCameraChangeListener; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.model.BitmapDescriptorFactory; +import com.google.android.gms.maps.model.CameraPosition; +import com.google.android.gms.maps.model.Circle; +import com.google.android.gms.maps.model.CircleOptions; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.LatLngBounds; +import com.google.android.gms.maps.model.Marker; +import com.google.android.gms.maps.model.MarkerOptions; +import com.inoss.tuiapp.R; +import com.parse.FindCallback; +import com.parse.ParseException; +import com.parse.ParseGeoPoint; +import com.parse.ParseQuery; +import com.parse.ParseQueryAdapter; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class MainActivity extends FragmentActivity implements LocationListener, + GooglePlayServicesClient.ConnectionCallbacks, + GooglePlayServicesClient.OnConnectionFailedListener { + + /* + * Define a request code to send to Google Play services This code is returned in + * Activity.onActivityResult + */ + private final static int CONNECTION_FAILURE_RESOLUTION_REQUEST = 9000; + + /* + * Constants for location update parameters + */ + // Milliseconds per second + private static final int MILLISECONDS_PER_SECOND = 1000; + + // The update interval + private static final int UPDATE_INTERVAL_IN_SECONDS = 5; + + // A fast interval ceiling + private static final int FAST_CEILING_IN_SECONDS = 1; + + // Update interval in milliseconds + private static final long UPDATE_INTERVAL_IN_MILLISECONDS = MILLISECONDS_PER_SECOND + * UPDATE_INTERVAL_IN_SECONDS; + + // A fast ceiling of update intervals, used when the app is visible + private static final long FAST_INTERVAL_CEILING_IN_MILLISECONDS = MILLISECONDS_PER_SECOND + * FAST_CEILING_IN_SECONDS; + + /* + * Constants for handling location results + */ + // Conversion from feet to meters + private static final float METERS_PER_FEET = 0.3048f; + + // Conversion from kilometers to meters + private static final int METERS_PER_KILOMETER = 1000; + + // Initial offset for calculating the map bounds + private static final double OFFSET_CALCULATION_INIT_DIFF = 1.0; + + // Accuracy for calculating the map bounds + private static final float OFFSET_CALCULATION_ACCURACY = 0.01f; + + // Maximum results returned from a Parse query + private static final int MAX_POST_SEARCH_RESULTS = 20; + + // Maximum post search radius for map in kilometers + private static final int MAX_POST_SEARCH_DISTANCE = 100; + + /* + * Other class member variables + */ + // Map fragment + private SupportMapFragment mapFragment; + + // Represents the circle around a map + private Circle mapCircle; + + // Fields for the map radius in feet + private float radius; + private float lastRadius; + + // Fields for helping process map and location changes + private final Map mapMarkers = new HashMap(); + private int mostRecentMapUpdate; + private boolean hasSetUpInitialLocation; + private String selectedPostObjectId; + private Location lastLocation; + private Location currentLocation; + + // A request to connect to Location Services + private LocationRequest locationRequest; + + // Stores the current instantiation of the location client in this object + private LocationClient locationClient; + + // Adapter for the Parse query + private ParseQueryAdapter postsQueryAdapter; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + radius = Application.getSearchDistance(); + lastRadius = radius; + setContentView(R.layout.activity_main); + + // Create a new global location parameters object + locationRequest = LocationRequest.create(); + + // Set the update interval + locationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS); + + // Use high accuracy + locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); + + // Set the interval ceiling to one minute + locationRequest.setFastestInterval(FAST_INTERVAL_CEILING_IN_MILLISECONDS); + + // Create a new location client, using the enclosing class to handle callbacks. + locationClient = new LocationClient(this, this, this); + + // Set up a customized query + ParseQueryAdapter.QueryFactory factory = + new ParseQueryAdapter.QueryFactory() { + public ParseQuery create() { + Location myLoc = (currentLocation == null) ? lastLocation : currentLocation; + ParseQuery query = AnywallPost.getQuery(); + query.include("user"); + query.orderByDescending("createdAt"); + query.whereWithinKilometers("location", geoPointFromLocation(myLoc), radius + * METERS_PER_FEET / METERS_PER_KILOMETER); + query.setLimit(MAX_POST_SEARCH_RESULTS); + return query; + } + }; + + // Set up the query adapter + postsQueryAdapter = new ParseQueryAdapter(this, factory) { + @Override + public View getItemView(AnywallPost post, View view, ViewGroup parent) { + if (view == null) { + view = View.inflate(getContext(), R.layout.anywall_post_item, null); + } + TextView contentView = (TextView) view.findViewById(R.id.content_view); + TextView usernameView = (TextView) view.findViewById(R.id.username_view); + contentView.setText(post.getText()); + usernameView.setText(post.getUser().getUsername()); + return view; + } + }; + + // Disable automatic loading when the adapter is attached to a view. + postsQueryAdapter.setAutoload(false); + + // Disable pagination, we'll manage the query limit ourselves + postsQueryAdapter.setPaginationEnabled(false); + + // Attach the query adapter to the view + ListView postsListView = (ListView) findViewById(R.id.posts_listview); + postsListView.setAdapter(postsQueryAdapter); + + // Set up the handler for an item's selection + postsListView.setOnItemClickListener(new OnItemClickListener() { + public void onItemClick(AdapterView parent, View view, int position, long id) { + final AnywallPost item = postsQueryAdapter.getItem(position); + selectedPostObjectId = item.getObjectId(); + mapFragment.getMap().animateCamera( + CameraUpdateFactory.newLatLng(new LatLng(item.getLocation().getLatitude(), item + .getLocation().getLongitude())), new CancelableCallback() { + public void onFinish() { + Marker marker = mapMarkers.get(item.getObjectId()); + if (marker != null) { + marker.showInfoWindow(); + } + } + + public void onCancel() { + } + }); + Marker marker = mapMarkers.get(item.getObjectId()); + if (marker != null) { + marker.showInfoWindow(); + } + } + }); + + // Set up the map fragment + mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map_fragment); + + // Enable the current location "blue dot" + mapFragment.getMap().setMyLocationEnabled(true); + // Set up the camera change handler + mapFragment.getMap().setOnCameraChangeListener(new OnCameraChangeListener() { + public void onCameraChange(CameraPosition position) { + // When the camera changes, update the query + doMapQuery(); + } + }); + + // Set up the handler for the post button click + Button postButton = (Button) findViewById(R.id.post_button); + postButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + // Only allow posts if we have a location + Location myLoc = (currentLocation == null) ? lastLocation : currentLocation; + if (myLoc == null) { + Toast.makeText(MainActivity.this, + "Please try again after your location appears on the map.", Toast.LENGTH_LONG).show(); + return; + } + + Intent intent = new Intent(MainActivity.this, PostActivity.class); + intent.putExtra(Application.INTENT_EXTRA_LOCATION, myLoc); + startActivity(intent); + } + }); + } + + /* + * Called when the Activity is no longer visible at all. Stop updates and disconnect. + */ + @Override + public void onStop() { + // If the client is connected + if (locationClient.isConnected()) { + stopPeriodicUpdates(); + } + + // After disconnect() is called, the client is considered "dead". + locationClient.disconnect(); + + super.onStop(); + } + + /* + * Called when the Activity is restarted, even before it becomes visible. + */ + @Override + public void onStart() { + super.onStart(); + + // Connect to the location services client + locationClient.connect(); + } + + /* + * Called when the Activity is resumed. Updates the view. + */ + @Override + protected void onResume() { + super.onResume(); + + Application.getConfigHelper().fetchConfigIfNeeded(); + + // Get the latest search distance preference + radius = Application.getSearchDistance(); + // Checks the last saved location to show cached data if it's available + if (lastLocation != null) { + LatLng myLatLng = new LatLng(lastLocation.getLatitude(), lastLocation.getLongitude()); + // If the search distance preference has been changed, move + // map to new bounds. + if (lastRadius != radius) { + updateZoom(myLatLng); + } + // Update the circle map + updateCircle(myLatLng); + } + // Save the current radius + lastRadius = radius; + // Query for the latest data to update the views. + doMapQuery(); + doListQuery(); + } + + /* + * Handle results returned to this Activity by other Activities started with + * startActivityForResult(). In particular, the method onConnectionFailed() in + * LocationUpdateRemover and LocationUpdateRequester may call startResolutionForResult() to start + * an Activity that handles Google Play services problems. The result of this call returns here, + * to onActivityResult. + */ + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent intent) { + // Choose what to do based on the request code + switch (requestCode) { + + // If the request code matches the code sent in onConnectionFailed + case CONNECTION_FAILURE_RESOLUTION_REQUEST: + + switch (resultCode) { + // If Google Play services resolved the problem + case Activity.RESULT_OK: + + if (Application.APPDEBUG) { + // Log the result + Log.d(Application.APPTAG, "Connected to Google Play services"); + } + + break; + + // If any other result was returned by Google Play services + default: + if (Application.APPDEBUG) { + // Log the result + Log.d(Application.APPTAG, "Could not connect to Google Play services"); + } + break; + } + + // If any other request code was received + default: + if (Application.APPDEBUG) { + // Report that this Activity received an unknown requestCode + Log.d(Application.APPTAG, "Unknown request code received for the activity"); + } + break; + } + } + + /* + * Verify that Google Play services is available before making a request. + * + * @return true if Google Play services is available, otherwise false + */ + private boolean servicesConnected() { + // Check that Google Play services is available + int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this); + + // If Google Play services is available + if (ConnectionResult.SUCCESS == resultCode) { + if (Application.APPDEBUG) { + // In debug mode, log the status + Log.d(Application.APPTAG, "Google play services available"); + } + // Continue + return true; + // Google Play services was not available for some reason + } else { + // Display an error dialog + Dialog dialog = GooglePlayServicesUtil.getErrorDialog(resultCode, this, 0); + if (dialog != null) { + ErrorDialogFragment errorFragment = new ErrorDialogFragment(); + errorFragment.setDialog(dialog); + errorFragment.show(getSupportFragmentManager(), Application.APPTAG); + } + return false; + } + } + + /* + * Called by Location Services when the request to connect the client finishes successfully. At + * this point, you can request the current location or start periodic updates + */ + public void onConnected(Bundle bundle) { + if (Application.APPDEBUG) { + Log.d("Connected to location services", Application.APPTAG); + } + currentLocation = getLocation(); + startPeriodicUpdates(); + } + + /* + * Called by Location Services if the connection to the location client drops because of an error. + */ + public void onDisconnected() { + if (Application.APPDEBUG) { + Log.d("Disconnected from location services", Application.APPTAG); + } + } + + /* + * Called by Location Services if the attempt to Location Services fails. + */ + public void onConnectionFailed(ConnectionResult connectionResult) { + // Google Play services can resolve some errors it detects. If the error has a resolution, try + // sending an Intent to start a Google Play services activity that can resolve error. + if (connectionResult.hasResolution()) { + try { + + // Start an Activity that tries to resolve the error + connectionResult.startResolutionForResult(this, CONNECTION_FAILURE_RESOLUTION_REQUEST); + + } catch (IntentSender.SendIntentException e) { + + if (Application.APPDEBUG) { + // Thrown if Google Play services canceled the original PendingIntent + Log.d(Application.APPTAG, "An error occurred when connecting to location services.", e); + } + } + } else { + // If no resolution is available, display a dialog to the user with the error. + showErrorDialog(connectionResult.getErrorCode()); + } + } + + /* + * Report location updates to the UI. + */ + public void onLocationChanged(Location location) { + currentLocation = location; + if (lastLocation != null + && geoPointFromLocation(location) + .distanceInKilometersTo(geoPointFromLocation(lastLocation)) < 0.01) { + // If the location hasn't changed by more than 10 meters, ignore it. + return; + } + lastLocation = location; + LatLng myLatLng = new LatLng(location.getLatitude(), location.getLongitude()); + if (!hasSetUpInitialLocation) { + // Zoom to the current location. + updateZoom(myLatLng); + hasSetUpInitialLocation = true; + } + // Update map radius indicator + updateCircle(myLatLng); + doMapQuery(); + doListQuery(); + } + + /* + * In response to a request to start updates, send a request to Location Services + */ + private void startPeriodicUpdates() { + locationClient.requestLocationUpdates(locationRequest, this); + } + + /* + * In response to a request to stop updates, send a request to Location Services + */ + private void stopPeriodicUpdates() { + locationClient.removeLocationUpdates(this); + } + + /* + * Get the current location + */ + private Location getLocation() { + // If Google Play Services is available + if (servicesConnected()) { + // Get the current location + return locationClient.getLastLocation(); + } else { + return null; + } + } + + /* + * Set up a query to update the list view + */ + private void doListQuery() { + Location myLoc = (currentLocation == null) ? lastLocation : currentLocation; + // If location info is available, load the data + if (myLoc != null) { + // Refreshes the list view with new data based + // usually on updated location data. + postsQueryAdapter.loadObjects(); + } + } + + /* + * Set up the query to update the map view + */ + private void doMapQuery() { + final int myUpdateNumber = ++mostRecentMapUpdate; + Location myLoc = (currentLocation == null) ? lastLocation : currentLocation; + // If location info isn't available, clean up any existing markers + if (myLoc == null) { + cleanUpMarkers(new HashSet()); + return; + } + final ParseGeoPoint myPoint = geoPointFromLocation(myLoc); + // Create the map Parse query + ParseQuery mapQuery = AnywallPost.getQuery(); + // Set up additional query filters + mapQuery.whereWithinKilometers("location", myPoint, MAX_POST_SEARCH_DISTANCE); + mapQuery.include("user"); + mapQuery.orderByDescending("createdAt"); + mapQuery.setLimit(MAX_POST_SEARCH_RESULTS); + // Kick off the query in the background + mapQuery.findInBackground(new FindCallback() { + @Override + public void done(List objects, ParseException e) { + if (e != null) { + if (Application.APPDEBUG) { + Log.d(Application.APPTAG, "An error occurred while querying for map posts.", e); + } + return; + } + /* + * Make sure we're processing results from + * the most recent update, in case there + * may be more than one in progress. + */ + if (myUpdateNumber != mostRecentMapUpdate) { + return; + } + // Posts to show on the map + Set toKeep = new HashSet(); + // Loop through the results of the search + for (AnywallPost post : objects) { + // Add this post to the list of map pins to keep + toKeep.add(post.getObjectId()); + // Check for an existing marker for this post + Marker oldMarker = mapMarkers.get(post.getObjectId()); + // Set up the map marker's location + MarkerOptions markerOpts = + new MarkerOptions().position(new LatLng(post.getLocation().getLatitude(), post + .getLocation().getLongitude())); + // Set up the marker properties based on if it is within the search radius + if (post.getLocation().distanceInKilometersTo(myPoint) > radius * METERS_PER_FEET + / METERS_PER_KILOMETER) { + // Check for an existing out of range marker + if (oldMarker != null) { + if (oldMarker.getSnippet() == null) { + // Out of range marker already exists, skip adding it + continue; + } else { + // Marker now out of range, needs to be refreshed + oldMarker.remove(); + } + } + // Display a red marker with a predefined title and no snippet + markerOpts = + markerOpts.title(getResources().getString(R.string.post_out_of_range)).icon( + BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED)); + } else { + // Check for an existing in range marker + if (oldMarker != null) { + if (oldMarker.getSnippet() != null) { + // In range marker already exists, skip adding it + continue; + } else { + // Marker now in range, needs to be refreshed + oldMarker.remove(); + } + } + // Display a green marker with the post information + markerOpts = + markerOpts.title(post.getText()).snippet(post.getUser().getUsername()) + .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_GREEN)); + } + // Add a new marker + Marker marker = mapFragment.getMap().addMarker(markerOpts); + mapMarkers.put(post.getObjectId(), marker); + if (post.getObjectId().equals(selectedPostObjectId)) { + marker.showInfoWindow(); + selectedPostObjectId = null; + } + } + // Clean up old markers. + cleanUpMarkers(toKeep); + } + }); + } + + /* + * Helper method to clean up old markers + */ + private void cleanUpMarkers(Set markersToKeep) { + for (String objId : new HashSet(mapMarkers.keySet())) { + if (!markersToKeep.contains(objId)) { + Marker marker = mapMarkers.get(objId); + marker.remove(); + mapMarkers.get(objId).remove(); + mapMarkers.remove(objId); + } + } + } + + /* + * Helper method to get the Parse GEO point representation of a location + */ + private ParseGeoPoint geoPointFromLocation(Location loc) { + return new ParseGeoPoint(loc.getLatitude(), loc.getLongitude()); + } + + /* + * Displays a circle on the map representing the search radius + */ + private void updateCircle(LatLng myLatLng) { + if (mapCircle == null) { + mapCircle = + mapFragment.getMap().addCircle( + new CircleOptions().center(myLatLng).radius(radius * METERS_PER_FEET)); + int baseColor = Color.DKGRAY; + mapCircle.setStrokeColor(baseColor); + mapCircle.setStrokeWidth(2); + mapCircle.setFillColor(Color.argb(50, Color.red(baseColor), Color.green(baseColor), + Color.blue(baseColor))); + } + mapCircle.setCenter(myLatLng); + mapCircle.setRadius(radius * METERS_PER_FEET); // Convert radius in feet to meters. + } + + /* + * Zooms the map to show the area of interest based on the search radius + */ + private void updateZoom(LatLng myLatLng) { + // Get the bounds to zoom to + LatLngBounds bounds = calculateBoundsWithCenter(myLatLng); + // Zoom to the given bounds + mapFragment.getMap().animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 5)); + } + + /* + * Helper method to calculate the offset for the bounds used in map zooming + */ + private double calculateLatLngOffset(LatLng myLatLng, boolean bLatOffset) { + // The return offset, initialized to the default difference + double latLngOffset = OFFSET_CALCULATION_INIT_DIFF; + // Set up the desired offset distance in meters + float desiredOffsetInMeters = radius * METERS_PER_FEET; + // Variables for the distance calculation + float[] distance = new float[1]; + boolean foundMax = false; + double foundMinDiff = 0; + // Loop through and get the offset + do { + // Calculate the distance between the point of interest + // and the current offset in the latitude or longitude direction + if (bLatOffset) { + Location.distanceBetween(myLatLng.latitude, myLatLng.longitude, myLatLng.latitude + + latLngOffset, myLatLng.longitude, distance); + } else { + Location.distanceBetween(myLatLng.latitude, myLatLng.longitude, myLatLng.latitude, + myLatLng.longitude + latLngOffset, distance); + } + // Compare the current difference with the desired one + float distanceDiff = distance[0] - desiredOffsetInMeters; + if (distanceDiff < 0) { + // Need to catch up to the desired distance + if (!foundMax) { + foundMinDiff = latLngOffset; + // Increase the calculated offset + latLngOffset *= 2; + } else { + double tmp = latLngOffset; + // Increase the calculated offset, at a slower pace + latLngOffset += (latLngOffset - foundMinDiff) / 2; + foundMinDiff = tmp; + } + } else { + // Overshot the desired distance + // Decrease the calculated offset + latLngOffset -= (latLngOffset - foundMinDiff) / 2; + foundMax = true; + } + } while (Math.abs(distance[0] - desiredOffsetInMeters) > OFFSET_CALCULATION_ACCURACY); + return latLngOffset; + } + + /* + * Helper method to calculate the bounds for map zooming + */ + LatLngBounds calculateBoundsWithCenter(LatLng myLatLng) { + // Create a bounds + LatLngBounds.Builder builder = LatLngBounds.builder(); + + // Calculate east/west points that should to be included + // in the bounds + double lngDifference = calculateLatLngOffset(myLatLng, false); + LatLng east = new LatLng(myLatLng.latitude, myLatLng.longitude + lngDifference); + builder.include(east); + LatLng west = new LatLng(myLatLng.latitude, myLatLng.longitude - lngDifference); + builder.include(west); + + // Calculate north/south points that should to be included + // in the bounds + double latDifference = calculateLatLngOffset(myLatLng, true); + LatLng north = new LatLng(myLatLng.latitude + latDifference, myLatLng.longitude); + builder.include(north); + LatLng south = new LatLng(myLatLng.latitude - latDifference, myLatLng.longitude); + builder.include(south); + + return builder.build(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.main, menu); + + menu.findItem(R.id.action_settings).setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + startActivity(new Intent(MainActivity.this, SettingsActivity.class)); + return true; + } + }); + return true; + } + + /* + * Show a dialog returned by Google Play services for the connection error code + */ + private void showErrorDialog(int errorCode) { + // Get the error dialog from Google Play services + Dialog errorDialog = + GooglePlayServicesUtil.getErrorDialog(errorCode, this, + CONNECTION_FAILURE_RESOLUTION_REQUEST); + + // If Google Play services can provide an error dialog + if (errorDialog != null) { + + // Create a new DialogFragment in which to show the error dialog + ErrorDialogFragment errorFragment = new ErrorDialogFragment(); + + // Set the dialog in the DialogFragment + errorFragment.setDialog(errorDialog); + + // Show the error dialog in the DialogFragment + errorFragment.show(getSupportFragmentManager(), Application.APPTAG); + } + } + + /* + * Define a DialogFragment to display the error dialog generated in showErrorDialog. + */ + public static class ErrorDialogFragment extends DialogFragment { + // Global field to contain the error dialog + private Dialog mDialog; + + /** + * Default constructor. Sets the dialog field to null + */ + public ErrorDialogFragment() { + super(); + mDialog = null; + } + + /* + * Set the dialog to display + * + * @param dialog An error dialog + */ + public void setDialog(Dialog dialog) { + mDialog = dialog; + } + + /* + * This method must return a Dialog to the DialogFragment. + */ + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + return mDialog; + } + } +} diff --git a/app/src/main/java/com/parse/inoss/PostActivity.java b/app/src/main/java/com/parse/inoss/PostActivity.java new file mode 100644 index 0000000..734011f --- /dev/null +++ b/app/src/main/java/com/parse/inoss/PostActivity.java @@ -0,0 +1,120 @@ +package com.parse.inoss; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Intent; +import android.location.Location; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; + +import com.inoss.tuiapp.R; +import com.parse.ParseACL; +import com.parse.ParseException; +import com.parse.ParseGeoPoint; +import com.parse.ParseUser; +import com.parse.SaveCallback; + +/** + * Activity which displays a login screen to the user, offering registration as well. + */ +public class PostActivity extends Activity { + // UI references. + private EditText postEditText; + private TextView characterCountTextView; + private Button postButton; + + private int maxCharacterCount = Application.getConfigHelper().getPostMaxCharacterCount(); + private ParseGeoPoint geoPoint; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_post); + + Intent intent = getIntent(); + Location location = intent.getParcelableExtra(Application.INTENT_EXTRA_LOCATION); + geoPoint = new ParseGeoPoint(location.getLatitude(), location.getLongitude()); + + postEditText = (EditText) findViewById(R.id.post_edittext); + postEditText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) { + } + + @Override + public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) { + } + + @Override + public void afterTextChanged(Editable s) { + updatePostButtonState(); + updateCharacterCountTextViewText(); + } + }); + + characterCountTextView = (TextView) findViewById(R.id.character_count_textview); + + postButton = (Button) findViewById(R.id.post_button); + postButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + post(); + } + }); + + updatePostButtonState(); + updateCharacterCountTextViewText(); + } + + private void post () { + String text = postEditText.getText().toString().trim(); + + // Set up a progress dialog + final ProgressDialog dialog = new ProgressDialog(PostActivity.this); + dialog.setMessage(getString(R.string.progress_post)); + dialog.show(); + + // Create a post. + AnywallPost post = new AnywallPost(); + + // Set the location to the current user's location + post.setLocation(geoPoint); + post.setText(text); + post.setUser(ParseUser.getCurrentUser()); + ParseACL acl = new ParseACL(); + + // Give public read access + acl.setPublicReadAccess(true); + post.setACL(acl); + + // Save the post + post.saveInBackground(new SaveCallback() { + @Override + public void done(ParseException e) { + dialog.dismiss(); + finish(); + } + }); + } + + private String getPostEditTextText () { + return postEditText.getText().toString().trim(); + } + + private void updatePostButtonState () { + int length = getPostEditTextText().length(); + boolean enabled = length > 0 && length < maxCharacterCount; + postButton.setEnabled(enabled); + } + + private void updateCharacterCountTextViewText () { + String characterCountString = String.format("%d/%d", postEditText.length(), maxCharacterCount); + characterCountTextView.setText(characterCountString); + } +} diff --git a/app/src/main/java/com/parse/inoss/SettingsActivity.java b/app/src/main/java/com/parse/inoss/SettingsActivity.java new file mode 100644 index 0000000..6499e57 --- /dev/null +++ b/app/src/main/java/com/parse/inoss/SettingsActivity.java @@ -0,0 +1,74 @@ +package com.parse.inoss; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.RadioButton; +import android.widget.RadioGroup; +import android.widget.RadioGroup.OnCheckedChangeListener; + +import com.inoss.tuiapp.R; +import com.parse.ParseUser; + +import java.util.Collections; +import java.util.List; + +/** + * Activity that displays the settings screen. + */ +public class SettingsActivity extends Activity { + + private List availableOptions = Application.getConfigHelper().getSearchDistanceAvailableOptions(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_settings); + + float currentSearchDistance = Application.getSearchDistance(); + if (!availableOptions.contains(currentSearchDistance)) { + availableOptions.add(currentSearchDistance); + } + Collections.sort(availableOptions); + + // The search distance choices + RadioGroup searchDistanceRadioGroup = (RadioGroup) findViewById(R.id.searchdistance_radiogroup); + + for (int index = 0; index < availableOptions.size(); index++) { + float searchDistance = availableOptions.get(index); + + RadioButton button = new RadioButton(this); + button.setId(index); + button.setText(getString(R.string.settings_distance_format, (int)searchDistance)); + searchDistanceRadioGroup.addView(button, index); + + if (currentSearchDistance == searchDistance) { + searchDistanceRadioGroup.check(index); + } + } + + // Set up the selection handler to save the selection to the application + searchDistanceRadioGroup.setOnCheckedChangeListener(new OnCheckedChangeListener() { + public void onCheckedChanged(RadioGroup group, int checkedId) { + Application.setSearchDistance(availableOptions.get(checkedId)); + } + }); + + // Set up the log out button click handler + Button logoutButton = (Button) findViewById(R.id.logout_button); + logoutButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + // Call the Parse log out method + ParseUser.logOut(); + // Start and intent for the dispatch activity + Intent intent = new Intent(SettingsActivity.this, DispatchActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } + }); + } +} diff --git a/app/src/main/java/com/parse/inoss/SignUpActivity.java b/app/src/main/java/com/parse/inoss/SignUpActivity.java new file mode 100644 index 0000000..ed2f706 --- /dev/null +++ b/app/src/main/java/com/parse/inoss/SignUpActivity.java @@ -0,0 +1,123 @@ +package com.parse.inoss; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Intent; +import android.os.Bundle; +import android.view.KeyEvent; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; + +import com.inoss.tuiapp.R; +import com.parse.ParseException; +import com.parse.ParseUser; +import com.parse.SignUpCallback; + +/** + * Activity which displays a login screen to the user. + */ +public class SignUpActivity extends Activity { + // UI references. + private EditText usernameEditText; + private EditText passwordEditText; + private EditText passwordAgainEditText; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_signup); + + // Set up the signup form. + usernameEditText = (EditText) findViewById(R.id.username_edit_text); + + passwordEditText = (EditText) findViewById(R.id.password_edit_text); + passwordAgainEditText = (EditText) findViewById(R.id.password_again_edit_text); + passwordAgainEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if (actionId == R.id.edittext_action_signup || + actionId == EditorInfo.IME_ACTION_UNSPECIFIED) { + signup(); + return true; + } + return false; + } + }); + + // Set up the submit button click handler + Button mActionButton = (Button) findViewById(R.id.action_button); + mActionButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View view) { + signup(); + } + }); + } + + private void signup() { + String username = usernameEditText.getText().toString().trim(); + String password = passwordEditText.getText().toString().trim(); + String passwordAgain = passwordAgainEditText.getText().toString().trim(); + + // Validate the sign up data + boolean validationError = false; + StringBuilder validationErrorMessage = new StringBuilder(getString(R.string.error_intro)); + if (username.length() == 0) { + validationError = true; + validationErrorMessage.append(getString(R.string.error_blank_username)); + } + if (password.length() == 0) { + if (validationError) { + validationErrorMessage.append(getString(R.string.error_join)); + } + validationError = true; + validationErrorMessage.append(getString(R.string.error_blank_password)); + } + if (!password.equals(passwordAgain)) { + if (validationError) { + validationErrorMessage.append(getString(R.string.error_join)); + } + validationError = true; + validationErrorMessage.append(getString(R.string.error_mismatched_passwords)); + } + validationErrorMessage.append(getString(R.string.error_end)); + + // If there is a validation error, display the error + if (validationError) { + Toast.makeText(SignUpActivity.this, validationErrorMessage.toString(), Toast.LENGTH_LONG) + .show(); + return; + } + + // Set up a progress dialog + final ProgressDialog dialog = new ProgressDialog(SignUpActivity.this); + dialog.setMessage(getString(R.string.progress_signup)); + dialog.show(); + + // Set up a new Parse user + ParseUser user = new ParseUser(); + user.setUsername(username); + user.setPassword(password); + + // Call the Parse signup method + user.signUpInBackground(new SignUpCallback() { + @Override + public void done(ParseException e) { + dialog.dismiss(); + if (e != null) { + // Show the error message + Toast.makeText(SignUpActivity.this, e.getMessage(), Toast.LENGTH_LONG).show(); + } else { + // Start an intent for the dispatch activity + Intent intent = new Intent(SignUpActivity.this, DispatchActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } + } + }); + } +} diff --git a/app/src/main/java/com/parse/inoss/WelcomeActivity.java b/app/src/main/java/com/parse/inoss/WelcomeActivity.java new file mode 100644 index 0000000..bab6883 --- /dev/null +++ b/app/src/main/java/com/parse/inoss/WelcomeActivity.java @@ -0,0 +1,41 @@ +package com.parse.inoss; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; + +import com.inoss.tuiapp.R; + + +/** + * Activity which displays a registration screen to the user. + */ +public class WelcomeActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_welcome); + + // Log in button click handler + Button loginButton = (Button) findViewById(R.id.login_button); + loginButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + // Starts an intent of the log in activity + startActivity(new Intent(WelcomeActivity.this, LoginActivity.class)); + } + }); + + // Sign up button click handler + Button signupButton = (Button) findViewById(R.id.signup_button); + signupButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + // Starts an intent for the sign up activity + startActivity(new Intent(WelcomeActivity.this, SignUpActivity.class)); + } + }); + } +} diff --git a/app/src/main/res/drawable-hdpi/ic_launcher.png b/app/src/main/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 0000000..b9e79d0 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_launcher.png b/app/src/main/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 0000000..9985f30 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_launcher.png b/app/src/main/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 0000000..f33448c Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_launcher.png b/app/src/main/res/drawable-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..7ee00ab Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml new file mode 100644 index 0000000..ed89004 --- /dev/null +++ b/app/src/main/res/layout/activity_login.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + +