diff --git a/.travis.yml b/.travis.yml index dfc367c..5985f40 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ android: - platform-tools - tools # The BuildTools version used by your project - - build-tools-24.0.0 + - build-tools-24.0.2 - android-24 - extra-android-m2repository - extra-google-m2repository @@ -20,6 +20,11 @@ android: # Specify at least one system image, if you need to run emulator(s) during your tests - sys-img-armeabi-v7a-android-24 + licenses: + - android-sdk-preview-license-.+ + - android-sdk-license-.+ + - google-gdk-license-.+ + notifications: email: false diff --git a/CONTRIBUTING.MD b/CONTRIBUTING.MD deleted file mode 100644 index 47b00e3..0000000 --- a/CONTRIBUTING.MD +++ /dev/null @@ -1,116 +0,0 @@ -# How to contribute - -Third-party patches are essential for keeping Puppet great. We simply can't -access the huge number of platforms and myriad configurations for running -Puppet. We want to keep it as easy as possible to contribute changes that -get things working in your environment. There are a few guidelines that we -need contributors to follow so that we can have a chance of keeping on -top of things. - -## Puppet Core vs Modules - -New functionality is typically directed toward modules to provide a slimmer -Puppet Core, reducing its surface area, and to allow greater freedom for -module maintainers to ship releases at their own cadence, rather than -being held to the cadence of Puppet releases. With Puppet 4's "all in one" -packaging, a list of modules at specific versions will be packaged with the -core so that popular types and providers will still be available as part of -the "out of the box" experience. - -Generally, new types and new OS-specific providers for existing types should -be added in modules. Exceptions would be things like new cross-OS providers -and updates to existing core types. - -If you are unsure of whether your contribution should be implemented as a -module or part of Puppet Core, you may visit -[#puppet-dev on Freenode IRC](https://freenode.net) or ask on the -[puppet-dev mailing list](https://groups.google.com/forum/#!forum/puppet-dev) -for advice. - -## Getting Started - -* Make sure you have a [Jira account](https://tickets.puppetlabs.com) -* Make sure you have a [GitHub account](https://github.com/signup/free) -* Submit a ticket for your issue, assuming one does not already exist. - * Clearly describe the issue including steps to reproduce when it is a bug. - * Make sure you fill in the earliest version that you know has the issue. -* Fork the repository on GitHub - -## Making Changes - -* Create a topic branch from where you want to base your work. - * This is usually the master branch. - * Only target release branches if you are certain your fix must be on that - branch. - * To quickly create a topic branch based on master; `git checkout -b - fix/master/my_contribution master`. Please avoid working directly on the - `master` branch. -* Make commits of logical units. -* Check for unnecessary whitespace with `git diff --check` before committing. -* Make sure your commit messages are in the proper format. - -```` - (PUP-1234) Make the example in CONTRIBUTING imperative and concrete - - Without this patch applied the example commit message in the CONTRIBUTING - document is not a concrete example. This is a problem because the - contributor is left to imagine what the commit message should look like - based on a description rather than an example. This patch fixes the - problem by making the example concrete and imperative. - - The first line is a real life imperative statement with a ticket number - from our issue tracker. The body describes the behavior without the patch, - why this is a problem, and how the patch fixes the problem when applied. -```` - -* Make sure you have added the necessary tests for your changes. -* Run _all_ the tests to assure nothing else was accidentally broken. - -## Making Trivial Changes - -### Documentation - -For changes of a trivial nature to comments and documentation, it is not -always necessary to create a new ticket in Jira. In this case, it is -appropriate to start the first line of a commit with '(doc)' instead of -a ticket number. - -```` - (doc) Add documentation commit example to CONTRIBUTING - - There is no example for contributing a documentation commit - to the Puppet repository. This is a problem because the contributor - is left to assume how a commit of this nature may appear. - - The first line is a real life imperative statement with '(doc)' in - place of what would have been the ticket number in a - non-documentation related commit. The body describes the nature of - the new documentation or comments added. -```` - -## Submitting Changes - -* Sign the [Contributor License Agreement](http://links.puppet.com/cla). -* Push your changes to a topic branch in your fork of the repository. -* Submit a pull request to the repository in the puppetlabs organization. -* Update your Jira ticket to mark that you have submitted code and are ready for it to be reviewed (Status: Ready for Merge). - * Include a link to the pull request in the ticket. -* The core team looks at Pull Requests on a regular basis in a weekly triage - meeting that we hold in a public Google Hangout. The hangout is announced in - the weekly status updates that are sent to the puppet-dev list. Notes are - posted to the [Puppet Community community-triage - repo](https://github.com/puppet-community/community-triage/tree/master/core/notes) - and include a link to a YouTube recording of the hangout. -* After feedback has been given we expect responses within two weeks. After two - weeks we may close the pull request if it isn't showing any activity. - -# Additional Resources - -* [Puppet community guidelines](https://docs.puppet.com/community/community_guidelines.html) -* [Bug tracker (Jira)](https://tickets.puppetlabs.com) -* [Contributor License Agreement](http://links.puppet.com/cla) -* [General GitHub documentation](https://help.github.com/) -* [GitHub pull request documentation](https://help.github.com/send-pull-requests/) -* #puppet-dev IRC channel on freenode.org ([Archive](https://botbot.me/freenode/puppet-dev/)) -* [puppet-dev mailing list](https://groups.google.com/forum/#!forum/puppet-dev) -* [Community PR Triage notes](https://github.com/puppet-community/community-triage/tree/master/core/notes) \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..358a990 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + 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 2014 The Android Open Source Project + + 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. \ No newline at end of file diff --git a/README.MD b/README.MD index 11c8670..728628f 100644 --- a/README.MD +++ b/README.MD @@ -44,29 +44,20 @@ This sample is available [![Get it on Google Play](../development/art/google-play-badge.png)](https://play.google.com/store/apps/details?id=com.joaquimley.avenging) ## Configs & API authentication -If you wish to run your own **CI** (I'm using Travis) ensure you have the following environment variables: +If you wish to run your own **CI** (I'm using Travis) ensure you have the following environment variables on **your CI config**: * IS_ CI_JOB = true * CI_ PRODUCTION_ENDPOINT = http://gateway.marvel.com/v1/public/ * CI_ PUBLIC_KEY = < YOUR-PUBLIC-KEY > * CI_ PRIVATE_KEY = < YOUR-PRIVATE-KEY > -* CI_ ANDROID_ BUILD_ TARGET_ SDK_VERSION = 24 -* CI_ ANDROID_ BUILD_ MIN_ SDK_VERSION = 17 -* CI_ ANDROID_ BUILD_ TOOLS_VERSION = 24.0.0 -* CI_ ANDROID_ BUILD_ SDK_VERSION = 24 - To run **locally** have your private and public keys in your **gradle.properties** file (project's root folder). * marvelProductionEndpoint = http://gateway.marvel.com/v1/public/ * marvelPublicKey = < YOUR-PUBLIC-KEY > * marvelPrivateKey = < YOUR-PRIVATE-KEY > -* ANDROID_ BUILD_ TARGET_SDK_VERSION = 24 -* ANDROID_ BUILD_ MIN_SDK_VERSION = 17 -* ANDROID_ BUILD_ TOOLS_VERSION = 24.0.0 -* ANDROID_ BUILD_ SDK_VERSION = 24 -Mind the markedown spaces. +_Mind the markedown spaces._ ## Dependencies diff --git a/build.gradle b/build.gradle index 52d178e..940e0e3 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.1.3' + classpath 'com.android.tools.build:gradle:2.2.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -32,6 +32,23 @@ allprojects { repositories { jcenter() } + + project.ext { + compileSdkVersion = 24 + buildToolsVersion = '24.0.2' + + junitVersion = '4.12' + mockitoVersion = '1.10.19' + espressoVersion = '2.2.1' + + supportLibraryVersion = '24.2.1' + playServicesVersion = '9.4.0' + leakCanaryVersion = '1.3.1' + + retrofitVersion = '2.1.0' + okhttpLoggerVsion = '3.3.0' + picassoVersion = '2.5.2' + } } task clean(type: Delete) { diff --git a/core/build.gradle b/core/build.gradle index 1fb6829..640beb1 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -1,24 +1,19 @@ apply plugin: 'com.android.library' android { - if (System.getenv().containsKey("IS_CI_JOB")) { - compileSdkVersion Integer.parseInt(System.getenv("CI_ANDROID_BUILD_SDK_VERSION")) - buildToolsVersion System.getenv("CI_ANDROID_BUILD_TOOLS_VERSION") - } else { - compileSdkVersion Integer.parseInt(project.ANDROID_BUILD_SDK_VERSION) - buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION - } + compileSdkVersion project.compileSdkVersion + buildToolsVersion project.buildToolsVersion + publishNonDefault true defaultConfig { + minSdkVersion 11 + targetSdkVersion 24 + if (System.getenv().containsKey("IS_CI_JOB")) { - minSdkVersion Integer.parseInt(System.getenv("CI_ANDROID_BUILD_MIN_SDK_VERSION")) - targetSdkVersion Integer.parseInt(System.getenv("CI_ANDROID_BUILD_TARGET_SDK_VERSION")) buildConfigField("String", "PRODUCTION_ENDPOINT", "\"" + System.getenv("CI_PRODUCTION_ENDPOINT") + "\"") buildConfigField("String", "PUBLIC_KEY", "\"" + System.getenv("CI_PUBLIC_KEY") + "\"") buildConfigField("String", "PRIVATE_KEY", "\"" + System.getenv("CI_PRIVATE_KEY") + "\"") } else { - minSdkVersion Integer.parseInt(project.ANDROID_BUILD_MIN_SDK_VERSION) - targetSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION) buildConfigField("String", "PRODUCTION_ENDPOINT", "\"${marvelProductionEndpoint}\"") buildConfigField("String", "PUBLIC_KEY", "\"${marvelPublicKey}\"") buildConfigField("String", "PRIVATE_KEY", "\"${marvelPrivateKey}\"") @@ -41,32 +36,18 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } - - packagingOptions { - exclude 'META-INF/LICENSE.txt' - exclude 'META-INF/NOTICE.txt' - exclude 'META-INF/LICENSE' - exclude 'META-INF/NOTICE' - } } dependencies { - final SUPPORT_LIBRARY_VERSION = '24.0.0' - final RETROFIT_VERSION = '2.0.2' - final OK_HTTP_VERSION = '3.0.0' - final LEAK_CANARY_VERSION = '1.3.1' - final MOCKITO_VERSION = '1.10.5' - final JUNIT_VERSION = '4.12' - - compile "com.android.support:support-annotations:$SUPPORT_LIBRARY_VERSION" - compile "com.squareup.retrofit2:retrofit:$RETROFIT_VERSION" - compile "com.squareup.retrofit2:converter-jackson:$RETROFIT_VERSION" - compile "com.squareup.okhttp3:logging-interceptor:$OK_HTTP_VERSION" + compile "com.android.support:support-annotations:$supportLibraryVersion" + compile "com.squareup.retrofit2:retrofit:$retrofitVersion" + compile "com.squareup.retrofit2:converter-jackson:$retrofitVersion" + compile "com.squareup.okhttp3:logging-interceptor:$okhttpLoggerVsion" // LeakCanary - testCompile "com.squareup.leakcanary:leakcanary-android-no-op:$LEAK_CANARY_VERSION" - debugCompile "com.squareup.leakcanary:leakcanary-android:$LEAK_CANARY_VERSION" - releaseCompile "com.squareup.leakcanary:leakcanary-android-no-op:$LEAK_CANARY_VERSION" + testCompile "com.squareup.leakcanary:leakcanary-android-no-op:$leakCanaryVersion" + debugCompile "com.squareup.leakcanary:leakcanary-android:$leakCanaryVersion" + releaseCompile "com.squareup.leakcanary:leakcanary-android-no-op:$leakCanaryVersion" // Unit tests dependencies - testCompile "junit:junit:$JUNIT_VERSION" - testCompile "org.mockito:mockito-core:$MOCKITO_VERSION" + testCompile "junit:junit:$junitVersion" + testCompile "org.mockito:mockito-core:$mockitoVersion" } diff --git a/core/src/main/java/com/joaquimley/core/AvengingApplication.java b/core/src/main/java/com/joaquimley/core/AvengingApplication.java index b3f23a0..98ab920 100644 --- a/core/src/main/java/com/joaquimley/core/AvengingApplication.java +++ b/core/src/main/java/com/joaquimley/core/AvengingApplication.java @@ -18,36 +18,13 @@ import android.app.Application; -import com.joaquimley.core.data.network.MarvelService; -import com.joaquimley.core.data.network.MarvelServiceFactory; import com.squareup.leakcanary.LeakCanary; public class AvengingApplication extends Application { - private static AvengingApplication sInstance; - private static MarvelService sMarvelService; -// private static PresenterCache sPresenterCache; - @Override public void onCreate() { super.onCreate(); LeakCanary.install(this); - sInstance = this; - sMarvelService = MarvelServiceFactory.makeMarvelService(true); -// sPresenterCache = PresenterCache.makePresenterCache(); - } - - public static AvengingApplication getInstance() { - if (sInstance == null) { - sInstance = new AvengingApplication(); - } - return sInstance; - } - - public MarvelService getMarvelService() { - if (sMarvelService == null) { - sMarvelService = MarvelServiceFactory.makeMarvelService(BuildConfig.DEBUG); - } - return sMarvelService; } } diff --git a/core/src/main/java/com/joaquimley/core/data/network/MarvelServiceConfig.java b/core/src/main/java/com/joaquimley/core/data/DataManager.java similarity index 55% rename from core/src/main/java/com/joaquimley/core/data/network/MarvelServiceConfig.java rename to core/src/main/java/com/joaquimley/core/data/DataManager.java index c7a4461..f221112 100644 --- a/core/src/main/java/com/joaquimley/core/data/network/MarvelServiceConfig.java +++ b/core/src/main/java/com/joaquimley/core/data/DataManager.java @@ -14,25 +14,102 @@ * limitations under the License. */ -package com.joaquimley.core.data.network; +package com.joaquimley.core.data; +import android.support.annotation.StringDef; import android.util.Log; import com.joaquimley.core.BuildConfig; +import com.joaquimley.core.data.model.CharacterMarvel; +import com.joaquimley.core.data.model.Comic; +import com.joaquimley.core.data.model.DataWrapper; +import com.joaquimley.core.data.network.MarvelService; +import com.joaquimley.core.data.network.MarvelServiceFactory; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.List; + +import retrofit2.Call; +import retrofit2.Callback; /** - * Constants for Api + * Api abstraction */ -public class MarvelServiceConfig { +public class DataManager { + + private static DataManager sInstance; + + private final MarvelService mMarvelService; + + public static DataManager getInstance() { + if (sInstance == null) { + sInstance = new DataManager(); + } + return sInstance; + } + + private DataManager() { + mMarvelService = MarvelServiceFactory.makeMarvelService(); + } + + public void getCharactersList(int offSet, int limit, String searchQuery, + Callback>> listener) { + long timeStamp = System.currentTimeMillis(); + mMarvelService.getCharacters(BuildConfig.PUBLIC_KEY, + buildMd5AuthParameter(timeStamp), timeStamp, offSet, limit, searchQuery) + .enqueue(listener); + } - public static final int HTTP_CONNECT_TIMEOUT = 6000; - public static final int HTTP_READ_TIMEOUT = 10000; - public static final String MD5 = "MD5"; - public static final String MD5_DIGEST_FIRST_CHAR = "0"; + public void getCharacter(long characterId, Callback>> listener) { + long timeStamp = System.currentTimeMillis(); + mMarvelService.getCharacter(characterId, BuildConfig.PUBLIC_KEY, + buildMd5AuthParameter(timeStamp), timeStamp) + .enqueue(listener); + } + + + private static final String COMIC_TYPE_COMICS = "comics"; + private static final String COMIC_TYPE_SERIES = "series"; + private static final String COMIC_TYPE_STORIES = "stories"; + private static final String COMIC_TYPE_EVENTS = "events"; + + @StringDef({COMIC_TYPE_COMICS, COMIC_TYPE_SERIES, COMIC_TYPE_STORIES, COMIC_TYPE_EVENTS}) + @Retention(RetentionPolicy.SOURCE) + private @interface Type { + } + + public void getComics(long characterId, Integer offset, Integer limit, Callback>> listener) { + getComicListByType(characterId, COMIC_TYPE_COMICS, offset, limit).enqueue(listener); + } + + public void getSeries(long characterId, Integer offset, Integer limit, Callback>> listener) { + getComicListByType(characterId, COMIC_TYPE_SERIES, offset, limit).enqueue(listener); + } + + public void getStories(long characterId, Integer offset, Integer limit, Callback>> listener) { + getComicListByType(characterId, COMIC_TYPE_STORIES, offset, limit).enqueue(listener); + } + + public void getEvents(long characterId, Integer offset, Integer limit, Callback>> listener) { + getComicListByType(characterId, COMIC_TYPE_EVENTS, offset, limit).enqueue(listener); + } + + /** + * Base request to prevent boilerplate + * + * @param id {@link CharacterMarvel} Id + * @param comicType Which {@link .Type} list should be requested + */ + private Call>> getComicListByType(long id, @Type String comicType, + Integer offset, Integer limit) { + long timeStamp = System.currentTimeMillis(); + return mMarvelService.getCharacterComics(id, comicType, offset, limit, BuildConfig.PUBLIC_KEY, + buildMd5AuthParameter(timeStamp), timeStamp); + } /** * Builds the required API "hash" parameter (timeStamp + privateKey + publicKey) @@ -40,25 +117,25 @@ public class MarvelServiceConfig { * @param timeStamp Current timeStamp * @return MD5 hash string */ - public static String buildMd5AuthParameter(long timeStamp) { + private static String buildMd5AuthParameter(long timeStamp) { try { - MessageDigest md = MessageDigest.getInstance(MD5); - byte[] messageDigest = md.digest((timeStamp + BuildConfig.PRIVATE_KEY + BuildConfig.PUBLIC_KEY).getBytes()); + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] messageDigest = md.digest((timeStamp + BuildConfig.PRIVATE_KEY + + BuildConfig.PUBLIC_KEY).getBytes()); BigInteger number = new BigInteger(1, messageDigest); String md5 = number.toString(16); while (md5.length() < 32) { - md5 = MD5_DIGEST_FIRST_CHAR + md5; + md5 = 0 + md5; } return md5; } catch (NoSuchAlgorithmException e) { - Log.e("MarvelServiceConfig", "Error hashing required parameters: " + e.getMessage()); + Log.e("DataManager", "Error hashing required parameters: " + e.getMessage()); return ""; } } - } /** @@ -107,6 +184,15 @@ public static String buildMd5AuthParameter(long timeStamp) { * 401 Invalid Hash Occurs when a ts, hash and apikey parameter are sent but the hash is not valid per the above hash generation rule. * 405 Method Not Allowed Occurs when an API endpoint is accessed using an HTTP verb which is not allowed for that endpoint. * 403 Forbidden + *

+ * -- Authorization -- + * 409 Missing API Key Occurs when the apikey parameter is not included with a request. + * 409 Missing Hash Occurs when an apikey parameter is included with a request, a ts parameter is present, but no hash parameter is sent. Occurs on server-side applications only. + * 409 Missing Timestamp Occurs when an apikey parameter is included with a request, a hash parameter is present, but no ts parameter is sent. Occurs on server-side applications only. + * 401 Invalid Referer Occurs when a referrer which is not valid for the passed apikey parameter is sent. + * 401 Invalid Hash Occurs when a ts, hash and apikey parameter are sent but the hash is not valid per the above hash generation rule. + * 405 Method Not Allowed Occurs when an API endpoint is accessed using an HTTP verb which is not allowed for that endpoint. + * 403 Forbidden */ /** diff --git a/core/src/main/java/com/joaquimley/core/data/MarvelDataManager.java b/core/src/main/java/com/joaquimley/core/data/MarvelDataManager.java deleted file mode 100644 index 2097eef..0000000 --- a/core/src/main/java/com/joaquimley/core/data/MarvelDataManager.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) Joaquim Ley 2016. All Rights Reserved. - *

- * 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. - */ - -package com.joaquimley.core.data; - -import com.joaquimley.core.BuildConfig; -import com.joaquimley.core.data.model.CharacterDataWrapper; -import com.joaquimley.core.data.model.CharacterMarvel; -import com.joaquimley.core.data.model.Comic; -import com.joaquimley.core.data.model.ComicDataWrapper; -import com.joaquimley.core.data.network.MarvelService; -import com.joaquimley.core.data.network.MarvelServiceConfig; - -import retrofit2.Call; -import retrofit2.Retrofit; - -/** - * Api abstraction - */ -public class MarvelDataManager { - - private final MarvelService mMarvelService; - - public MarvelDataManager(MarvelService marvelService) { - mMarvelService = marvelService; - } - - public Call getCharactersList(int offSet, int limit, String searchQuery) { - long timeStamp = System.currentTimeMillis(); - return mMarvelService.getCharacters(BuildConfig.PUBLIC_KEY, - MarvelServiceConfig.buildMd5AuthParameter(timeStamp), timeStamp, offSet, limit, searchQuery); - } - - public Call getCharacter(long characterId) { - long timeStamp = System.currentTimeMillis(); - return mMarvelService.getCharacter(characterId, BuildConfig.PUBLIC_KEY, - MarvelServiceConfig.buildMd5AuthParameter(timeStamp), timeStamp); - } - - public Call getComics(long characterId, Integer offset, Integer limit) { - return getComicListByType(characterId, Comic.COMIC_TYPE_COMICS, offset, limit); - } - - public Call getSeries(long characterId, Integer offset, Integer limit) { - return getComicListByType(characterId, Comic.COMIC_TYPE_SERIES, offset, limit); - } - - public Call getStories(long characterId, Integer offset, Integer limit) { - return getComicListByType(characterId, Comic.COMIC_TYPE_STORIES, offset, limit); - } - - public Call getEvents(long characterId, Integer offset, Integer limit) { - return getComicListByType(characterId, Comic.COMIC_TYPE_EVENTS, offset, limit); - } - - /** - * Base request to prevent boilerplate - * - * @param id {@link CharacterMarvel} Id - * @param comicType Which {@link Comic.Type} list should be requested - * @return A simple {@link Retrofit} call with preset parameters - */ - private Call getComicListByType(long id, String comicType, Integer offset, Integer limit) { - long timeStamp = System.currentTimeMillis(); - return mMarvelService.getCharacterComics(id, comicType, offset, limit, BuildConfig.PUBLIC_KEY, - MarvelServiceConfig.buildMd5AuthParameter(timeStamp), timeStamp); - } -} diff --git a/core/src/main/java/com/joaquimley/core/data/model/CharacterComic.java b/core/src/main/java/com/joaquimley/core/data/model/CharacterComic.java deleted file mode 100644 index 6724b81..0000000 --- a/core/src/main/java/com/joaquimley/core/data/model/CharacterComic.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) Joaquim Ley 2016. All Rights Reserved. - *

- * 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. - */ - -package com.joaquimley.core.data.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - -@JsonIgnoreProperties(ignoreUnknown = true) -public class CharacterComic { - - @JsonProperty("name") - protected String mName; - @JsonProperty("resourceURI") - protected String mResourceUri; - - public CharacterComic() { - } - - public int getIdFromResourceUri() { - return mResourceUri != null ? Integer.parseInt(mResourceUri.substring(mResourceUri.lastIndexOf("/") + 1)) : -1; - } - - public String getResourceUri() { - return mResourceUri; - } - - public void setResourceUri(String resourceUri) { - mResourceUri = resourceUri; - } - - public String getName() { - return mName; - } - - public void setName(String name) { - mName = name; - } - - -} diff --git a/core/src/main/java/com/joaquimley/core/data/model/CharacterComicDataContainer.java b/core/src/main/java/com/joaquimley/core/data/model/CharacterComicWrapper.java similarity index 95% rename from core/src/main/java/com/joaquimley/core/data/model/CharacterComicDataContainer.java rename to core/src/main/java/com/joaquimley/core/data/model/CharacterComicWrapper.java index e62fbcc..aff761f 100644 --- a/core/src/main/java/com/joaquimley/core/data/model/CharacterComicDataContainer.java +++ b/core/src/main/java/com/joaquimley/core/data/model/CharacterComicWrapper.java @@ -23,7 +23,7 @@ import java.util.List; @JsonIgnoreProperties(ignoreUnknown = true) -public class CharacterComicDataContainer { +public class CharacterComicWrapper { @JsonProperty("available") protected int mAvailable; @@ -35,7 +35,7 @@ public class CharacterComicDataContainer { public List mItems = new ArrayList<>(); - public CharacterComicDataContainer() { + public CharacterComicWrapper() { } diff --git a/core/src/main/java/com/joaquimley/core/data/model/CharacterDataContainer.java b/core/src/main/java/com/joaquimley/core/data/model/CharacterDataContainer.java deleted file mode 100644 index 3472d20..0000000 --- a/core/src/main/java/com/joaquimley/core/data/model/CharacterDataContainer.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) Joaquim Ley 2016. All Rights Reserved. - *

- * 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. - */ - -package com.joaquimley.core.data.model; - -import com.fasterxml.jackson.annotation.JsonProperty; - -import java.util.ArrayList; -import java.util.List; - -public class CharacterDataContainer extends BaseDataContainer { - - @JsonProperty("results") - public List mResults = new ArrayList<>(); - - public CharacterDataContainer() { - } - - public List getResults() { - return mResults; - } - - public void setResults(List results) { - mResults = results; - } -} diff --git a/core/src/main/java/com/joaquimley/core/data/model/CharacterDataWrapper.java b/core/src/main/java/com/joaquimley/core/data/model/CharacterDataWrapper.java deleted file mode 100644 index fb29d19..0000000 --- a/core/src/main/java/com/joaquimley/core/data/model/CharacterDataWrapper.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) Joaquim Ley 2016. All Rights Reserved. - *

- * 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. - */ - -package com.joaquimley.core.data.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - -@JsonIgnoreProperties(ignoreUnknown = true) -public class CharacterDataWrapper extends BaseDataWrapper { - - @JsonProperty("data") - public CharacterDataContainer mData; - - public CharacterDataWrapper(){ - } - - public CharacterDataContainer getData() { - return mData; - } - - public void setData(CharacterDataContainer data) { - mData = data; - } -} diff --git a/core/src/main/java/com/joaquimley/core/data/model/CharacterMarvel.java b/core/src/main/java/com/joaquimley/core/data/model/CharacterMarvel.java index 56f0171..c9f36b6 100644 --- a/core/src/main/java/com/joaquimley/core/data/model/CharacterMarvel.java +++ b/core/src/main/java/com/joaquimley/core/data/model/CharacterMarvel.java @@ -40,13 +40,13 @@ public class CharacterMarvel implements Parcelable { @JsonProperty(value = "thumbnail") private Image mThumbnail; @JsonProperty(value = "comics") - private CharacterComicDataContainer mComics; + private CharacterComicWrapper mComics; @JsonProperty(value = "series") - private CharacterComicDataContainer mSeries; + private CharacterComicWrapper mSeries; @JsonProperty(value = "stories") - private CharacterComicDataContainer mStories; + private CharacterComicWrapper mStories; @JsonProperty(value = "events") - private CharacterComicDataContainer mEvents; + private CharacterComicWrapper mEvents; @JsonProperty(value = "urls") private List mUrls; @@ -102,35 +102,35 @@ public void setThumbnail(Image thumbnail) { mThumbnail = thumbnail; } - public CharacterComicDataContainer getComics() { + public CharacterComicWrapper getComics() { return mComics; } - public void setComics(CharacterComicDataContainer comics) { + public void setComics(CharacterComicWrapper comics) { mComics = comics; } - public CharacterComicDataContainer getSeries() { + public CharacterComicWrapper getSeries() { return mSeries; } - public void setSeries(CharacterComicDataContainer series) { + public void setSeries(CharacterComicWrapper series) { mSeries = series; } - public CharacterComicDataContainer getStories() { + public CharacterComicWrapper getStories() { return mStories; } - public void setStories(CharacterComicDataContainer stories) { + public void setStories(CharacterComicWrapper stories) { mStories = stories; } - public CharacterComicDataContainer getEvents() { + public CharacterComicWrapper getEvents() { return mEvents; } - public void setEvents(CharacterComicDataContainer events) { + public void setEvents(CharacterComicWrapper events) { mEvents = events; } @@ -142,6 +142,19 @@ public void setUrls(List urls) { mUrls = urls; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof CharacterMarvel)) return false; + CharacterMarvel that = (CharacterMarvel) o; + return mId == that.mId; + } + + @Override + public int hashCode() { + return (int) (mId ^ (mId >>> 32)); + } + @Override public int describeContents() { return 0; diff --git a/core/src/main/java/com/joaquimley/core/data/model/Comic.java b/core/src/main/java/com/joaquimley/core/data/model/Comic.java index e652039..087ed81 100644 --- a/core/src/main/java/com/joaquimley/core/data/model/Comic.java +++ b/core/src/main/java/com/joaquimley/core/data/model/Comic.java @@ -19,27 +19,15 @@ import android.os.Parcel; import android.os.Parcelable; -import android.support.annotation.StringDef; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; @JsonIgnoreProperties(ignoreUnknown = true) -public class Comic extends CharacterComic implements Parcelable { - - public static final String COMIC_TYPE_COMICS = "comics"; - public static final String COMIC_TYPE_SERIES = "series"; - public static final String COMIC_TYPE_STORIES = "stories"; - public static final String COMIC_TYPE_EVENTS = "events"; - - @StringDef({COMIC_TYPE_COMICS, COMIC_TYPE_SERIES, COMIC_TYPE_STORIES, COMIC_TYPE_EVENTS}) - @Retention(RetentionPolicy.SOURCE) - public @interface Type {} +public class Comic implements Parcelable { @JsonProperty("id") private Integer mId; @@ -53,11 +41,14 @@ public class Comic extends CharacterComic implements Parcelable { private Integer mEndYear; @JsonProperty("rating") private String mRating; - @Type @JsonProperty("type") private String mType; @JsonProperty("modified") private String mModified; + @JsonProperty("name") + private String mName; + @JsonProperty("resourceURI") + private String mResourceUri; @JsonProperty(value = "thumbnail") private Image mThumbnail; @JsonProperty(value = "images") @@ -90,6 +81,59 @@ public ArrayList getImageUrlList() { return results; } + + @Override + public int describeContents() { + return 0; + } + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Comic)) return false; + Comic comic = (Comic) o; + return mId != null ? mId.equals(comic.mId) : comic.mId == null + && (mType != null ? mType.equals(comic.mType) : comic.mType == null); + + } + + @Override + public int hashCode() { + int result = mId != null ? mId.hashCode() : 0; + result = 31 * result + (mType != null ? mType.hashCode() : 0); + return result; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeValue(this.mId); + dest.writeString(this.mTitle); + dest.writeString(this.mResourceUri); + dest.writeParcelable(this.mThumbnail, flags); + dest.writeTypedList(this.mImageList); + } + + protected Comic(Parcel in) { + this.mId = (Integer) in.readValue(Integer.class.getClassLoader()); + this.mTitle = in.readString(); + this.mResourceUri = in.readString(); + this.mThumbnail = in.readParcelable(Image.class.getClassLoader()); + this.mImageList = in.createTypedArrayList(Image.CREATOR); + } + + public static final Creator CREATOR = new Creator() { + @Override + public Comic createFromParcel(Parcel source) { + return new Comic(source); + } + + @Override + public Comic[] newArray(int size) { + return new Comic[size]; + } + }; + public Integer getId() { return mId; } @@ -142,7 +186,7 @@ public String getType() { return mType; } - public void setType(@Type String type) { + public void setType(String type) { mType = type; } @@ -154,45 +198,35 @@ public void setModified(String modified) { mModified = modified; } - public Image getThumbnail() { - return mThumbnail; + public String getName() { + return mName; } - public void setThumbnail(Image thumbnail) { - mThumbnail = thumbnail; + public void setName(String name) { + mName = name; } - @Override - public int describeContents() { - return 0; + public String getResourceUri() { + return mResourceUri; } - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeValue(this.mId); - dest.writeString(this.mTitle); - dest.writeString(this.mResourceUri); - dest.writeParcelable(this.mThumbnail, flags); - dest.writeTypedList(this.mImageList); + public void setResourceUri(String resourceUri) { + mResourceUri = resourceUri; } - protected Comic(Parcel in) { - this.mId = (Integer) in.readValue(Integer.class.getClassLoader()); - this.mTitle = in.readString(); - this.mResourceUri = in.readString(); - this.mThumbnail = in.readParcelable(Image.class.getClassLoader()); - this.mImageList = in.createTypedArrayList(Image.CREATOR); + public Image getThumbnail() { + return mThumbnail; } - public static final Creator CREATOR = new Creator() { - @Override - public Comic createFromParcel(Parcel source) { - return new Comic(source); - } + public void setThumbnail(Image thumbnail) { + mThumbnail = thumbnail; + } - @Override - public Comic[] newArray(int size) { - return new Comic[size]; - } - }; + public List getImageList() { + return mImageList; + } + + public void setImageList(List imageList) { + mImageList = imageList; + } } diff --git a/core/src/main/java/com/joaquimley/core/data/model/ComicDataContainer.java b/core/src/main/java/com/joaquimley/core/data/model/ComicDataContainer.java deleted file mode 100644 index 07e69a6..0000000 --- a/core/src/main/java/com/joaquimley/core/data/model/ComicDataContainer.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (c) Joaquim Ley 2016. All Rights Reserved. - *

- * 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. - */ - -package com.joaquimley.core.data.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; - -import java.util.ArrayList; -import java.util.List; - -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonIgnoreProperties(ignoreUnknown = true) -public class ComicDataContainer extends BaseDataContainer { - - @JsonProperty("code") - protected int mCode; - @JsonProperty("code") - protected String mStatus; - @JsonProperty("results") - private List mResults = new ArrayList<>(); - - public ComicDataContainer() { - } - - public int getCode() { - return mCode; - } - - public void setCode(int code) { - mCode = code; - } - - public String getStatus() { - return mStatus; - } - - public void setStatus(String status) { - mStatus = status; - } - - public List getResults() { - return mResults; - } - - public void setResults(List results) { - mResults = results; - } - - public Integer getOffset() { - return mOffset; - } - - public void setOffset(Integer offset) { - mOffset = offset; - } - - public Integer getLimit() { - return mLimit; - } - - public void setLimit(Integer limit) { - mLimit = limit; - } - - public Integer getTotal() { - return mTotal; - } - - public void setTotal(Integer total) { - mTotal = total; - } - - public Integer getCount() { - return mCount; - } - - public void setCount(Integer count) { - mCount = count; - } -} diff --git a/core/src/main/java/com/joaquimley/core/data/model/ComicDataWrapper.java b/core/src/main/java/com/joaquimley/core/data/model/ComicDataWrapper.java deleted file mode 100644 index 8ba3747..0000000 --- a/core/src/main/java/com/joaquimley/core/data/model/ComicDataWrapper.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) Joaquim Ley 2016. All Rights Reserved. - *

- * 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. - */ - -package com.joaquimley.core.data.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - -@JsonIgnoreProperties(ignoreUnknown = true) -public class ComicDataWrapper extends BaseDataWrapper { - - @JsonProperty("data") - public ComicDataContainer mData; - - public ComicDataWrapper() { - } - - public ComicDataContainer getData() { - return mData; - } - - public void setData(ComicDataContainer data) { - mData = data; - } -} diff --git a/core/src/main/java/com/joaquimley/core/data/model/BaseDataContainer.java b/core/src/main/java/com/joaquimley/core/data/model/DataContainer.java similarity index 83% rename from core/src/main/java/com/joaquimley/core/data/model/BaseDataContainer.java rename to core/src/main/java/com/joaquimley/core/data/model/DataContainer.java index 7213973..7f8fc4a 100644 --- a/core/src/main/java/com/joaquimley/core/data/model/BaseDataContainer.java +++ b/core/src/main/java/com/joaquimley/core/data/model/DataContainer.java @@ -17,19 +17,10 @@ package com.joaquimley.core.data.model; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; - -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonPropertyOrder({ - "offset", - "limit", - "total", - "count", -}) + @JsonIgnoreProperties(ignoreUnknown = true) -public class BaseDataContainer { +public class DataContainer { @JsonProperty("offset") protected Integer mOffset; @@ -39,8 +30,10 @@ public class BaseDataContainer { protected Integer mTotal; @JsonProperty("count") protected Integer mCount; + @JsonProperty("results") + protected T mResults; - public BaseDataContainer() { + public DataContainer() { } public Integer getOffset() { @@ -74,4 +67,13 @@ public Integer getCount() { public void setCount(Integer count) { mCount = count; } + + + public T getResults() { + return mResults; + } + + public void setResults(T results) { + mResults = results; + } } diff --git a/core/src/main/java/com/joaquimley/core/data/model/BaseDataWrapper.java b/core/src/main/java/com/joaquimley/core/data/model/DataWrapper.java similarity index 89% rename from core/src/main/java/com/joaquimley/core/data/model/BaseDataWrapper.java rename to core/src/main/java/com/joaquimley/core/data/model/DataWrapper.java index 6980bc7..a98ab38 100644 --- a/core/src/main/java/com/joaquimley/core/data/model/BaseDataWrapper.java +++ b/core/src/main/java/com/joaquimley/core/data/model/DataWrapper.java @@ -20,7 +20,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; @JsonIgnoreProperties(ignoreUnknown = true) -public class BaseDataWrapper { +public class DataWrapper { @JsonProperty("code") public int mCode; @@ -34,8 +34,10 @@ public class BaseDataWrapper { public String mAttributionHTML; @JsonProperty("etag") public String mETag; + @JsonProperty("data") + public DataContainer mData; - public BaseDataWrapper() { + public DataWrapper() { } public int getCode() { @@ -86,4 +88,11 @@ public void setETag(String eTag) { mETag = eTag; } + public DataContainer getData() { + return mData; + } + + public void setData(DataContainer data) { + mData = data; + } } diff --git a/core/src/main/java/com/joaquimley/core/data/network/MarvelService.java b/core/src/main/java/com/joaquimley/core/data/network/MarvelService.java index 01edf79..f999f3f 100644 --- a/core/src/main/java/com/joaquimley/core/data/network/MarvelService.java +++ b/core/src/main/java/com/joaquimley/core/data/network/MarvelService.java @@ -18,8 +18,11 @@ import android.support.annotation.Nullable; -import com.joaquimley.core.data.model.CharacterDataWrapper; -import com.joaquimley.core.data.model.ComicDataWrapper; +import com.joaquimley.core.data.model.CharacterMarvel; +import com.joaquimley.core.data.model.Comic; +import com.joaquimley.core.data.model.DataWrapper; + +import java.util.List; import retrofit2.Call; import retrofit2.http.GET; @@ -32,33 +35,33 @@ public interface MarvelService { * Retrieve list of characters */ @GET("characters") - Call getCharacters(@Query("apikey") String publicKey, - @Query("hash") String md5Digest, - @Query("ts") long timestamp, - @Nullable @Query("offset") Integer offset, - @Nullable @Query("limit") Integer limit, - @Nullable @Query("nameStartsWith") String searchQuery); + Call>> getCharacters(@Query("apikey") String publicKey, + @Query("hash") String md5Digest, + @Query("ts") long timestamp, + @Nullable @Query("offset") Integer offset, + @Nullable @Query("limit") Integer limit, + @Nullable @Query("nameStartsWith") String searchQuery); /** * Retrieve character by given Id */ @GET("characters/{characterId}") - Call getCharacter(@Path("characterId") long characterId, - @Query("apikey") String publicKey, - @Query("hash") String md5Digest, - @Query("ts") long timestamp); + Call>> getCharacter(@Path("characterId") long characterId, + @Query("apikey") String publicKey, + @Query("hash") String md5Digest, + @Query("ts") long timestamp); /** * Retrieve list of comics by character Id */ @GET("characters/{characterId}/{comicType}") - Call getCharacterComics(@Path("characterId") long characterId, - @Path("comicType") String comicType, - @Query("offset") Integer offset, - @Query("limit") Integer limit, - @Query("apikey") String publicKey, - @Query("hash") String md5Digest, - @Query("ts") long timestamp); + Call>> getCharacterComics(@Path("characterId") long characterId, + @Path("comicType") String comicType, + @Query("offset") Integer offset, + @Query("limit") Integer limit, + @Query("apikey") String publicKey, + @Query("hash") String md5Digest, + @Query("ts") long timestamp); } diff --git a/core/src/main/java/com/joaquimley/core/data/network/MarvelServiceFactory.java b/core/src/main/java/com/joaquimley/core/data/network/MarvelServiceFactory.java index 73cfc78..67a34f0 100644 --- a/core/src/main/java/com/joaquimley/core/data/network/MarvelServiceFactory.java +++ b/core/src/main/java/com/joaquimley/core/data/network/MarvelServiceFactory.java @@ -30,15 +30,14 @@ */ public class MarvelServiceFactory { - public static MarvelService makeMarvelService() { - return makeMarvelService(makeOkHttpClient(false)); - } + private static final int HTTP_READ_TIMEOUT = 10000; + private static final int HTTP_CONNECT_TIMEOUT = 6000; - public static MarvelService makeMarvelService(boolean withLoggingInterceptor) { - return makeMarvelService(makeOkHttpClient(withLoggingInterceptor)); + public static MarvelService makeMarvelService() { + return makeMarvelService(makeOkHttpClient()); } - public static MarvelService makeMarvelService(OkHttpClient okHttpClient) { + private static MarvelService makeMarvelService(OkHttpClient okHttpClient) { Retrofit retrofit = new Retrofit.Builder() .baseUrl(BuildConfig.PRODUCTION_ENDPOINT) .addConverterFactory(JacksonConverterFactory.create()) @@ -48,20 +47,18 @@ public static MarvelService makeMarvelService(OkHttpClient okHttpClient) { return retrofit.create(MarvelService.class); } - public static OkHttpClient makeOkHttpClient(boolean withLoggingInterceptor) { + private static OkHttpClient makeOkHttpClient() { OkHttpClient.Builder httpClientBuilder = new OkHttpClient().newBuilder(); - httpClientBuilder.connectTimeout(MarvelServiceConfig.HTTP_CONNECT_TIMEOUT, TimeUnit.SECONDS); - httpClientBuilder.readTimeout(MarvelServiceConfig.HTTP_READ_TIMEOUT, TimeUnit.SECONDS); - if(withLoggingInterceptor) { - httpClientBuilder.addInterceptor(makeLoggingInterceptor()); - } + httpClientBuilder.connectTimeout(HTTP_CONNECT_TIMEOUT, TimeUnit.SECONDS); + httpClientBuilder.readTimeout(HTTP_READ_TIMEOUT, TimeUnit.SECONDS); + httpClientBuilder.addInterceptor(makeLoggingInterceptor()); return httpClientBuilder.build(); } - public static HttpLoggingInterceptor makeLoggingInterceptor() { + private static HttpLoggingInterceptor makeLoggingInterceptor() { HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); -// logging.setLevel(BuildConfig.DEBUG ? HttpLoggingInterceptor.Level.BODY : HttpLoggingInterceptor.Level.NONE); - logging.setLevel(HttpLoggingInterceptor.Level.BODY); + logging.setLevel(BuildConfig.DEBUG ? HttpLoggingInterceptor.Level.BODY + : HttpLoggingInterceptor.Level.NONE); return logging; } } \ No newline at end of file diff --git a/core/src/main/java/com/joaquimley/core/data/network/RequestWatcher.java b/core/src/main/java/com/joaquimley/core/data/network/RequestWatcher.java deleted file mode 100644 index 256eaff..0000000 --- a/core/src/main/java/com/joaquimley/core/data/network/RequestWatcher.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) Joaquim Ley 2016. All Rights Reserved. - *

- * 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. - */ - -package com.joaquimley.core.data.network; - -import android.util.Log; - -import java.util.ArrayList; -import java.util.List; - -import retrofit2.Call; - -/** - * Request manager, TODO: Needs refactor -> study a better extendable class for better async control - */ -public class RequestWatcher { - - private static final String TAG = RequestWatcher.class.getSimpleName(); - - private List mRequestsList = new ArrayList<>(); - - public void detach() { - for (int i = 0; i < mRequestsList.size(); i++) { - mRequestsList.get(i).cancel(); - mRequestsList.remove(i); - } - } - - public boolean subscribe(Call request) { - if (request.isExecuted()) { - Log.w(TAG, "Request is already in execution, " + request.toString()); - return false; - } - mRequestsList.add(request); - return true; - } - - public void unsubscribe(Call request) { - request.cancel(); - if (mRequestsList != null) { - mRequestsList.remove(request); - } - } -} diff --git a/core/src/main/java/com/joaquimley/core/ui/base/BasePresenter.java b/core/src/main/java/com/joaquimley/core/ui/base/BasePresenter.java index aef9b1a..5ad7621 100644 --- a/core/src/main/java/com/joaquimley/core/ui/base/BasePresenter.java +++ b/core/src/main/java/com/joaquimley/core/ui/base/BasePresenter.java @@ -12,46 +12,37 @@ * 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. +<<<<<<< HEAD +======= */ package com.joaquimley.core.ui.base; +import android.support.annotation.NonNull; + /** * Base class that implements the Presenter interface and provides a base implementation for * attachView() and detachView(). It also handles keeping a reference to the PresenterView that * can be accessed from the children classes by calling getPresenterView(). */ -public class BasePresenter implements Presenter { - - private T mPresenterView; - @Override - public void attachView(T presenterView) { - mPresenterView = presenterView; - } +public abstract class BasePresenter { - @Override - public void detachView() { - mPresenterView = null; - } + protected V mView; - public boolean isViewAttached() { - return mPresenterView != null; + public final void attachView(@NonNull V view) { + mView = view; } - public T getPresenterView() { - return mPresenterView; + public final void detachView() { + mView = null; } - public void checkViewAttached() { - if (!isViewAttached()) throw new PresenterViewNotAttachedException(); - } - - public static class PresenterViewNotAttachedException extends RuntimeException { - public PresenterViewNotAttachedException() { - super("Please call Presenter.attachView(presenterView) before" + - " requesting data to the Presenter"); - } + /** + * Check if the view is attached. + * This checking is only necessary when returning from an asynchronous call + */ + protected final boolean isViewAttached() { + return mView != null; } } - diff --git a/core/src/main/java/com/joaquimley/core/ui/base/RemoteCallback.java b/core/src/main/java/com/joaquimley/core/ui/base/RemoteCallback.java new file mode 100644 index 0000000..63d6384 --- /dev/null +++ b/core/src/main/java/com/joaquimley/core/ui/base/RemoteCallback.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) Joaquim Ley 2016. All Rights Reserved. + *

+ * 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. + */ + +package com.joaquimley.core.ui.base; + +import java.net.HttpURLConnection; + +import javax.net.ssl.HttpsURLConnection; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public abstract class RemoteCallback implements Callback { + + @Override + public final void onResponse(Call call, Response response) { + switch (response.code()) { + case HttpsURLConnection.HTTP_OK: + case HttpsURLConnection.HTTP_CREATED: + case HttpsURLConnection.HTTP_ACCEPTED: + case HttpsURLConnection.HTTP_NOT_AUTHORITATIVE: + if (response.body() != null) { + onSuccess(response.body()); + } + break; + + case HttpURLConnection.HTTP_UNAUTHORIZED: + onUnauthorized(); + break; + + default: + onFailed(new Throwable("Default " + response.code() + " " + response.message())); + } + } + + @Override + public final void onFailure(Call call, Throwable t) { + onFailed(t); + } + + public abstract void onSuccess(T response); + + public abstract void onUnauthorized(); + + public abstract void onFailed(Throwable throwable); +} diff --git a/core/src/main/java/com/joaquimley/core/ui/list/ListPresenterView.java b/core/src/main/java/com/joaquimley/core/ui/base/RemoteView.java similarity index 63% rename from core/src/main/java/com/joaquimley/core/ui/list/ListPresenterView.java rename to core/src/main/java/com/joaquimley/core/ui/base/RemoteView.java index 586a429..60e2990 100644 --- a/core/src/main/java/com/joaquimley/core/ui/list/ListPresenterView.java +++ b/core/src/main/java/com/joaquimley/core/ui/base/RemoteView.java @@ -14,16 +14,19 @@ * limitations under the License. */ -package com.joaquimley.core.ui.list; +package com.joaquimley.core.ui.base; -import com.joaquimley.core.data.model.CharacterMarvel; -import com.joaquimley.core.ui.base.RemotePresenterView; +public interface RemoteView { -import java.util.List; + void showProgress(); -public interface ListPresenterView extends RemotePresenterView { + void hideProgress(); - void showCharacters(List characterList); + void showUnauthorizedError(); - void showSearchedCharacters(List characterList); + void showEmpty(); + + void showError(String errorMessage); + + void showMessageLayout(boolean show); } diff --git a/core/src/main/java/com/joaquimley/core/ui/character/CharacterContract.java b/core/src/main/java/com/joaquimley/core/ui/character/CharacterContract.java new file mode 100644 index 0000000..f24602b --- /dev/null +++ b/core/src/main/java/com/joaquimley/core/ui/character/CharacterContract.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) Joaquim Ley 2016. All Rights Reserved. + *

+ * 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. + */ + +package com.joaquimley.core.ui.character; + +import com.joaquimley.core.ui.base.RemoteView; +import com.joaquimley.core.data.model.CharacterMarvel; +import com.joaquimley.core.data.model.Comic; + +import java.util.List; + +public interface CharacterContract extends RemoteView { + + interface ViewActions { + + void onCharacterRequested(Long characterId); + + void onCharacterComicsRequested(Long characterId, int limit); + + void onCharacterSeriesRequested(Long characterId, int limit); + + void onCharacterStoriesRequested(Long characterId, int limit); + + void onCharacterEventsRequested(Long characterId, int limit); + } + + interface CharacterView extends RemoteView { + + void showCharacter(CharacterMarvel character); + + void showComicList(List comicList); + + void showSeriesList(List seriesList); + + void showStoriesList(List storiesList); + + void showEventsList(List eventsList); + } +} diff --git a/core/src/main/java/com/joaquimley/core/ui/character/CharacterPresenter.java b/core/src/main/java/com/joaquimley/core/ui/character/CharacterPresenter.java index 0490033..422f05d 100644 --- a/core/src/main/java/com/joaquimley/core/ui/character/CharacterPresenter.java +++ b/core/src/main/java/com/joaquimley/core/ui/character/CharacterPresenter.java @@ -16,252 +16,200 @@ package com.joaquimley.core.ui.character; -import com.joaquimley.core.AvengingApplication; -import com.joaquimley.core.data.MarvelDataManager; -import com.joaquimley.core.data.model.CharacterDataWrapper; +import com.joaquimley.core.data.DataManager; +import com.joaquimley.core.data.model.CharacterMarvel; import com.joaquimley.core.data.model.Comic; -import com.joaquimley.core.data.model.ComicDataWrapper; +import com.joaquimley.core.data.model.DataWrapper; import com.joaquimley.core.ui.base.BasePresenter; +import com.joaquimley.core.ui.base.RemoteCallback; import java.util.ArrayList; import java.util.List; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class CharacterPresenter extends BasePresenter { +public class CharacterPresenter extends BasePresenter implements + CharacterContract.ViewActions { private static final int SINGLE_ITEM_INDEX = 0; - private final MarvelDataManager mDataManager; - + private CharacterMarvel mCharacter; private List mComicList; private List mSeriesList; private List mStoriesList; private List mEventsList; public CharacterPresenter() { - mDataManager = new MarvelDataManager(AvengingApplication.getInstance().getMarvelService()); - mComicList = new ArrayList<>(); + mComicList = new ArrayList<>(); mSeriesList = new ArrayList<>(); mStoriesList = new ArrayList<>(); mEventsList = new ArrayList<>(); } @Override - public void detachView() { - mComicList = null; - mSeriesList = null; - mStoriesList = null; - mEventsList = null; - super.detachView(); + public void onCharacterRequested(Long characterId) { + getCharacter(characterId); } - public void getCharacter(long id) { - checkViewAttached(); - final Call request = mDataManager.getCharacter(id); - getPresenterView().showMessageLayout(false); - getPresenterView().showProgress(); - request.enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - switch (response.code()) { - case 200: - getPresenterView().hideProgress(); - if (response.body().getData().getResults().isEmpty()) { - getPresenterView().showEmpty(); - return; - } - getPresenterView().showCharacter(response.body().getData().getResults().get(SINGLE_ITEM_INDEX)); - break; - - default: - getPresenterView().hideProgress(); - getPresenterView().showError("Server error: " + response.code() + " " + response.message()); - } - } + @Override + public void onCharacterComicsRequested(Long characterId, int limit) { + getComicList(characterId, null, limit); + } - @Override - public void onFailure(Call call, Throwable t) { - getPresenterView().hideProgress(); - getPresenterView().showError("Failed: " + t.getMessage()); - } - }); + @Override + public void onCharacterSeriesRequested(Long characterId, int limit) { + getSeriesList(characterId, null, limit); } - public void getComicList(long id) { - getComicList(id, null, null); + @Override + public void onCharacterStoriesRequested(Long characterId, int limit) { + getStoriesList(characterId, null, limit); } - public void getComicList(long id, int limit) { - getComicList(id, null, limit); + @Override + public void onCharacterEventsRequested(Long characterId, int limit) { + getEventsList(characterId, null, limit); } - public void getComicList(long id, Integer offset, Integer limit) { - checkViewAttached(); - if (mComicList != null) { - getPresenterView().showComicList(mComicList); + private void getCharacter(long id) { + if (!isViewAttached()) return; + mView.showMessageLayout(false); + if (mCharacter != null && mCharacter.getId() == id) { + mView.showCharacter(mCharacter); return; } - final Call request = mDataManager.getComics(id, offset, limit); - - request.enqueue(new Callback() { + mView.showProgress(); + DataManager.getInstance().getCharacter(id, new RemoteCallback>>() { @Override - public void onResponse(Call call, Response response) { - switch (response.code()) { - case 200: - mComicList = response.body().getData().getResults(); - if (response.body().getData().getResults().isEmpty()) { - return; - } - getPresenterView().showComicList(mComicList); - break; - - default: - getPresenterView().showError("Server error: " + response.code() + " " - + response.message()); + public void onSuccess(DataWrapper> response) { + mView.hideProgress(); + if (response.getData().getResults().isEmpty()) { + mView.showError("Character does not exist"); + return; } + mCharacter = response.getData().getResults().get(SINGLE_ITEM_INDEX); + mView.showCharacter(mCharacter); } @Override - public void onFailure(Call call, Throwable t) { - if (getPresenterView() != null) { - getPresenterView().showError(t.toString()); - } + public void onUnauthorized() { + mView.showUnauthorizedError(); } - }); - } - - public void getSeriesList(long id) { - getSeriesList(id, null, null); - } - public void getSeriesList(long id, int limit) { - getSeriesList(id, null, limit); + @Override + public void onFailed(Throwable throwable) { + mView.showError(throwable.getMessage()); + } + }); } - public void getSeriesList(long id, Integer offset, Integer limit) { - checkViewAttached(); - if (mSeriesList != null) { - getPresenterView().showComicList(mSeriesList); - return; - } + private void getComicList(long id, Integer offset, Integer limit) { + if (!isViewAttached()) return; + mView.showMessageLayout(false); + mView.showProgress(); - final Call request = mDataManager.getSeries(id, offset, limit); - - request.enqueue(new Callback() { + DataManager.getInstance().getComics(id, offset, limit, new RemoteCallback>>() { @Override - public void onResponse(Call call, Response response) { - switch (response.code()) { - case 200: - mSeriesList = response.body().getData().getResults(); - if (response.body().getData().getResults().isEmpty()) { - return; - } - getPresenterView().showSeriesList(mSeriesList); - break; - - default: - getPresenterView().showError("Server error: " + response.code() + " " - + response.message()); + public void onSuccess(DataWrapper> response) { + mView.hideProgress(); + if (response.getData().getResults().isEmpty()) { + mView.showError("Character has no comics"); + return; } + mView.showComicList(response.getData().getResults()); } @Override - public void onFailure(Call call, Throwable t) { - if (getPresenterView() != null) { - getPresenterView().showError("Failed: " + t.getMessage()); - } + public void onUnauthorized() { + mView.showUnauthorizedError(); } - }); - } - - public void getStoriesList(long id) { - getStoriesList(id, null, null); - } - public void getStoriesList(long id, int limit) { - getStoriesList(id, null, limit); + @Override + public void onFailed(Throwable throwable) { + mView.showError(throwable.getMessage()); + } + }); } - public void getStoriesList(long id, Integer offset, Integer limit) { - checkViewAttached(); - if (mStoriesList != null) { - getPresenterView().showComicList(mStoriesList); - return; - } + private void getSeriesList(long id, Integer offset, Integer limit) { + if (!isViewAttached()) return; + mView.showMessageLayout(false); + mView.showProgress(); - final Call request = mDataManager.getStories(id, offset, limit); - - request.enqueue(new Callback() { + DataManager.getInstance().getSeries(id, offset, limit, new RemoteCallback>>() { @Override - public void onResponse(Call call, Response response) { - switch (response.code()) { - case 200: - mStoriesList = response.body().getData().getResults(); - if (response.body().getData().getResults().isEmpty()) { - return; - } - getPresenterView().showStoriesList(mStoriesList); - break; - - default: - getPresenterView().showError("Server error: " + response.code() + " " - + response.message()); + public void onSuccess(DataWrapper> response) { + mView.hideProgress(); + if (response.getData().getResults().isEmpty()) { + mView.showError("Character has no series"); + return; } + mView.showSeriesList(response.getData().getResults()); } @Override - public void onFailure(Call call, Throwable t) { - if (getPresenterView() != null) { - getPresenterView().showError("Failed: " + t.getMessage()); - } + public void onUnauthorized() { + mView.showUnauthorizedError(); + } + + @Override + public void onFailed(Throwable throwable) { + mView.showError(throwable.getMessage()); } }); } - public void getEventsList(long id) { - getEventsList(id, null, null); - } + private void getStoriesList(long id, Integer offset, Integer limit) { + if (!isViewAttached()) return; + mView.showMessageLayout(false); + mView.showProgress(); - public void getEventsList(long id, int limit) { - getEventsList(id, null, limit); - } + DataManager.getInstance().getStories(id, offset, limit, new RemoteCallback>>() { + @Override + public void onSuccess(DataWrapper> response) { + mView.hideProgress(); + if (response.getData().getResults().isEmpty()) { + mView.showError("Character has no stories"); + return; + } + mView.showStoriesList(response.getData().getResults()); + } - public void getEventsList(long id, Integer offset, Integer limit) { - checkViewAttached(); - if (mEventsList != null) { - getPresenterView().showComicList(mEventsList); - return; - } + @Override + public void onUnauthorized() { + mView.showUnauthorizedError(); + } + + @Override + public void onFailed(Throwable throwable) { + mView.showError(throwable.getMessage()); + } + }); + } - final Call request = mDataManager.getEvents(id, offset, limit); + private void getEventsList(long id, Integer offset, Integer limit) { + if (!isViewAttached()) return; + mView.showMessageLayout(false); + mView.showProgress(); - request.enqueue(new Callback() { + DataManager.getInstance().getEvents(id, offset, limit, new RemoteCallback>>() { @Override - public void onResponse(Call call, Response response) { - switch (response.code()) { - case 200: - mEventsList = response.body().getData().getResults(); - if (response.body().getData().getResults().isEmpty()) { - return; - } - getPresenterView().showEventsList(mEventsList); - break; - - default: - getPresenterView().showError("Server error: " + response.code() + " " - + response.message()); + public void onSuccess(DataWrapper> response) { + mView.hideProgress(); + if (response.getData().getResults().isEmpty()) { + mView.showError("Character has no events"); + return; } + mView.showEventsList(response.getData().getResults()); } @Override - public void onFailure(Call call, Throwable t) { - if (getPresenterView() != null) { - getPresenterView().showError("Failed: " + t.getMessage()); - } + public void onUnauthorized() { + mView.showUnauthorizedError(); + } + + @Override + public void onFailed(Throwable throwable) { + mView.showError(throwable.getMessage()); } }); } diff --git a/core/src/main/java/com/joaquimley/core/ui/character/CharacterPresenterView.java b/core/src/main/java/com/joaquimley/core/ui/list/ListContract.java similarity index 58% rename from core/src/main/java/com/joaquimley/core/ui/character/CharacterPresenterView.java rename to core/src/main/java/com/joaquimley/core/ui/list/ListContract.java index a412454..dc72d0a 100644 --- a/core/src/main/java/com/joaquimley/core/ui/character/CharacterPresenterView.java +++ b/core/src/main/java/com/joaquimley/core/ui/list/ListContract.java @@ -14,25 +14,30 @@ * limitations under the License. */ -package com.joaquimley.core.ui.character; +package com.joaquimley.core.ui.list; +import com.joaquimley.core.ui.base.RemoteView; import com.joaquimley.core.data.model.CharacterMarvel; -import com.joaquimley.core.data.model.Comic; -import com.joaquimley.core.ui.base.RemotePresenterView; import java.util.List; -public interface CharacterPresenterView extends RemotePresenterView { +public interface ListContract { - void showCharacter(CharacterMarvel character); + interface ViewActions { + void onInitialListRequested(); - void showComicList(List comicList); + void onListEndReached(Integer offset, Integer limit, String searchQuery); - void showSeriesList(List seriesList); + void onCharacterSearched(String searchQuery); + } - void showStoriesList(List storiesList); + interface ListView extends RemoteView { + + void showCharacters(List characterList); + + void showSearchedCharacters(List characterList); + + } - void showEventsList(List eventsList); - void showError(String message); } diff --git a/core/src/main/java/com/joaquimley/core/ui/list/ListPresenter.java b/core/src/main/java/com/joaquimley/core/ui/list/ListPresenter.java index d78b897..066a156 100644 --- a/core/src/main/java/com/joaquimley/core/ui/list/ListPresenter.java +++ b/core/src/main/java/com/joaquimley/core/ui/list/ListPresenter.java @@ -16,114 +16,72 @@ package com.joaquimley.core.ui.list; -import com.joaquimley.core.AvengingApplication; -import com.joaquimley.core.data.MarvelDataManager; -import com.joaquimley.core.data.model.CharacterDataWrapper; +import android.support.annotation.Nullable; +import android.text.TextUtils; + +import com.joaquimley.core.data.DataManager; import com.joaquimley.core.data.model.CharacterMarvel; +import com.joaquimley.core.data.model.DataWrapper; import com.joaquimley.core.ui.base.BasePresenter; +import com.joaquimley.core.ui.base.RemoteCallback; -import java.util.ArrayList; import java.util.List; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class ListPresenter extends BasePresenter { +public class ListPresenter extends BasePresenter implements ListContract.ViewActions { private static final int ITEM_REQUEST_INITIAL_OFFSET = 0; private static final int ITEM_REQUEST_LIMIT = 6; - private final MarvelDataManager mDataManager; - private List mCharacterList; - - public ListPresenter() { - mDataManager = new MarvelDataManager(AvengingApplication.getInstance().getMarvelService()); - initItems(); - } - - public ListPresenter(MarvelDataManager dataManager) { - mDataManager = dataManager; - initItems(); - } - - private void initItems() { - mCharacterList = new ArrayList<>(); - } +// private List mCharacterList; @Override - public void detachView() { - super.detachView(); - mCharacterList = null; - } - - public void getCharacters() { - getCharacters(ITEM_REQUEST_INITIAL_OFFSET, ITEM_REQUEST_LIMIT, false, null); - } - - public void getCharacters(String query) { - getCharacters(ITEM_REQUEST_INITIAL_OFFSET, ITEM_REQUEST_LIMIT, false, query); + public void onInitialListRequested() { + getCharacters(ITEM_REQUEST_INITIAL_OFFSET, ITEM_REQUEST_LIMIT, null); } - public void getCharacters(Integer offSet, Boolean isLoadMore) { - getCharacters(offSet, ITEM_REQUEST_LIMIT, isLoadMore, null); + @Override + public void onListEndReached(Integer offset, @Nullable Integer limit, String searchQuery) { + getCharacters(offset, limit == null ? ITEM_REQUEST_LIMIT : limit, searchQuery); } - public void getCharacters(Integer offSet, Boolean isLoadMore, String query) { - getCharacters(offSet, ITEM_REQUEST_LIMIT, isLoadMore, query == null || query.isEmpty() ? null : query); + @Override + public void onCharacterSearched(String searchQuery) { + getCharacters(ITEM_REQUEST_INITIAL_OFFSET, ITEM_REQUEST_LIMIT, searchQuery); } - public void getCharacters(Integer offSet, Integer limit, Boolean isLoadMore, final String queryString) { - checkViewAttached(); - getPresenterView().showMessageLayout(false); - - /** - * Initial request or "refresh" action, reset all states - */ - if (offSet == ITEM_REQUEST_INITIAL_OFFSET) { - mCharacterList = new ArrayList<>(); - } - - if (mCharacterList != null && !mCharacterList.isEmpty() && mCharacterList.size() >= offSet + 1) { - getPresenterView().showCharacters(mCharacterList); - return; - } - - final Call request = mDataManager.getCharactersList(offSet, limit, queryString); - - if (!isLoadMore) { - getPresenterView().showProgress(); - } - - request.enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - getPresenterView().hideProgress(); - switch (response.code()) { - case 200: - mCharacterList.addAll(response.body().getData().getResults()); - if (mCharacterList.isEmpty()) { - getPresenterView().showEmpty(); + private void getCharacters(Integer offset, Integer limit, final String searchQuery) { + if (!isViewAttached()) return; + mView.showMessageLayout(false); + mView.showProgress(); + DataManager.getInstance().getCharactersList(offset, limit, searchQuery, + new RemoteCallback>>() { + @Override + public void onSuccess(DataWrapper> response) { + mView.hideProgress(); + List responseResults = response.getData().getResults(); + if (responseResults.isEmpty()) { + mView.showEmpty(); return; } - if(queryString != null && !queryString.isEmpty()) { - getPresenterView().showSearchedCharacters(response.body().getData().getResults()); - return; + if (TextUtils.isEmpty(searchQuery)) { + mView.showCharacters(responseResults); + } else { + mView.showSearchedCharacters(responseResults); } - getPresenterView().showCharacters(response.body().getData().getResults()); - break; - - default: - getPresenterView().showError(response.message()); - } - } - - @Override - public void onFailure(Call call, Throwable t) { - getPresenterView().hideProgress(); - getPresenterView().showError(t.getMessage()); - } - }); + } + + @Override + public void onUnauthorized() { + mView.hideProgress(); + mView.showUnauthorizedError(); + } + + @Override + public void onFailed(Throwable throwable) { + mView.hideProgress(); + mView.showError(throwable.getMessage()); + } + }); } } \ No newline at end of file diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index d9258de..21d1ec1 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -17,7 +17,7 @@ Avenging Marvel Characters - All Data provided by Marvel. © 2016 MARVEL + All Data provided by Marvel. © 2016 MARVEL Acknowledge Try Again diff --git a/mobile/build.gradle b/mobile/build.gradle index 816e08f..d4388b3 100644 --- a/mobile/build.gradle +++ b/mobile/build.gradle @@ -1,27 +1,15 @@ apply plugin: 'com.android.application' android { - publishNonDefault true - if (System.getenv().containsKey("IS_CI_JOB")) { - compileSdkVersion Integer.parseInt(System.getenv("CI_ANDROID_BUILD_SDK_VERSION")) - buildToolsVersion System.getenv("CI_ANDROID_BUILD_TOOLS_VERSION") - } else { - compileSdkVersion Integer.parseInt(project.ANDROID_BUILD_SDK_VERSION) - buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION - } + compileSdkVersion project.compileSdkVersion + buildToolsVersion project.buildToolsVersion defaultConfig { - if (System.getenv().containsKey("IS_CI_JOB")) { - minSdkVersion Integer.parseInt(System.getenv("CI_ANDROID_BUILD_MIN_SDK_VERSION")) - targetSdkVersion Integer.parseInt(System.getenv("CI_ANDROID_BUILD_TARGET_SDK_VERSION")) - } else { - minSdkVersion Integer.parseInt(project.ANDROID_BUILD_MIN_SDK_VERSION) - targetSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION) - } - applicationId "com.joaquimley.avenging" - versionCode 3 - versionName "1.0" + minSdkVersion 11 + targetSdkVersion 24 + versionCode 4 + versionName "1.0.1" } lintOptions { @@ -40,18 +28,22 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + + packagingOptions { + exclude 'META-INF/LICENSE.txt' + exclude 'META-INF/NOTICE.txt' + exclude 'META-INF/LICENSE' + exclude 'META-INF/NOTICE' + } } dependencies { - final SUPPORT_LIBRARY_VERSION = '24.2.0' - final PICASSO_VERSION = '2.5.2' -// final ESPRESSO_VERSION = '2.2.1' - - compile project(':core') - compile "com.android.support:design:$SUPPORT_LIBRARY_VERSION" - compile "com.squareup.picasso:picasso:$PICASSO_VERSION" + debugCompile project(path: ':core', configuration: 'debug') + releaseCompile project(path: ':core', configuration: 'release') + compile "com.android.support:design:$supportLibraryVersion" + compile "com.squareup.picasso:picasso:$picassoVersion" // Functional tests dependencies -// androidTestCompile ("com.android.support.test.espresso:espresso-core:$ESPRESSO_VERSION") { +// androidTestCompile ("com.android.support.test.espresso:espresso-core:$espressoVersion") { // transitive = false; // } } diff --git a/mobile/src/main/java/com/joaquimley/avenging/ui/character/CharacterFragment.java b/mobile/src/main/java/com/joaquimley/avenging/ui/character/CharacterFragment.java index 6690dd6..1bbeea2 100644 --- a/mobile/src/main/java/com/joaquimley/avenging/ui/character/CharacterFragment.java +++ b/mobile/src/main/java/com/joaquimley/avenging/ui/character/CharacterFragment.java @@ -39,25 +39,22 @@ import com.joaquimley.avenging.util.widgets.UrlFrameWrapper; import com.joaquimley.core.data.model.CharacterMarvel; import com.joaquimley.core.data.model.Comic; +import com.joaquimley.core.ui.character.CharacterContract; import com.joaquimley.core.ui.character.CharacterPresenter; -import com.joaquimley.core.ui.character.CharacterPresenterView; import com.squareup.picasso.Picasso; import java.util.List; -public class CharacterFragment extends Fragment implements CharacterPresenterView, +public class CharacterFragment extends Fragment implements CharacterContract.CharacterView, ComicAdapter.InteractionListener { private static final String ARG_CHARACTER = "argCharacter"; - private static final String ARG_CHARACTER_DOWNLOADED = "argCharacterDownloaded"; - private AppCompatActivity mActivity; private CharacterPresenter mCharacterPresenter; - private CharacterMarvel mCharacterMarvel; - private boolean mCharacterDownloaded; + private AppCompatActivity mActivity; private LinearLayout mContentFrame; private ProgressBar mContentLoadingProgress; @@ -75,7 +72,6 @@ public class CharacterFragment extends Fragment implements CharacterPresenterVie public static CharacterFragment newInstance(CharacterMarvel characterMarvel) { Bundle args = new Bundle(); args.putParcelable(ARG_CHARACTER, characterMarvel); - args.putBoolean(ARG_CHARACTER_DOWNLOADED, false); CharacterFragment fragment = new CharacterFragment(); fragment.setArguments(args); return fragment; @@ -95,17 +91,14 @@ public void onCreate(Bundle savedInstanceState) { mCharacterPresenter = new CharacterPresenter(); if (savedInstanceState != null) { mCharacterMarvel = savedInstanceState.getParcelable(ARG_CHARACTER); - mCharacterDownloaded = savedInstanceState.getBoolean(ARG_CHARACTER_DOWNLOADED); } else if (getArguments() != null) { mCharacterMarvel = getArguments().getParcelable(ARG_CHARACTER); - mCharacterDownloaded = getArguments().getBoolean(ARG_CHARACTER_DOWNLOADED); } } @Override public void onSaveInstanceState(Bundle outState) { outState.putParcelable(ARG_CHARACTER, mCharacterMarvel); - outState.putBoolean(ARG_CHARACTER_DOWNLOADED, mCharacterDownloaded); super.onSaveInstanceState(outState); } @@ -116,12 +109,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, mCharacterPresenter.attachView(this); initViews(view); - if (mCharacterDownloaded) { - showCharacter(mCharacterMarvel); - } else { - mCharacterPresenter.getCharacter(mCharacterMarvel.getId()); - } - + mCharacterPresenter.onCharacterRequested(mCharacterMarvel.getId()); return view; } @@ -159,11 +147,7 @@ private void initViews(View view) { mMessageButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - if (mCharacterDownloaded) { - showCharacter(mCharacterMarvel); - } else { - mCharacterPresenter.getCharacter(mCharacterMarvel.getId()); - } + mCharacterPresenter.onCharacterRequested(mCharacterMarvel.getId()); } }); } @@ -183,36 +167,28 @@ public void showCharacter(CharacterMarvel character) { if (!characterComics.isEmpty()) { mComicWrapper = new ComicFrameWrapper(mActivity, getString(R.string.comics), characterComics, this); mContentFrame.addView(mComicWrapper); - if (!mCharacterDownloaded) { - mCharacterPresenter.getComicList(character.getId(), characterComics.size()); - } + mCharacterPresenter.onCharacterComicsRequested(character.getId(), characterComics.size()); } List characterSeries = character.getSeries().getItems(); if (!characterSeries.isEmpty()) { mSeriesWrapper = new ComicFrameWrapper(mActivity, getString(R.string.series), characterSeries, this); mContentFrame.addView(mSeriesWrapper); - if (!mCharacterDownloaded) { - mCharacterPresenter.getSeriesList(character.getId(), characterSeries.size()); - } + mCharacterPresenter.onCharacterSeriesRequested(character.getId(), characterSeries.size()); } List characterStories = character.getStories().getItems(); if (!characterStories.isEmpty()) { mStoriesWrapper = new ComicFrameWrapper(mActivity, getString(R.string.stories), characterStories, this); mContentFrame.addView(mStoriesWrapper); - if (!mCharacterDownloaded) { - mCharacterPresenter.getStoriesList(character.getId(), characterStories.size()); - } + mCharacterPresenter.onCharacterStoriesRequested(character.getId(), characterStories.size()); } List characterEvents = character.getEvents().getItems(); if (!characterEvents.isEmpty()) { mEventsWrapper = new ComicFrameWrapper(mActivity, getString(R.string.events), characterEvents, this); mContentFrame.addView(mEventsWrapper); - if (!mCharacterDownloaded) { - mCharacterPresenter.getEventsList(character.getId(), characterEvents.size()); - } + mCharacterPresenter.onCharacterEventsRequested(character.getId(), characterEvents.size()); } if (!character.getUrls().isEmpty()) { @@ -222,7 +198,6 @@ public void showCharacter(CharacterMarvel character) { TextView copyRightTextView = new TextView(mActivity); copyRightTextView.setText(getString(R.string.marvel_copyright_notice)); mContentFrame.addView(copyRightTextView); - mCharacterDownloaded = true; } @Override @@ -239,7 +214,9 @@ public void onComicClick(List comicList, ImageView sharedImageView, int c @Override public void showProgress() { - mContentLoadingProgress.setVisibility(View.VISIBLE); + if (mContentLoadingProgress.getVisibility() != View.VISIBLE) { + mContentLoadingProgress.setVisibility(View.VISIBLE); + } mContentFrame.setVisibility(View.GONE); } @@ -249,6 +226,14 @@ public void hideProgress() { mContentFrame.setVisibility(View.VISIBLE); } + @Override + public void showUnauthorizedError() { + mMessageImage.setImageResource(R.drawable.ic_error_list); + mMessageText.setText(getString(R.string.error_generic_server_error, "Unauthorized")); + mMessageButton.setText(getString(R.string.action_try_again)); + showMessageLayout(true); + } + @Override public void showError(String errorMessage) { mMessageImage.setImageResource(R.drawable.ic_error_list); @@ -294,7 +279,6 @@ public void showEventsList(List eventsList) { @Override public void onDestroy() { mCharacterPresenter.detachView(); - mCharacterPresenter = null; super.onDestroy(); } } diff --git a/mobile/src/main/java/com/joaquimley/avenging/ui/comic/ComicFragment.java b/mobile/src/main/java/com/joaquimley/avenging/ui/comic/ComicFragment.java index e56b23b..32c090f 100644 --- a/mobile/src/main/java/com/joaquimley/avenging/ui/comic/ComicFragment.java +++ b/mobile/src/main/java/com/joaquimley/avenging/ui/comic/ComicFragment.java @@ -41,19 +41,20 @@ */ public class ComicFragment extends Fragment implements ViewPager.OnPageChangeListener { - public static final String TAG = ComicFragment.class.getSimpleName(); + public static final String TAG = "ComicFragment"; private static final String ARG_COMIC_LIST = "argComicList"; private static final String ARG_CLICKED_POSITION = "argClickedPosition"; private static final String ARG_TRANSACTION_NAME = "argTransactionName"; private static final int VIEW_PAGER_OFF_SCREEN_LIMIT = 2; - private AppCompatActivity mActivity; - private TextView mPageCounter; - private List mComicList; - private ComicViewPagerAdapter mViewPagerAdapter; private int mClickedPosition; private String mTransactionName; + private List mComicList; + private ComicViewPagerAdapter mViewPagerAdapter; + + private AppCompatActivity mActivity; + private TextView mPageCounter; public static ComicFragment newInstance(List comicList, int clickedPosition) { return newInstance(comicList, null, clickedPosition); diff --git a/mobile/src/main/java/com/joaquimley/avenging/ui/list/ListFragment.java b/mobile/src/main/java/com/joaquimley/avenging/ui/list/ListFragment.java index f3c5e4f..bbdc553 100644 --- a/mobile/src/main/java/com/joaquimley/avenging/ui/list/ListFragment.java +++ b/mobile/src/main/java/com/joaquimley/avenging/ui/list/ListFragment.java @@ -48,13 +48,13 @@ import com.joaquimley.avenging.ui.character.CharacterActivity; import com.joaquimley.avenging.util.DisplayMetricsUtil; import com.joaquimley.core.data.model.CharacterMarvel; +import com.joaquimley.core.ui.list.ListContract; import com.joaquimley.core.ui.list.ListPresenter; -import com.joaquimley.core.ui.list.ListPresenterView; import java.util.List; -public class ListFragment extends Fragment implements ListPresenterView, ListAdapter.InteractionListener, - SearchView.OnQueryTextListener, SwipeRefreshLayout.OnRefreshListener { +public class ListFragment extends Fragment implements ListContract.ListView, + ListAdapter.InteractionListener, SearchView.OnQueryTextListener, SwipeRefreshLayout.OnRefreshListener { private static final int TAB_LAYOUT_SPAN_SIZE = 2; private static final int TAB_LAYOUT_ITEM_SPAN_SIZE = 1; @@ -110,7 +110,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, mListPresenter.attachView(this); mListCharacterAdapter.setListInteractionListener(this); if (mListCharacterAdapter.isEmpty()) { - mListPresenter.getCharacters(); + mListPresenter.onInitialListRequested(); } return view; } @@ -183,7 +183,7 @@ private EndlessRecyclerViewOnScrollListener setupScrollListener(boolean isTablet @Override public void onLoadMore(int page, int totalItemsCount) { if (mListCharacterAdapter.addLoadingView()) { - mListPresenter.getCharacters(totalItemsCount, true, mSearchQuery); + mListPresenter.onListEndReached(totalItemsCount, null, mSearchQuery); } } }; @@ -192,14 +192,19 @@ public void onLoadMore(int page, int totalItemsCount) { @Override public void onRefresh() { mListCharacterAdapter.removeAll(); - mListPresenter.getCharacters(); + mListPresenter.onInitialListRequested(); } @Override public void showCharacters(List characterList) { - mListCharacterAdapter.setViewType(ListAdapter.VIEW_TYPE_GALLERY); - mSwipeRefreshLayout.setEnabled(true); + if (mListCharacterAdapter.getViewType() != ListAdapter.VIEW_TYPE_GALLERY) { + mListCharacterAdapter.removeAll(); + mListCharacterAdapter.setViewType(ListAdapter.VIEW_TYPE_GALLERY); + } + if(!mSwipeRefreshLayout.isActivated()) { + mSwipeRefreshLayout.setEnabled(true); + } mListCharacterAdapter.addItems(characterList); } @@ -210,7 +215,9 @@ public void showSearchedCharacters(List searchResults) { mListCharacterAdapter.setViewType(ListAdapter.VIEW_TYPE_LIST); } - mSwipeRefreshLayout.setEnabled(false); + if(mSwipeRefreshLayout.isActivated()) { + mSwipeRefreshLayout.setEnabled(false); + } mListCharacterAdapter.addItems(searchResults); } @@ -228,6 +235,14 @@ public void hideProgress() { mListCharacterAdapter.removeLoadingView(); } + @Override + public void showUnauthorizedError() { + mMessageImage.setImageResource(R.drawable.ic_error_list); + mMessageText.setText(getString(R.string.error_generic_server_error, "Unauthorized")); + mMessageButton.setText(getString(R.string.action_try_again)); + showMessageLayout(true); + } + @Override public void showError(String errorMessage) { mMessageImage.setImageResource(R.drawable.ic_error_list); @@ -292,7 +307,7 @@ public boolean onQueryTextSubmit(String query) { public boolean onQueryTextChange(String queryText) { mSearchQuery = queryText; if (!TextUtils.isEmpty(mSearchQuery)) { - mListPresenter.getCharacters(mSearchQuery); + mListPresenter.onCharacterSearched(mSearchQuery); return true; } return false; @@ -301,7 +316,6 @@ public boolean onQueryTextChange(String queryText) { @Override public void onDestroy() { mListPresenter.detachView(); - mListPresenter = null; super.onDestroy(); } } \ No newline at end of file diff --git a/wear/build.gradle b/wear/build.gradle index c1380f4..5dd3708 100644 --- a/wear/build.gradle +++ b/wear/build.gradle @@ -1,16 +1,15 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 24 - buildToolsVersion "24.0.0" - publishNonDefault true + compileSdkVersion project.compileSdkVersion + buildToolsVersion project.buildToolsVersion defaultConfig { applicationId "com.joaquimley.avenging" minSdkVersion 19 targetSdkVersion 24 - versionCode 2 - versionName "1.0" + versionCode 3 + versionName "1.0.1" } buildTypes { @@ -22,12 +21,13 @@ android { } dependencies { - final PICASSO_VERSION = '2.5.2' - final PLAY_SERVICES_WEARABLE_VERSION = '9.4.0' + debugCompile project(path: ':core', configuration: 'debug') + releaseCompile project(path: ':core', configuration: 'release') + + final PLAY_SERVICES_WEARABLE_VERSION = '9.6.0' final SUPPORT_WEARABLE_VERSION = '2.0.0-alpha1' - compile project(':core') - compile "com.squareup.picasso:picasso:$PICASSO_VERSION" + compile "com.squareup.picasso:picasso:$picassoVersion" compile "com.google.android.support:wearable:$SUPPORT_WEARABLE_VERSION" compile "com.google.android.gms:play-services-wearable:$PLAY_SERVICES_WEARABLE_VERSION" } diff --git a/wear/src/main/java/com/joaquimley/avenging/ui/base/BaseActivity.java b/wear/src/main/java/com/joaquimley/avenging/ui/base/BaseActivity.java deleted file mode 100644 index eea367c..0000000 --- a/wear/src/main/java/com/joaquimley/avenging/ui/base/BaseActivity.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) Joaquim Ley 2016. All Rights Reserved. - *

- * 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. - */ - -package com.joaquimley.avenging.ui.base; - -import android.app.Activity; -import android.os.Bundle; -import android.view.MenuItem; - -public class BaseActivity extends Activity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - finish(); - return true; - default: - return super.onOptionsItemSelected(item); - } - } -} diff --git a/wear/src/main/java/com/joaquimley/avenging/ui/character/CharacterActivity.java b/wear/src/main/java/com/joaquimley/avenging/ui/character/CharacterActivity.java index 1c40bc9..a5fab31 100644 --- a/wear/src/main/java/com/joaquimley/avenging/ui/character/CharacterActivity.java +++ b/wear/src/main/java/com/joaquimley/avenging/ui/character/CharacterActivity.java @@ -16,6 +16,7 @@ package com.joaquimley.avenging.ui.character; +import android.app.Activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; @@ -26,16 +27,15 @@ import android.widget.TextView; import com.joaquimley.avenging.R; -import com.joaquimley.avenging.ui.base.BaseActivity; import com.joaquimley.core.data.model.CharacterMarvel; import com.joaquimley.core.data.model.Comic; +import com.joaquimley.core.ui.character.CharacterContract; import com.joaquimley.core.ui.character.CharacterPresenter; -import com.joaquimley.core.ui.character.CharacterPresenterView; import java.util.List; -public class CharacterActivity extends BaseActivity implements CharacterPresenterView { +public class CharacterActivity extends Activity implements CharacterContract.CharacterView { private static final String EXTRA_CHARACTER = "extraCharacter"; @@ -65,7 +65,7 @@ protected void onCreate(Bundle savedInstanceState) { mCharacterPresenter.attachView(this); initViews(); - mCharacterPresenter.getCharacter(mCharacter.getId()); + mCharacterPresenter.onCharacterRequested(mCharacter.getId()); } private void initViews() { @@ -77,7 +77,7 @@ private void initViews() { mMessageButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - mCharacterPresenter.getCharacter(mCharacter.getId()); + mCharacterPresenter.onCharacterRequested(mCharacter.getId()); } }); } @@ -85,6 +85,45 @@ public void onClick(View view) { @Override public void showCharacter(CharacterMarvel character) { mCharacter = character; +// if (mDescriptionWrapper == null && !mCharacter.getDescription().isEmpty()) { +// mDescriptionWrapper = new DescriptionFrameWrapper(mActivity, +// mActivity.getResources().getString(R.string.description), +// mCharacter.getDescription()); +// mContentFrame.addView(mDescriptionWrapper); +// } +// +// List characterComics = character.getComics().getItems(); +// if (!characterComics.isEmpty()) { +// mComicWrapper = new ComicFrameWrapper(mActivity, getString(R.string.comics), characterComics, this); +// mContentFrame.addView(mComicWrapper); +// mCharacterPresenter.onCharacterComicsRequested(character.getId(), characterComics.size()); +// } +// +// List characterSeries = character.getSeries().getItems(); +// if (!characterSeries.isEmpty()) { +// mSeriesWrapper = new ComicFrameWrapper(mActivity, getString(R.string.series), characterSeries, this); +// mContentFrame.addView(mSeriesWrapper); +// mCharacterPresenter.onCharacterSeriesRequested(character.getId(), characterSeries.size()); +// } +// +// List characterStories = character.getStories().getItems(); +// if (!characterStories.isEmpty()) { +// mStoriesWrapper = new ComicFrameWrapper(mActivity, getString(R.string.stories), characterStories, this); +// mContentFrame.addView(mStoriesWrapper); +// mCharacterPresenter.onCharacterStoriesRequested(character.getId(), characterStories.size()); +// } +// +// List characterEvents = character.getEvents().getItems(); +// if (!characterEvents.isEmpty()) { +// mEventsWrapper = new ComicFrameWrapper(mActivity, getString(R.string.events), characterEvents, this); +// mContentFrame.addView(mEventsWrapper); +// mCharacterPresenter.onCharacterEventsRequested(character.getId(), characterEvents.size()); +// } +// +// if (!character.getUrls().isEmpty()) { +// mContentFrame.addView(new UrlFrameWrapper(mActivity, +// mActivity.getResources().getString(R.string.related_links), character.getUrls())); +// } // TODO: 04/08/16 see wear module } @@ -118,6 +157,14 @@ public void hideProgress() { mContentLoadingProgress.setVisibility(View.GONE); } + @Override + public void showUnauthorizedError() { + mMessageImage.setImageResource(R.drawable.ic_error_list); + mMessageText.setText(getString(R.string.error_generic_server_error, "Unauthorized")); + mMessageButton.setText(getString(R.string.action_try_again)); + showMessageLayout(true); + } + @Override public void showError(String errorMessage) { mMessageImage.setImageResource(R.drawable.ic_error_list); diff --git a/wear/src/main/java/com/joaquimley/avenging/ui/list/ListActivity.java b/wear/src/main/java/com/joaquimley/avenging/ui/list/ListActivity.java index e338886..270886d 100644 --- a/wear/src/main/java/com/joaquimley/avenging/ui/list/ListActivity.java +++ b/wear/src/main/java/com/joaquimley/avenging/ui/list/ListActivity.java @@ -16,6 +16,7 @@ package com.joaquimley.avenging.ui.list; +import android.app.Activity; import android.os.Bundle; import android.support.wearable.view.GridViewPager; import android.view.View; @@ -25,10 +26,10 @@ import android.widget.TextView; import com.joaquimley.avenging.R; -import com.joaquimley.avenging.ui.base.BaseActivity; +import com.joaquimley.avenging.ui.character.CharacterActivity; import com.joaquimley.core.data.model.CharacterMarvel; +import com.joaquimley.core.ui.list.ListContract; import com.joaquimley.core.ui.list.ListPresenter; -import com.joaquimley.core.ui.list.ListPresenterView; import java.util.List; @@ -36,7 +37,7 @@ * You can view more implementations of a list (as viewable on README.MD), this can be found * on the "deprecated/wear/list" folder. */ -public class ListActivity extends BaseActivity implements ListPresenterView, +public class ListActivity extends Activity implements ListContract.ListView, ListAdapter.InteractionListener, GridViewPager.OnPageChangeListener { private ListPresenter mListPresenter; @@ -56,13 +57,13 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_grid_pager_list); mListPresenter = (ListPresenter) getLastNonConfigurationInstance(); - if(mListPresenter == null) { + if (mListPresenter == null) { mListPresenter = new ListPresenter(); } mListPresenter.attachView(this); initViews(); - mListPresenter.getCharacters(); + mListPresenter.onInitialListRequested(); } @Override @@ -87,14 +88,14 @@ private void initViews() { @Override public void onClick(View view) { mGridPagerAdapter.removeAll(); - mListPresenter.getCharacters(); + mListPresenter.onInitialListRequested(); } }); } @Override public void onListClick(CharacterMarvel character) { - //TODO: startActivity(CharacterActivity.newStartIntent(this, character)); on 1.1 + CharacterActivity.newStartIntent(this, character); } @Override @@ -117,6 +118,14 @@ public void hideProgress() { mContentLoadingProgress.setVisibility(View.GONE); } + @Override + public void showUnauthorizedError() { + mMessageImage.setImageResource(R.drawable.ic_error_list); + mMessageText.setText(getString(R.string.error_generic_server_error, "Unauthorized")); + mMessageButton.setText(getString(R.string.action_try_again)); + showMessageLayout(true); + } + @Override public void showError(String errorMessage) { mMessageImage.setImageResource(R.drawable.ic_error_list); @@ -146,14 +155,12 @@ public void onPageScrolled(int i, int i1, float v, float v1, int i2, int i3) { @Override public void onPageSelected(int i, int i1) { - if(mGridPagerAdapter.getRowCount() == i + 2) { - // onLoadMore() - mListPresenter.getCharacters(mGridPagerAdapter.getRowCount(), true); + if (mGridPagerAdapter.getRowCount() == i + 2) { + mListPresenter.onListEndReached(mGridPagerAdapter.getRowCount(), null, null); } } @Override public void onPageScrollStateChanged(int i) { - } } diff --git a/wear/src/main/java/com/joaquimley/avenging/ui/list/ListAdapter.java b/wear/src/main/java/com/joaquimley/avenging/ui/list/ListAdapter.java index a9f1208..65ffbae 100644 --- a/wear/src/main/java/com/joaquimley/avenging/ui/list/ListAdapter.java +++ b/wear/src/main/java/com/joaquimley/avenging/ui/list/ListAdapter.java @@ -100,7 +100,7 @@ public CharacterViewHolder(View view) { name = (TextView) listItem.findViewById(R.id.tv_name); } - void bind(final CharacterMarvel character) { + private void bind(final CharacterMarvel character) { name.setText(character.getName()); Picasso.with(mContext).load(character.getImageUrl()).into(image); listItem.setOnClickListener(new View.OnClickListener() {