├── .gitignore
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── saulmm
│ │ └── avengers
│ │ ├── TestData.java
│ │ └── tests
│ │ └── CharacterListActivityInstrumentationTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── assets
│ │ └── Abel.ttf
│ ├── java
│ │ └── saulmm
│ │ │ └── avengers
│ │ │ ├── AvengersApplication.java
│ │ │ ├── ButterKnifeUtils.java
│ │ │ ├── ResizeBehavior.java
│ │ │ ├── injector
│ │ │ ├── Activity.java
│ │ │ ├── components
│ │ │ │ ├── ActivityComponent.java
│ │ │ │ ├── AppComponent.java
│ │ │ │ ├── AvengerInformationComponent.java
│ │ │ │ └── AvengersComponent.java
│ │ │ └── modules
│ │ │ │ ├── ActivityModule.java
│ │ │ │ ├── AppModule.java
│ │ │ │ └── AvengerInformationModule.java
│ │ │ ├── mvp
│ │ │ ├── presenters
│ │ │ │ ├── CharacterDetailPresenter.java
│ │ │ │ ├── CharacterListPresenter.java
│ │ │ │ ├── CollectionPresenter.java
│ │ │ │ └── Presenter.java
│ │ │ └── views
│ │ │ │ ├── CharacterDetailView.java
│ │ │ │ ├── CharacterListView.java
│ │ │ │ ├── CollectionView.java
│ │ │ │ └── View.java
│ │ │ ├── utils
│ │ │ ├── DatabindingUtils.java
│ │ │ ├── OnCharacterImageCallback.java
│ │ │ ├── TransitionUtils.java
│ │ │ └── Utils.java
│ │ │ └── views
│ │ │ ├── RecyclerClickListener.java
│ │ │ ├── activities
│ │ │ ├── CharacterDetailActivity.java
│ │ │ ├── CharacterListActivity.java
│ │ │ └── CollectionActivity.java
│ │ │ ├── adapter
│ │ │ └── AvengersListAdapter.java
│ │ │ ├── utils
│ │ │ ├── AnimUtils.java
│ │ │ └── TransitionListenerAdapter.java
│ │ │ └── views
│ │ │ ├── CustomTextView.java
│ │ │ ├── RecyclerInsetsDecoration.java
│ │ │ └── SimpleDivider.java
│ └── res
│ │ ├── drawable-hdpi
│ │ └── ic_action_favorite.png
│ │ ├── drawable-mdpi
│ │ └── ic_action_favorite.png
│ │ ├── drawable-nodpi
│ │ ├── avengers_header.png
│ │ ├── error_placeholder.png
│ │ ├── header_background.png
│ │ └── marvel.png
│ │ ├── drawable-xhdpi
│ │ └── ic_action_favorite.png
│ │ ├── drawable-xxhdpi
│ │ └── ic_action_favorite.png
│ │ ├── drawable-xxxhdpi
│ │ └── ic_action_favorite.png
│ │ ├── drawable
│ │ ├── divider.xml
│ │ ├── gradient_dark.xml
│ │ ├── gradient_dark_up.xml
│ │ └── ic_more.xml
│ │ ├── layout
│ │ ├── activity_avenger_detail.xml
│ │ ├── activity_avengers_list.xml
│ │ ├── activity_character_collection.xml
│ │ ├── item_character.xml
│ │ ├── item_comic.xml
│ │ ├── view_error.xml
│ │ ├── view_filter_dialog.xml
│ │ └── view_loading_characters.xml
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ │ └── values
│ │ ├── attrs.xml
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── integer.xml
│ │ ├── string-arrays.xml
│ │ ├── strings.xml
│ │ ├── strings_character_list.xml
│ │ ├── strings_common.xml
│ │ ├── styles.xml
│ │ └── themes.xml
│ └── test
│ └── java
│ ├── DetailPresenterTest.java
│ └── ListPresenterTest.java
├── art
├── cap.png
├── screen_detail.png
└── screen_main.png
├── build.gradle
├── domain
├── .gitignore
├── build.gradle
└── src
│ ├── main
│ └── java
│ │ └── saulmm
│ │ └── avengers
│ │ ├── CharacterDetailsUsecase.java
│ │ ├── GetCharactersUsecase.java
│ │ ├── GetCollectionUsecase.java
│ │ └── Usecase.java
│ └── test
│ └── java
│ ├── GetCharacterDetailsTest.java
│ └── GetCharactersUsecaseTest.java
├── gradle.properties
├── model
├── .gitignore
├── build.gradle
└── src
│ ├── main
│ └── java
│ │ └── saulmm
│ │ └── avengers
│ │ ├── entities
│ │ ├── CollectionItem.java
│ │ ├── Comic.java
│ │ ├── ComicDate.java
│ │ ├── ComicsCollection.java
│ │ ├── ItemPreview.java
│ │ ├── MarvelCharacter.java
│ │ ├── TextObject.java
│ │ └── Thumbnail.java
│ │ ├── repository
│ │ ├── CharacterRepository.java
│ │ └── wrappers
│ │ │ ├── ComicsWrapper.java
│ │ │ ├── MarvelApiWrapper.java
│ │ │ └── MarvelDataWrapper.java
│ │ └── rest
│ │ ├── Endpoint.java
│ │ ├── HttpErrors.java
│ │ ├── MarvelApi.java
│ │ ├── MarvelAuthorizer.java
│ │ ├── RestDataSource.java
│ │ ├── exceptions
│ │ ├── NetworkErrorException.java
│ │ ├── NetworkTimeOutException.java
│ │ ├── NetworkUknownHostException.java
│ │ ├── ServerErrorException.java
│ │ └── UknownErrorException.java
│ │ └── utils
│ │ ├── MarvelApiUtils.java
│ │ ├── deserializers
│ │ ├── MarvelResultsComicsDeserialiser.java
│ │ └── MarvelResultsDeserializer.java
│ │ └── interceptors
│ │ └── MarvelSigningIterceptor.java
│ └── test
│ └── java
│ └── GsonDeserializersTest.java
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | #Android generated
2 | bin
3 | gen
4 | lint.xml
5 | lint
6 |
7 | #Eclipse
8 | .project
9 | .classpath
10 | .settings
11 | .checkstyle
12 |
13 | #IntelliJ IDEA
14 | .idea
15 | *.iml
16 | *.ipr
17 | *.iws
18 | classes
19 | gen-external-apklibs
20 |
21 | #gradle
22 | .gradle
23 | local.properties
24 | gradlew
25 | gradlew.bat
26 | gradle/
27 | build/
28 |
29 | #vi
30 | *.swp
31 |
32 | #other editors
33 | *.bak
34 |
35 | #Maven
36 | target
37 | release.properties
38 | pom.xml.*
39 |
40 | #Ant
41 | build.xml
42 | ant.properties
43 | local.properties
44 | proguard.cfg
45 | proguard-project.txt
46 |
47 | #Other
48 | .DS_Store
49 | Thumbs.db
50 | tmp
51 | *.tgz
52 | *.lock
53 | *.lck
54 | com_crashlytics_export_strings.xml
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Mozilla Public License
2 | About the License
3 |
4 | Mozilla is the custodian of the Mozilla Public License ("MPL"), an open source/free software license.
5 |
6 | The current version of the license is MPL 2.0 (html | plain text). If you want to use or distribute code licensed under the MPL 2.0 and have questions about it, you may want to read the FAQ.
7 |
8 | MPL 2.0 Revision Process
9 |
10 | The release of MPL 2.0 was the result of a two year process that revised MPL 1.1. A Revision FAQ documents this process, and explains the most significant changes made.
11 |
12 | Historical Documents
13 |
14 | Various historical documents relating to the Mozilla and Netscape Public Licenses are available, including deprecated versions of the license such as MPL 1.1.
15 |
16 | Mozilla Licensing Information
17 |
18 | The Mozilla Project is only one of many users of the MPL, but because many people come to this page looking for information about Mozilla's open source licensing policies and practices, we've provided the information below as a reference.
19 |
20 | Correctly Licensing New Source Code
21 |
22 | Any new code checked into Mozilla's source repositories needs to comply with Mozilla's source code licensing policy. Please use the appropriate header text at the top of each file.
23 |
24 | Licenses For Existing Source Code
25 |
26 | Most Mozilla software projects use the MPL, but some have different terms. Detailed information on the licensing of existing code can be found by inspecting its license headers, or by visiting the license information page in the relevant Mozilla software.
27 |
28 | For information on how other things are licensed, including Mozilla's trademarks and websites, see our general licensing information page.
29 |
30 | Questions?
31 |
32 | If, after reading all the above carefully (particularly the FAQ) you have a further question about the MPL or the licensing terms of Mozilla project code, please send it to licensing@mozilla.org.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | ### When Avengers meet Dagger2, RxJava & Retrofit in a clean way
3 |
4 | This is the source code of a series focused on giving some basic ideas about how to use [Retrofit](http://square.github.io/retrofit/), [Dagger2](http://google.github.io/dagger/) & [RxJava](https://github.com/ReactiveX/RxJava) together with a [Clean Architecture](http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html).
5 |
6 | ##### [Part 1 - Dagger 2](http://saulmm.github.io/when-Thor-and-Hulk-meet-dagger2-rxjava-1)
7 |
8 | In this first part it explains how Dagger 2 can help the decoupling of the layers in a project, removing dependencies so that it is easily scalable and testable.
9 |
10 | ##### [Part 2 - RxJava, RxAndroid, Reactive Extensions & operators](http://saulmm.github.io/when-Iron-Man-becomes-Reactive-Avengers2)
11 |
12 | This part focuses on the understanding of what are the Reactive Extensions, its Java implementation, and use RxJava operators, all it integrated with a clean architecture
13 |
14 | *NOTE*: The source shown in articles refers to [this release].
15 |
16 | #### Screenshots
17 |
18 | 
19 |
20 | ### License
21 |
22 | ```
23 | This Source Code Form is subject to the terms of the Mozilla Public
24 | License, v. 2.0. If a copy of the MPL was not distributed with this
25 | file, You can obtain one at http://mozilla.org/MPL/2.0/.
26 | ```
27 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'me.tatarka.retrolambda'
2 | apply plugin: 'com.android.application'
3 | apply plugin: 'com.neenbedankt.android-apt'
4 | apply plugin: 'com.android.databinding'
5 |
6 | def cfg = rootProject.ext.configuration
7 | def libs = rootProject.ext.libraries;
8 | def test = rootProject.ext.testingLibraries;
9 |
10 | android {
11 | compileSdkVersion cfg.compileVersion
12 | buildToolsVersion cfg.buildToolsVersion
13 |
14 | //noinspection GroovyAssignabilityCheck
15 | defaultConfig {
16 | applicationId cfg.package
17 | minSdkVersion cfg.compileVersion
18 | targetSdkVersion cfg.targetSdk
19 | versionCode cfg.version_code
20 | versionName cfg.version_name
21 |
22 | buildConfigField "String", "MARVEL_PUBLIC_KEY", "\"${marvel_public_key}\""
23 | buildConfigField "String", "MARVEL_PRIVATE_KEY", "\"${marvel_private_key}\""
24 |
25 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
26 | }
27 |
28 | //noinspection GroovyAssignabilityCheck
29 | compileOptions {
30 | sourceCompatibility JavaVersion.VERSION_1_8
31 | targetCompatibility JavaVersion.VERSION_1_8
32 | }
33 |
34 | buildToolsVersion '23.0.3'
35 | }
36 |
37 | //noinspection GroovyAssignabilityCheck
38 | dependencies {
39 | compile project(':domain')
40 |
41 | compile "com.github.bumptech.glide:glide:${libs.glide}"
42 | compile "jp.wasabeef:recyclerview-animators:${libs.recycler_animators}"
43 | compile "com.jakewharton:butterknife:${libs.butterknife}"
44 | compile "de.hdodenhof:circleimageview:${libs.circleimageview}"
45 | compile "io.reactivex:rxandroid:${libs.rxandroid}"
46 |
47 | compile "com.squareup.retrofit:retrofit:${libs.retrofit}"
48 | compile "com.squareup.retrofit:converter-gson:${libs.retrofit}"
49 | compile "com.squareup.retrofit:adapter-rxjava:${libs.retrofit}"
50 |
51 | apt "com.google.dagger:dagger-compiler:${libs.dagger}"
52 |
53 | // Android
54 | compile "com.android.support:design:${libs.supportVersion}"
55 | compile "com.android.support:appcompat-v7:${libs.supportVersion}"
56 | compile "com.android.support:cardview-v7:${libs.supportVersion}"
57 | compile "com.android.support:recyclerview-v7:${libs.supportVersion}"
58 | compile "com.android.support:palette-v7:${libs.supportVersion}"
59 |
60 | // Test
61 | testCompile "org.mockito:mockito-core:$test.mockito"
62 | testCompile "junit:junit:$test.junit"
63 |
64 | androidTestCompile "com.squareup.okhttp:mockwebserver:$libs.okhttp"
65 | androidTestCompile "com.android.support.test:runner:$test.espressoRunner"
66 | androidTestCompile "com.android.support.test:rules:$test.espressoRules"
67 | androidTestCompile("com.android.support.test.espresso:espresso-core:$test.espresso") {
68 | exclude group: 'javax.inject'
69 | }
70 |
71 | androidTestCompile "com.android.support.test.espresso:espresso-intents:$test.espresso"
72 | androidTestCompile ("com.android.support.test.espresso:espresso-contrib:$test.espresso") {
73 | exclude group: 'com.android.support', module: 'appcompat'
74 | exclude group: 'com.android.support', module: 'support-v4'
75 | exclude module: 'recyclerview-v7'
76 | }
77 |
78 | androidTestApt "com.google.dagger:dagger-compiler:${libs.dagger}"
79 |
80 | }
81 |
82 | configurations.all {
83 | resolutionStrategy.force "com.android.support:support-annotations:$libs.supportVersion"
84 | resolutionStrategy.force "com.squareup.okhttp:okhttp:$libs.okhttp"
85 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/saulmm/android-sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/saulmm/avengers/TestData.java:
--------------------------------------------------------------------------------
1 | package saulmm.avengers;
2 |
3 | /**
4 | * Created by saulmm on 04/01/16.
5 | */
6 | public final class TestData {
7 |
8 | public static final String SINGLE_CHARACTER_JSON = "{\"code\":200,\"status\":\"Ok\",\"copyright\":\"© 2016 MARVEL\",\"attributionText\":\"Data provided by Marvel. © 2016 MARVEL\",\"attributionHTML\":\"Data provided by Marvel. © 2016 MARVEL\",\"etag\":\"352196c55578b344079a81ffea21343cc8843d38\",\"data\":{\"offset\":0,\"limit\":20,\"total\":1,\"count\":1,\"results\":[{\"id\":1011170,\"name\":\"Alain\",\"description\":\"\",\"modified\":\"1969-12-31T19:00:00-0500\",\"thumbnail\":{\"path\":\"http://i.annihil.us/u/prod/marvel/i/mg/b/40/image_not_available\",\"extension\":\"jpg\"},\"resourceURI\":\"http://gateway.marvel.com/v1/public/characters/1011170\",\"comics\":{\"available\":0,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1011170/comics\",\"items\":[],\"returned\":0},\"series\":{\"available\":0,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1011170/series\",\"items\":[],\"returned\":0},\"stories\":{\"available\":0,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1011170/stories\",\"items\":[],\"returned\":0},\"events\":{\"available\":0,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1011170/events\",\"items\":[],\"returned\":0},\"urls\":[{\"type\":\"detail\",\"url\":\"http://marvel.com/characters/116/alain?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"},{\"type\":\"wiki\",\"url\":\"http://marvel.com/universe/Alain?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"},{\"type\":\"comiclink\",\"url\":\"http://marvel.com/comics/characters/1011170/alain?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"}]}]}}";
9 |
10 |
11 | public static final String TWENTY_CHARACTERS_JSON = "{\"code\":200,\"status\":\"Ok\",\"copyright\":\"© 2016 MARVEL\",\"attributionText\":\"Data provided by Marvel. © 2016 MARVEL\",\"attributionHTML\":\"Data provided by Marvel. © 2016 MARVEL\",\"etag\":\"46495bc6b3f0e9f7348c3be2715f9cf27d3daeb1\",\"data\":{\"offset\":60,\"limit\":20,\"total\":1485,\"count\":20,\"results\":[{\"id\":1010686,\"name\":\"Arcana\",\"description\":\"\",\"modified\":\"1969-12-31T19:00:00-0500\",\"thumbnail\":{\"path\":\"http://i.annihil.us/u/prod/marvel/i/mg/b/40/image_not_available\",\"extension\":\"jpg\"},\"resourceURI\":\"http://gateway.marvel.com/v1/public/characters/1010686\",\"comics\":{\"available\":2,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1010686/comics\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/comics/3908\",\"name\":\"Squadron Supreme (2006) #1\"},{\"resourceURI\":\"http://gateway.marvel.com/v1/public/comics/5425\",\"name\":\"Squadron Supreme Vol. 1: The Pre-War Years Premiere (Hardcover)\"}],\"returned\":2},\"series\":{\"available\":2,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1010686/series\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/series/944\",\"name\":\"Squadron Supreme (2006)\"},{\"resourceURI\":\"http://gateway.marvel.com/v1/public/series/1791\",\"name\":\"Squadron Supreme Vol. 1: The Pre-War Years Premiere (2006)\"}],\"returned\":2},\"stories\":{\"available\":1,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1010686/stories\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/stories/5249\",\"name\":\"1 of 6 - The Pre-War Years\",\"type\":\"cover\"}],\"returned\":1},\"events\":{\"available\":0,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1010686/events\",\"items\":[],\"returned\":0},\"urls\":[{\"type\":\"detail\",\"url\":\"http://marvel.com/characters/179/arcana?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"},{\"type\":\"comiclink\",\"url\":\"http://marvel.com/comics/characters/1010686/arcana?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"}]},{\"id\":1009159,\"name\":\"Archangel\",\"description\":\"\",\"modified\":\"2013-10-18T12:48:24-0400\",\"thumbnail\":{\"path\":\"http://i.annihil.us/u/prod/marvel/i/mg/8/03/526165ed93180\",\"extension\":\"jpg\"},\"resourceURI\":\"http://gateway.marvel.com/v1/public/characters/1009159\",\"comics\":{\"available\":531,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1009159/comics\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/comics/17701\",\"name\":\"Age of Apocalypse: The Chosen (1995) #1\"}],\"returned\":1},\"series\":{\"available\":137,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1009159/series\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/series/3614\",\"name\":\"Age of Apocalypse: The Chosen (1995)\"}],\"returned\":1},\"stories\":{\"available\":576,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1009159/stories\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/stories/3261\",\"name\":\"2 of 2 - Save the Life of My Child\",\"type\":\"cover\"}],\"returned\":1},\"events\":{\"available\":17,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1009159/events\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/events/116\",\"name\":\"Acts of Vengeance!\"}],\"returned\":1},\"urls\":[{\"type\":\"detail\",\"url\":\"http://marvel.com/characters/1/angel?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"},{\"type\":\"wiki\",\"url\":\"http://marvel.com/universe/Angel_(Warren_Worthington_III)?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"},{\"type\":\"comiclink\",\"url\":\"http://marvel.com/comics/characters/1009159/archangel?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"}]},{\"id\":1009160,\"name\":\"Arclight\",\"description\":\"\",\"modified\":\"1969-12-31T19:00:00-0500\",\"thumbnail\":{\"path\":\"http://i.annihil.us/u/prod/marvel/i/mg/5/f0/4c0042067fd8b\",\"extension\":\"jpg\"},\"resourceURI\":\"http://gateway.marvel.com/v1/public/characters/1009160\",\"comics\":{\"available\":13,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1009160/comics\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/comics/26024\",\"name\":\"New Avengers Annual (2009) #3\"}],\"returned\":1},\"series\":{\"available\":5,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1009160/series\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/series/8140\",\"name\":\"New Avengers Annual (2009)\"}],\"returned\":1},\"stories\":{\"available\":12,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1009160/stories\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/stories/22086\",\"name\":\"Fallen Angel!\",\"type\":\"interiorStory\"}],\"returned\":1},\"events\":{\"available\":3,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1009160/events\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/events/252\",\"name\":\"Inferno\"}],\"returned\":1},\"urls\":[{\"type\":\"detail\",\"url\":\"http://marvel.com/characters/182/arclight?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"},{\"type\":\"wiki\",\"url\":\"http://marvel.com/universe/Arclight?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"},{\"type\":\"comiclink\",\"url\":\"http://marvel.com/comics/characters/1009160/arclight?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"}]},{\"id\":1010784,\"name\":\"Ares\",\"description\":\"\",\"modified\":\"2014-04-29T14:50:59-0400\",\"thumbnail\":{\"path\":\"http://i.annihil.us/u/prod/marvel/i/mg/c/10/535ff3daea603\",\"extension\":\"jpg\"},\"resourceURI\":\"http://gateway.marvel.com/v1/public/characters/1010784\",\"comics\":{\"available\":41,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1010784/comics\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/comics/24348\",\"name\":\"Adam: Legend of the Blue Marvel (Trade Paperback)\"}],\"returned\":1},\"series\":{\"available\":17,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1010784/series\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/series/7524\",\"name\":\"Adam: Legend of the Blue Marvel (2008)\"}],\"returned\":1},\"stories\":{\"available\":43,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1010784/stories\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/stories/5532\",\"name\":\"1 of 5 xLS\",\"type\":\"cover\"}],\"returned\":1},\"events\":{\"available\":4,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1010784/events\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/events/296\",\"name\":\"Chaos War\"}],\"returned\":1},\"urls\":[{\"type\":\"detail\",\"url\":\"http://marvel.com/characters/183/ares?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"},{\"type\":\"wiki\",\"url\":\"http://marvel.com/universe/Ares?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"},{\"type\":\"comiclink\",\"url\":\"http://marvel.com/comics/characters/1010784/ares?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"}]},{\"id\":1011275,\"name\":\"Argent\",\"description\":\"\",\"modified\":\"1969-12-31T19:00:00-0500\",\"thumbnail\":{\"path\":\"http://i.annihil.us/u/prod/marvel/i/mg/b/40/image_not_available\",\"extension\":\"jpg\"},\"resourceURI\":\"http://gateway.marvel.com/v1/public/characters/1011275\",\"comics\":{\"available\":0,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1011275/comics\",\"items\":[],\"returned\":0},\"series\":{\"available\":0,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1011275/series\",\"items\":[],\"returned\":0},\"stories\":{\"available\":0,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1011275/stories\",\"items\":[],\"returned\":0},\"events\":{\"available\":0,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1011275/events\",\"items\":[],\"returned\":0},\"urls\":[{\"type\":\"detail\",\"url\":\"http://marvel.com/characters/184/argent?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"},{\"type\":\"wiki\",\"url\":\"http://marvel.com/universe/Argent?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"},{\"type\":\"comiclink\",\"url\":\"http://marvel.com/comics/characters/1011275/argent?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"}]},{\"id\":1011012,\"name\":\"Armadillo\",\"description\":\"\",\"modified\":\"1969-12-31T19:00:00-0500\",\"thumbnail\":{\"path\":\"http://i.annihil.us/u/prod/marvel/i/mg/2/40/4c0032754da02\",\"extension\":\"jpg\"},\"resourceURI\":\"http://gateway.marvel.com/v1/public/characters/1011012\",\"comics\":{\"available\":0,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1011012/comics\",\"items\":[],\"returned\":0},\"series\":{\"available\":0,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1011012/series\",\"items\":[],\"returned\":0},\"stories\":{\"available\":0,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1011012/stories\",\"items\":[],\"returned\":0},\"events\":{\"available\":0,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1011012/events\",\"items\":[],\"returned\":0},\"urls\":[{\"type\":\"detail\",\"url\":\"http://marvel.com/characters/189/armadillo?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"},{\"type\":\"wiki\",\"url\":\"http://marvel.com/universe/Armadillo?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"},{\"type\":\"comiclink\",\"url\":\"http://marvel.com/comics/characters/1011012/armadillo?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"}]},{\"id\":1011298,\"name\":\"Armor (Hiscanao Ichiki)\",\"description\":\"\",\"modified\":\"1969-12-31T19:00:00-0500\",\"thumbnail\":{\"path\":\"http://i.annihil.us/u/prod/marvel/i/mg/9/20/4c002e6cbf990\",\"extension\":\"jpg\"},\"resourceURI\":\"http://gateway.marvel.com/v1/public/characters/1011298\",\"comics\":{\"available\":4,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1011298/comics\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/comics/21511\",\"name\":\"Astonishing X-Men (2004) #25\"},{\"resourceURI\":\"http://gateway.marvel.com/v1/public/comics/38320\",\"name\":\"Astonishing X-Men (2004) #37\"},{\"resourceURI\":\"http://gateway.marvel.com/v1/public/comics/38318\",\"name\":\"Astonishing X-Men (2004) #38\"},{\"resourceURI\":\"http://gateway.marvel.com/v1/public/comics/41548\",\"name\":\"Wolverine & the X-Men: Alpha & Omega (2011) #3\"}],\"returned\":4},\"series\":{\"available\":2,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1011298/series\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/series/744\",\"name\":\"Astonishing X-Men (2004 - 2013)\"},{\"resourceURI\":\"http://gateway.marvel.com/v1/public/series/15486\",\"name\":\"Wolverine & the X-Men: Alpha & Omega (2011 - 2012)\"}],\"returned\":2},\"stories\":{\"available\":4,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1011298/stories\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/stories/47427\",\"name\":\"1 of 6 Ghost Box\",\"type\":\"cover\"},{\"resourceURI\":\"http://gateway.marvel.com/v1/public/stories/84191\",\"name\":\"Cover #84191\",\"type\":\"cover\"},{\"resourceURI\":\"http://gateway.marvel.com/v1/public/stories/89900\",\"name\":\"Astonishing X-Men (2004) #38\",\"type\":\"interiorStory\"},{\"resourceURI\":\"http://gateway.marvel.com/v1/public/stories/93985\",\"name\":\"Cover #93985\",\"type\":\"cover\"}],\"returned\":4},\"events\":{\"available\":0,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1011298/events\",\"items\":[],\"returned\":0},\"urls\":[{\"type\":\"detail\",\"url\":\"http://marvel.com/characters/191/armor?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"},{\"type\":\"wiki\",\"url\":\"http://marvel.com/universe/Armor_(Hisako_Ichiki)?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"},{\"type\":\"comiclink\",\"url\":\"http://marvel.com/comics/characters/1011298/armor_hisako_ichiki?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"}]},{\"id\":1010827,\"name\":\"Armory\",\"description\":\"\",\"modified\":\"1969-12-31T19:00:00-0500\",\"thumbnail\":{\"path\":\"http://i.annihil.us/u/prod/marvel/i/mg/b/40/image_not_available\",\"extension\":\"jpg\"},\"resourceURI\":\"http://gateway.marvel.com/v1/public/characters/1010827\",\"comics\":{\"available\":2,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1010827/comics\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/comics/17293\",\"name\":\"Avengers: The Initiative Annual (2007) #1\"},{\"resourceURI\":\"http://gateway.marvel.com/v1/public/comics/21085\",\"name\":\"Secret Invasion: The Infiltration (Trade Paperback)\"}],\"returned\":2},\"series\":{\"available\":2,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1010827/series\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/series/3087\",\"name\":\"Avengers: The Initiative Annual (2007)\"},{\"resourceURI\":\"http://gateway.marvel.com/v1/public/series/4619\",\"name\":\"Secret Invasion: The Infiltration (2008)\"}],\"returned\":2},\"stories\":{\"available\":1,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1010827/stories\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/stories/36242\",\"name\":\"1 of 1\",\"type\":\"cover\"}],\"returned\":1},\"events\":{\"available\":1,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1010827/events\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/events/269\",\"name\":\"Secret Invasion\"}],\"returned\":1},\"urls\":[{\"type\":\"detail\",\"url\":\"http://marvel.com/characters/193/armory?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"},{\"type\":\"comiclink\",\"url\":\"http://marvel.com/comics/characters/1010827/armory?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"}]},{\"id\":1009740,\"name\":\"Arnim Zola\",\"description\":\"The frail, dwarfish Arnim Zola was born in 1930s Switzerland where he became the world's leading biochemist and genetic engineer.\",\"modified\":\"2012-03-20T12:33:28-0400\",\"thumbnail\":{\"path\":\"http://i.annihil.us/u/prod/marvel/i/mg/8/b0/4c00393a4cb7c\",\"extension\":\"jpg\"},\"resourceURI\":\"http://gateway.marvel.com/v1/public/characters/1009740\",\"comics\":{\"available\":6,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1009740/comics\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/comics/5402\",\"name\":\"Captain America (2004) #24\"}],\"returned\":1},\"series\":{\"available\":5,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1009740/series\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/series/1996\",\"name\":\"Captain America (1968 - 1996)\"}],\"returned\":1},\"stories\":{\"available\":5,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1009740/stories\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/stories/4276\",\"name\":\"3 of 3 - Civil War\",\"type\":\"interiorStory\"}],\"returned\":1},\"events\":{\"available\":1,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1009740/events\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/events/238\",\"name\":\"Civil War\"}],\"returned\":1},\"urls\":[{\"type\":\"detail\",\"url\":\"http://marvel.com/characters/2790/arnim_zola?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"},{\"type\":\"wiki\",\"url\":\"http://marvel.com/universe/Zola,_Arnim?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"},{\"type\":\"comiclink\",\"url\":\"http://marvel.com/comics/characters/1009740/arnim_zola?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"}]},{\"id\":1010748,\"name\":\"Arsenic\",\"description\":\"\",\"modified\":\"1969-12-31T19:00:00-0500\",\"thumbnail\":{\"path\":\"http://i.annihil.us/u/prod/marvel/i/mg/8/c0/4c00359a2be7b\",\"extension\":\"jpg\"},\"resourceURI\":\"http://gateway.marvel.com/v1/public/characters/1010748\",\"comics\":{\"available\":0,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1010748/comics\",\"items\":[],\"returned\":0},\"series\":{\"available\":0,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1010748/series\",\"items\":[],\"returned\":0},\"stories\":{\"available\":0,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1010748/stories\",\"items\":[],\"returned\":0},\"events\":{\"available\":0,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1010748/events\",\"items\":[],\"returned\":0},\"urls\":[{\"type\":\"detail\",\"url\":\"http://marvel.com/characters/197/arsenic?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"},{\"type\":\"wiki\",\"url\":\"http://marvel.com/universe/Arsenic_(and_Old_Lace)?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"},{\"type\":\"comiclink\",\"url\":\"http://marvel.com/comics/characters/1010748/arsenic?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"}]},{\"id\":1009161,\"name\":\"Artiee\",\"description\":\"\",\"modified\":\"2011-10-27T09:59:16-0400\",\"thumbnail\":{\"path\":\"http://i.annihil.us/u/prod/marvel/i/mg/b/40/image_not_available\",\"extension\":\"jpg\"},\"resourceURI\":\"http://gateway.marvel.com/v1/public/characters/1009161\",\"comics\":{\"available\":1,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1009161/comics\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/comics/35509\",\"name\":\"Amazing Spider-Man (1999) #673\"}],\"returned\":1},\"series\":{\"available\":1,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1009161/series\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/series/454\",\"name\":\"Amazing Spider-Man (1999 - 2013)\"}],\"returned\":1},\"stories\":{\"available\":1,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1009161/stories\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/stories/95451\",\"name\":\"Amazing Spider-Man (1999) #673\",\"type\":\"cover\"}],\"returned\":1},\"events\":{\"available\":0,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1009161/events\",\"items\":[],\"returned\":0},\"urls\":[{\"type\":\"detail\",\"url\":\"http://marvel.com/characters/198/artiee?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"},{\"type\":\"comiclink\",\"url\":\"http://marvel.com/comics/characters/1009161/artiee?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"}]},{\"id\":1010718,\"name\":\"Asgardian\",\"description\":\"\",\"modified\":\"1969-12-31T19:00:00-0500\",\"thumbnail\":{\"path\":\"http://i.annihil.us/u/prod/marvel/i/mg/b/40/image_not_available\",\"extension\":\"jpg\"},\"resourceURI\":\"http://gateway.marvel.com/v1/public/characters/1010718\",\"comics\":{\"available\":0,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1010718/comics\",\"items\":[],\"returned\":0},\"series\":{\"available\":0,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1010718/series\",\"items\":[],\"returned\":0},\"stories\":{\"available\":0,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1010718/stories\",\"items\":[],\"returned\":0},\"events\":{\"available\":0,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1010718/events\",\"items\":[],\"returned\":0},\"urls\":[{\"type\":\"detail\",\"url\":\"http://marvel.com/characters/201/asgardian?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"},{\"type\":\"wiki\",\"url\":\"http://marvel.com/universe/Lorelei_%28Asgardian%29?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"},{\"type\":\"comiclink\",\"url\":\"http://marvel.com/comics/characters/1010718/asgardian?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"}]},{\"id\":1009162,\"name\":\"Askew-Tronics\",\"description\":\"\",\"modified\":\"1969-12-31T19:00:00-0500\",\"thumbnail\":{\"path\":\"http://i.annihil.us/u/prod/marvel/i/mg/b/40/image_not_available\",\"extension\":\"jpg\"},\"resourceURI\":\"http://gateway.marvel.com/v1/public/characters/1009162\",\"comics\":{\"available\":0,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1009162/comics\",\"items\":[],\"returned\":0},\"series\":{\"available\":0,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1009162/series\",\"items\":[],\"returned\":0},\"stories\":{\"available\":0,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1009162/stories\",\"items\":[],\"returned\":0},\"events\":{\"available\":0,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1009162/events\",\"items\":[],\"returned\":0},\"urls\":[{\"type\":\"detail\",\"url\":\"http://marvel.com/characters/204/askew-tronics?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"},{\"type\":\"comiclink\",\"url\":\"http://marvel.com/comics/characters/1009162/askew-tronics?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"}]},{\"id\":1010835,\"name\":\"Asylum\",\"description\":\"\",\"modified\":\"1969-12-31T19:00:00-0500\",\"thumbnail\":{\"path\":\"http://i.annihil.us/u/prod/marvel/i/mg/b/40/image_not_available\",\"extension\":\"jpg\"},\"resourceURI\":\"http://gateway.marvel.com/v1/public/characters/1010835\",\"comics\":{\"available\":0,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1010835/comics\",\"items\":[],\"returned\":0},\"series\":{\"available\":0,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1010835/series\",\"items\":[],\"returned\":0},\"stories\":{\"available\":0,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1010835/stories\",\"items\":[],\"returned\":0},\"events\":{\"available\":0,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1010835/events\",\"items\":[],\"returned\":0},\"urls\":[{\"type\":\"detail\",\"url\":\"http://marvel.com/characters/211/asylum?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"},{\"type\":\"wiki\",\"url\":\"http://marvel.com/universe/Asylum_(Henrique_Gallante)?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"},{\"type\":\"comiclink\",\"url\":\"http://marvel.com/comics/characters/1010835/asylum?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"}]},{\"id\":1010336,\"name\":\"Atlas (Team)\",\"description\":\"\",\"modified\":\"1969-12-31T19:00:00-0500\",\"thumbnail\":{\"path\":\"http://i.annihil.us/u/prod/marvel/i/mg/b/40/image_not_available\",\"extension\":\"jpg\"},\"resourceURI\":\"http://gateway.marvel.com/v1/public/characters/1010336\",\"comics\":{\"available\":12,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1010336/comics\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/comics/17493\",\"name\":\"Avengers (1998) #12\"}],\"returned\":1},\"series\":{\"available\":8,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1010336/series\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/series/354\",\"name\":\"Avengers (1998 - 2004)\"}],\"returned\":1},\"stories\":{\"available\":8,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1010336/stories\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/stories/3878\",\"name\":\"6 of 6 - One Step Forward\",\"type\":\"cover\"}],\"returned\":1},\"events\":{\"available\":0,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1010336/events\",\"items\":[],\"returned\":0},\"urls\":[{\"type\":\"detail\",\"url\":\"http://marvel.com/characters/214/atlas?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"},{\"type\":\"wiki\",\"url\":\"http://marvel.com/universe/Atlas_(Team)?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"},{\"type\":\"comiclink\",\"url\":\"http://marvel.com/comics/characters/1010336/atlas_team?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"}]},{\"id\":1009163,\"name\":\"Aurora\",\"description\":\"\",\"modified\":\"2011-05-10T15:56:51-0400\",\"thumbnail\":{\"path\":\"http://i.annihil.us/u/prod/marvel/i/mg/f/10/4c004203f1072\",\"extension\":\"jpg\"},\"resourceURI\":\"http://gateway.marvel.com/v1/public/characters/1009163\",\"comics\":{\"available\":53,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1009163/comics\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/comics/17701\",\"name\":\"Age of Apocalypse: The Chosen (1995) #1\"}],\"returned\":1},\"series\":{\"available\":14,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1009163/series\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/series/3614\",\"name\":\"Age of Apocalypse: The Chosen (1995)\"}],\"returned\":1},\"stories\":{\"available\":59,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1009163/stories\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/stories/15427\",\"name\":\"Shoot-Out at the Stampede!\",\"type\":\"interiorStory\"}],\"returned\":20},\"events\":{\"available\":4,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1009163/events\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/events/227\",\"name\":\"Age of Apocalypse\"},{\"resourceURI\":\"http://gateway.marvel.com/v1/public/events/296\",\"name\":\"Chaos War\"},{\"resourceURI\":\"http://gateway.marvel.com/v1/public/events/302\",\"name\":\"Fear Itself\"},{\"resourceURI\":\"http://gateway.marvel.com/v1/public/events/29\",\"name\":\"Infinity War\"}],\"returned\":4},\"urls\":[{\"type\":\"detail\",\"url\":\"http://marvel.com/characters/221/aurora?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"},{\"type\":\"wiki\",\"url\":\"http://marvel.com/universe/Aurora?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"},{\"type\":\"comiclink\",\"url\":\"http://marvel.com/comics/characters/1009163/aurora?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"}]},{\"id\":1009164,\"name\":\"Avalanche\",\"description\":\"\",\"modified\":\"1969-12-31T19:00:00-0500\",\"thumbnail\":{\"path\":\"http://i.annihil.us/u/prod/marvel/i/mg/f/10/4c0042010d383\",\"extension\":\"jpg\"},\"resourceURI\":\"http://gateway.marvel.com/v1/public/characters/1009164\",\"comics\":{\"available\":30,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1009164/comics\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/comics/6929\",\"name\":\"Avengers Annual (1967) #15\"}],\"returned\":1},\"series\":{\"available\":9,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1009164/series\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/series/1988\",\"name\":\"Avengers Annual (1967 - 1994)\"}],\"returned\":1},\"stories\":{\"available\":37,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1009164/stories\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/stories/15472\",\"name\":\"Days of Future Past\",\"type\":\"interiorStory\"}],\"returned\":1},\"events\":{\"available\":5,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1009164/events\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/events/246\",\"name\":\"Evolutionary War\"},{\"resourceURI\":\"http://gateway.marvel.com/v1/public/events/248\",\"name\":\"Fall of the Mutants\"},{\"resourceURI\":\"http://gateway.marvel.com/v1/public/events/32\",\"name\":\"Kings of Pain\"},{\"resourceURI\":\"http://gateway.marvel.com/v1/public/events/263\",\"name\":\"Mutant Massacre\"},{\"resourceURI\":\"http://gateway.marvel.com/v1/public/events/270\",\"name\":\"Secret Wars\"}],\"returned\":5},\"urls\":[{\"type\":\"detail\",\"url\":\"http://marvel.com/characters/222/avalanche?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"},{\"type\":\"wiki\",\"url\":\"http://marvel.com/universe/Avalanche?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"},{\"type\":\"comiclink\",\"url\":\"http://marvel.com/comics/characters/1009164/avalanche?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"}]},{\"id\":1009165,\"name\":\"Avengers\",\"description\":\"Earth's Mightiest Heroes joined forces to take on threats that were too big for any one hero to tackle. With a roster that has included Captain America, Iron Man, Ant-Man, Hulk, Thor, Wasp and dozens more over the years, the Avengers have come to be regarded as Earth's No. 1 team.\",\"modified\":\"2014-05-27T20:28:26-0400\",\"thumbnail\":{\"path\":\"http://i.annihil.us/u/prod/marvel/i/mg/9/20/5102c774ebae7\",\"extension\":\"jpg\"},\"resourceURI\":\"http://gateway.marvel.com/v1/public/characters/1009165\",\"comics\":{\"available\":962,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1009165/comics\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/comics/35501\",\"name\":\"Amazing Spider-Man (1999) #663\"}],\"returned\":1},\"series\":{\"available\":191,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1009165/series\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/series/15331\",\"name\":\"Age of Apocalypse (2011 - 2013)\"}],\"returned\":1},\"stories\":{\"available\":1373,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1009165/stories\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/stories/490\",\"name\":\"Interior #490\",\"type\":\"interiorStory\"}],\"returned\":1},\"events\":{\"available\":21,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1009165/events\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/events/116\",\"name\":\"Acts of Vengeance!\"}],\"returned\":1},\"urls\":[{\"type\":\"detail\",\"url\":\"http://marvel.com/comics/characters/1009165/avengers?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"},{\"type\":\"wiki\",\"url\":\"http://marvel.com/universe/Avengers?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"},{\"type\":\"comiclink\",\"url\":\"http://marvel.com/comics/characters/1009165/avengers?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"}]},{\"id\":1015239,\"name\":\"Avengers (Ultimate)\",\"description\":\"\",\"modified\":\"2012-07-10T19:18:28-0400\",\"thumbnail\":{\"path\":\"http://i.annihil.us/u/prod/marvel/i/mg/b/40/image_not_available\",\"extension\":\"jpg\"},\"resourceURI\":\"http://gateway.marvel.com/v1/public/characters/1015239\",\"comics\":{\"available\":23,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1015239/comics\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/comics/15718\",\"name\":\"Ultimate X-Men (2000) #27\"}],\"returned\":1},\"series\":{\"available\":9,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1015239/series\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/series/9867\",\"name\":\"Ultimate Comics Avengers 3 (2010 - 2011)\"}],\"returned\":1},\"stories\":{\"available\":16,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1015239/stories\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/stories/32194\",\"name\":\"Free Preview of AVENGERS 65\",\"type\":\"interiorStory\"}],\"returned\":1},\"events\":{\"available\":0,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1015239/events\",\"items\":[],\"returned\":0},\"urls\":[{\"type\":\"detail\",\"url\":\"http://marvel.com/characters/68/avengers?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"},{\"type\":\"comiclink\",\"url\":\"http://marvel.com/comics/characters/1015239/avengers_ultimate?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"}]},{\"id\":1011766,\"name\":\"Azazel (Mutant)\",\"description\":\"A mutant from biblical times, Azazel is the ruler of the Neyaphem and claims that the Earth and everything on it belongs to him.\",\"modified\":\"2011-06-09T11:04:52-0400\",\"thumbnail\":{\"path\":\"http://i.annihil.us/u/prod/marvel/i/mg/b/40/image_not_available\",\"extension\":\"jpg\"},\"resourceURI\":\"http://gateway.marvel.com/v1/public/characters/1011766\",\"comics\":{\"available\":2,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1011766/comics\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/comics/13969\",\"name\":\"Uncanny X-Men (1963) #428\"},{\"resourceURI\":\"http://gateway.marvel.com/v1/public/comics/41821\",\"name\":\"X-MEN FIRST CLASS: THE HIGH HAND (2011) #1\"}],\"returned\":2},\"series\":{\"available\":2,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1011766/series\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/series/2258\",\"name\":\"Uncanny X-Men (1963 - 2011)\"},{\"resourceURI\":\"http://gateway.marvel.com/v1/public/series/15598\",\"name\":\"X-MEN FIRST CLASS: THE HIGH HAND (2011)\"}],\"returned\":2},\"stories\":{\"available\":2,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1011766/stories\",\"items\":[{\"resourceURI\":\"http://gateway.marvel.com/v1/public/stories/28405\",\"name\":\"How Did I Get Here?\",\"type\":\"interiorStory\"},{\"resourceURI\":\"http://gateway.marvel.com/v1/public/stories/94626\",\"name\":\"X-MEN FIRST CLASS: THE HIGH HAND (2011) #1\",\"type\":\"interiorStory\"}],\"returned\":2},\"events\":{\"available\":0,\"collectionURI\":\"http://gateway.marvel.com/v1/public/characters/1011766/events\",\"items\":[],\"returned\":0},\"urls\":[{\"type\":\"detail\",\"url\":\"http://marvel.com/characters/227/azazel?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"},{\"type\":\"wiki\",\"url\":\"http://marvel.com/universe/Azazel_(mutant)?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"},{\"type\":\"comiclink\",\"url\":\"http://marvel.com/comics/characters/1011766/azazel_mutant?utm_campaign=apiRef&utm_source=74129ef99c9fd5f7692608f17abb88f9\"}]}]}}";
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/saulmm/avengers/tests/CharacterListActivityInstrumentationTest.java:
--------------------------------------------------------------------------------
1 | package saulmm.avengers.tests;
2 |
3 | import android.content.Intent;
4 | import android.support.test.espresso.Espresso;
5 | import android.support.test.espresso.contrib.RecyclerViewActions;
6 | import android.support.test.espresso.intent.Intents;
7 | import android.support.test.espresso.intent.rule.IntentsTestRule;
8 | import android.support.test.espresso.matcher.ViewMatchers;
9 | import android.support.test.rule.ActivityTestRule;
10 | import android.support.test.runner.AndroidJUnit4;
11 | import android.test.suitebuilder.annotation.LargeTest;
12 | import android.util.Log;
13 | import android.widget.TextView;
14 |
15 | import com.squareup.okhttp.mockwebserver.Dispatcher;
16 | import com.squareup.okhttp.mockwebserver.MockResponse;
17 | import com.squareup.okhttp.mockwebserver.MockWebServer;
18 | import com.squareup.okhttp.mockwebserver.RecordedRequest;
19 |
20 | import org.junit.After;
21 | import org.junit.Before;
22 | import org.junit.Rule;
23 | import org.junit.Test;
24 | import org.junit.runner.RunWith;
25 |
26 | import javax.inject.Inject;
27 |
28 | import saulmm.avengers.R;
29 | import saulmm.avengers.TestData;
30 | import saulmm.avengers.injector.components.DaggerAvengersComponent;
31 | import saulmm.avengers.injector.modules.ActivityModule;
32 | import saulmm.avengers.repository.CharacterRepository;
33 | import saulmm.avengers.rest.RestDataSource;
34 | import saulmm.avengers.views.activities.CharacterDetailActivity;
35 | import saulmm.avengers.views.activities.CharacterListActivity;
36 |
37 | import static android.support.test.espresso.Espresso.*;
38 | import static android.support.test.espresso.action.ViewActions.click;
39 | import static android.support.test.espresso.assertion.ViewAssertions.matches;
40 | import static android.support.test.espresso.intent.Intents.intended;
41 | import static android.support.test.espresso.intent.Intents.times;
42 | import static android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent;
43 | import static android.support.test.espresso.matcher.ViewMatchers.*;
44 | import static android.support.test.espresso.matcher.ViewMatchers.withId;
45 | import static org.hamcrest.CoreMatchers.allOf;
46 | import static org.hamcrest.CoreMatchers.instanceOf;
47 | import static org.hamcrest.CoreMatchers.is;
48 |
49 | @LargeTest
50 | @RunWith(AndroidJUnit4.class)
51 | public class CharacterListActivityInstrumentationTest {
52 | private MockWebServer mMockWebServer;
53 |
54 | @Rule public ActivityTestRule mCharacterListIntentRule =
55 | new ActivityTestRule<>(CharacterListActivity.class, true, false);
56 |
57 | @Before public void setUp() throws Exception {
58 | mMockWebServer = new MockWebServer();
59 | mMockWebServer.start();
60 |
61 | RestDataSource.END_POINT = mMockWebServer.getUrl("/").toString();
62 | }
63 |
64 | @Test public void showCharacters() {
65 | mMockWebServer.enqueue(new MockResponse().setBody(TestData.TWENTY_CHARACTERS_JSON));
66 |
67 | mCharacterListIntentRule.launchActivity(null);
68 |
69 | onView(withId(R.id.activity_avengers_recycler)).check(matches(isDisplayed()));
70 | }
71 |
72 | @After public void tearDown() throws Exception {
73 | mMockWebServer.shutdown();
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
16 |
17 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/app/src/main/assets/Abel.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saulmm/Avengers/2b02f19d5a0ca3e36b5370f545036b30fd62a114/app/src/main/assets/Abel.ttf
--------------------------------------------------------------------------------
/app/src/main/java/saulmm/avengers/AvengersApplication.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | */
6 | package saulmm.avengers;
7 |
8 | import android.app.Application;
9 |
10 | import saulmm.avengers.injector.modules.AppModule;
11 | import saulmm.avengers.injector.components.AppComponent;
12 | import saulmm.avengers.injector.components.DaggerAppComponent;
13 |
14 | public class AvengersApplication extends Application {
15 | private AppComponent mAppComponent;
16 |
17 | @Override
18 | public void onCreate() {
19 | super.onCreate();
20 | initializeInjector();
21 | }
22 |
23 | private void initializeInjector() {
24 | mAppComponent = DaggerAppComponent.builder()
25 | .appModule(new AppModule(this))
26 | .build();
27 | }
28 |
29 | public AppComponent getAppComponent() {
30 | return mAppComponent;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/java/saulmm/avengers/ButterKnifeUtils.java:
--------------------------------------------------------------------------------
1 | package saulmm.avengers;
2 |
3 | import android.widget.TextView;
4 | import butterknife.ButterKnife;
5 |
6 | public class ButterKnifeUtils {
7 |
8 | public static final ButterKnife.Setter TEXTCOLOR = new ButterKnife.Setter() {
9 |
10 | @Override
11 | public void set(TextView view, Integer value, int index) {
12 | view.setTextColor(value);
13 | }
14 | };
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/java/saulmm/avengers/ResizeBehavior.java:
--------------------------------------------------------------------------------
1 | package saulmm.avengers;
2 |
3 | import android.content.Context;
4 | import android.support.design.widget.AppBarLayout;
5 | import android.support.design.widget.CoordinatorLayout;
6 | import android.util.AttributeSet;
7 | import android.view.View;
8 | import android.widget.ImageView;
9 |
10 | @SuppressWarnings("unused")
11 | public class ResizeBehavior extends CoordinatorLayout.Behavior {
12 | private final static String TAG = "behavior";
13 |
14 | private float mFinalLeftAvatarPadding;
15 | private float mStartPosition;
16 | private float mStartToolbarPosition;
17 | private int mStartWidth;
18 | private int mFinalWidth;
19 |
20 | public ResizeBehavior(Context context, AttributeSet attrs) {
21 | }
22 |
23 | private int finalHeight;
24 | private int mStartHeight;
25 |
26 | @Override
27 | public boolean layoutDependsOn(CoordinatorLayout parent, ImageView child, View dependency) {
28 | return dependency instanceof AppBarLayout;
29 | }
30 |
31 | @Override
32 | public boolean onDependentViewChanged(CoordinatorLayout parent, ImageView child, View dependency) {
33 | shouldInitProperties(child, dependency);
34 | float expandedPercentageFactor = dependency.getY() / mStartHeight;
35 |
36 | // The child will remain always in the middle of its dependency
37 | child.setY(((dependency.getY() /2) + (dependency.getHeight() /2)) - child.getHeight()/2);
38 | float collapsedHeight = ((mStartHeight) * (1f - Math.abs(expandedPercentageFactor)));
39 |
40 | CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
41 | if (collapsedHeight > finalHeight) lp.height = (int) collapsedHeight;
42 | child.setLayoutParams(lp);
43 | return true;
44 | }
45 |
46 | private void shouldInitProperties(ImageView child, View dependency) {
47 | if (mStartHeight == 0)
48 | mStartHeight = child.getHeight();
49 |
50 | if (mStartWidth == 0)
51 | mStartWidth = child.getWidth();
52 |
53 | if (finalHeight == 0) finalHeight =
54 | child.getContext().getResources().getDimensionPixelOffset(R.dimen.image_final_height);
55 |
56 | if (mStartToolbarPosition == 0)
57 | mStartToolbarPosition = dependency.getY() + (dependency.getHeight()/2);
58 | }
59 | }
--------------------------------------------------------------------------------
/app/src/main/java/saulmm/avengers/injector/Activity.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2015 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package saulmm.avengers.injector;
6 |
7 | import java.lang.annotation.Retention;
8 |
9 | import javax.inject.Scope;
10 |
11 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
12 |
13 |
14 | @Scope @Retention(RUNTIME)
15 | public @interface Activity {}
16 |
--------------------------------------------------------------------------------
/app/src/main/java/saulmm/avengers/injector/components/ActivityComponent.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | */
6 | package saulmm.avengers.injector.components;
7 |
8 |
9 | import android.content.Context;
10 | import dagger.Component;
11 | import saulmm.avengers.injector.Activity;
12 | import saulmm.avengers.injector.modules.ActivityModule;
13 |
14 | @Activity
15 | @Component(dependencies = AppComponent.class, modules = ActivityModule.class)
16 | public interface ActivityComponent {
17 |
18 | Context context();
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/saulmm/avengers/injector/components/AppComponent.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | */
6 | package saulmm.avengers.injector.components;
7 |
8 | import dagger.Component;
9 |
10 | import javax.inject.Named;
11 | import javax.inject.Singleton;
12 |
13 | import rx.Scheduler;
14 | import saulmm.avengers.AvengersApplication;
15 | import saulmm.avengers.injector.modules.AppModule;
16 | import saulmm.avengers.repository.CharacterRepository;
17 | import saulmm.avengers.rest.Endpoint;
18 | import saulmm.avengers.rest.MarvelAuthorizer;
19 |
20 | @Singleton @Component(modules = AppModule.class)
21 | public interface AppComponent {
22 | AvengersApplication app();
23 | CharacterRepository dataRepository();
24 | Endpoint restEndpoint();
25 | MarvelAuthorizer marvelAuthorizer();
26 |
27 | @Named("ui_thread") Scheduler uiThread();
28 | @Named("executor_thread") Scheduler executorThread();
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/java/saulmm/avengers/injector/components/AvengerInformationComponent.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | */
6 | package saulmm.avengers.injector.components;
7 |
8 | import dagger.Component;
9 | import saulmm.avengers.CharacterDetailsUsecase;
10 | import saulmm.avengers.GetCollectionUsecase;
11 | import saulmm.avengers.injector.Activity;
12 | import saulmm.avengers.injector.modules.ActivityModule;
13 | import saulmm.avengers.injector.modules.AvengerInformationModule;
14 | import saulmm.avengers.views.activities.CharacterDetailActivity;
15 | import saulmm.avengers.views.activities.CollectionActivity;
16 |
17 | @Activity
18 | @Component(dependencies = AppComponent.class, modules = {AvengerInformationModule.class, ActivityModule.class})
19 | public interface AvengerInformationComponent extends ActivityComponent {
20 |
21 | void inject (CharacterDetailActivity detailActivity);
22 |
23 | void inject (CollectionActivity detailActivity);
24 |
25 | CharacterDetailsUsecase getCharacerInformationUsecase();
26 | GetCollectionUsecase getCollectionUsecase();
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/java/saulmm/avengers/injector/components/AvengersComponent.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | */
6 | package saulmm.avengers.injector.components;
7 |
8 | import android.content.Context;
9 |
10 | import dagger.Component;
11 | import saulmm.avengers.injector.Activity;
12 | import saulmm.avengers.injector.modules.ActivityModule;
13 | import saulmm.avengers.views.activities.CharacterListActivity;
14 |
15 | @Activity
16 | @Component(dependencies = AppComponent.class, modules = {ActivityModule.class})
17 | public interface AvengersComponent extends ActivityComponent {
18 | void inject (CharacterListActivity activity);
19 |
20 | Context activityContext();
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/saulmm/avengers/injector/modules/ActivityModule.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | */
6 | package saulmm.avengers.injector.modules;
7 |
8 |
9 | import android.content.Context;
10 |
11 | import dagger.Module;
12 | import dagger.Provides;
13 | import javax.inject.Named;
14 | import rx.Scheduler;
15 | import rx.android.schedulers.AndroidSchedulers;
16 | import rx.schedulers.Schedulers;
17 | import saulmm.avengers.injector.Activity;
18 |
19 | @Module
20 | public class ActivityModule {
21 | private final Context mContext;
22 |
23 | public ActivityModule(Context mContext) {
24 | this.mContext = mContext;
25 | }
26 |
27 | @Provides @Activity
28 | Context provideActivityContext() {
29 | return mContext;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/saulmm/avengers/injector/modules/AppModule.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | */
6 | package saulmm.avengers.injector.modules;
7 |
8 | import dagger.Module;
9 | import dagger.Provides;
10 |
11 | import javax.inject.Named;
12 | import javax.inject.Singleton;
13 |
14 | import rx.Scheduler;
15 | import rx.android.schedulers.AndroidSchedulers;
16 | import rx.schedulers.Schedulers;
17 | import saulmm.avengers.AvengersApplication;
18 | import saulmm.avengers.BuildConfig;
19 | import saulmm.avengers.repository.CharacterRepository;
20 | import saulmm.avengers.rest.Endpoint;
21 | import saulmm.avengers.rest.MarvelAuthorizer;
22 | import saulmm.avengers.rest.RestDataSource;
23 |
24 | @Module
25 | public class AppModule {
26 | private final AvengersApplication mAvengersApplication;
27 |
28 | public AppModule(AvengersApplication avengersApplication) {
29 | this.mAvengersApplication = avengersApplication;
30 | }
31 |
32 | @Provides @Singleton
33 | AvengersApplication provideAvengersApplicationContext() {
34 | return mAvengersApplication; }
35 |
36 | @Provides
37 | MarvelAuthorizer provideMarvelAuthorizer() {
38 | return new MarvelAuthorizer(BuildConfig.MARVEL_PUBLIC_KEY, BuildConfig.MARVEL_PRIVATE_KEY);
39 | }
40 |
41 | @Provides
42 | Endpoint provideRestEndpoint() {
43 | return new Endpoint("http://gateway.marvel.com/");
44 | }
45 |
46 | @Provides @Singleton
47 | CharacterRepository provideDataRepository(RestDataSource restDataSource) {
48 | return restDataSource; }
49 |
50 | @Provides @Named("executor_thread")
51 | Scheduler provideExecutorThread() {
52 | return Schedulers.newThread();
53 | }
54 |
55 | @Provides @Named("ui_thread")
56 | Scheduler provideUiThread() {
57 | return AndroidSchedulers.mainThread();
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/app/src/main/java/saulmm/avengers/injector/modules/AvengerInformationModule.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | */
6 | package saulmm.avengers.injector.modules;
7 |
8 | import javax.inject.Named;
9 |
10 | import dagger.Module;
11 | import dagger.Provides;
12 | import rx.Scheduler;
13 | import saulmm.avengers.CharacterDetailsUsecase;
14 | import saulmm.avengers.GetCollectionUsecase;
15 | import saulmm.avengers.injector.Activity;
16 | import saulmm.avengers.repository.CharacterRepository;
17 | import saulmm.avengers.rest.MarvelAuthorizer;
18 |
19 | @Module
20 | public class AvengerInformationModule {
21 | private final int mCharacterId;
22 |
23 | public AvengerInformationModule(int characterId) {
24 | mCharacterId = characterId;
25 | }
26 |
27 | @Provides @Activity CharacterDetailsUsecase provideGetCharacterInformationUsecase (
28 | CharacterRepository repository,
29 | @Named("ui_thread") Scheduler uiThread,
30 | @Named("executor_thread") Scheduler executorThread) {
31 |
32 | return new CharacterDetailsUsecase(mCharacterId, repository, uiThread, executorThread);
33 | }
34 |
35 | @Provides @Activity GetCollectionUsecase provideGetCharacterComicsUsecase (CharacterRepository repository,
36 | @Named("ui_thread") Scheduler uiThread,
37 | @Named("executor_thread") Scheduler executorThread) {
38 |
39 | return new GetCollectionUsecase(mCharacterId, repository, uiThread, executorThread);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/java/saulmm/avengers/mvp/presenters/CharacterDetailPresenter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | */
6 | package saulmm.avengers.mvp.presenters;
7 |
8 | import javax.inject.Inject;
9 | import rx.Subscription;
10 | import saulmm.avengers.CharacterDetailsUsecase;
11 | import saulmm.avengers.entities.MarvelCharacter;
12 | import saulmm.avengers.mvp.views.CharacterDetailView;
13 | import saulmm.avengers.mvp.views.View;
14 |
15 | public class CharacterDetailPresenter implements Presenter {
16 | private CharacterDetailView mCharacterDetailView;
17 |
18 | private final CharacterDetailsUsecase mGetCharacterInformationUsecase;
19 |
20 | private Subscription mCharacterSubscription;
21 | private int mCharacterId;
22 | private String mCharacterName;
23 |
24 | @Inject
25 | public CharacterDetailPresenter(CharacterDetailsUsecase getCharacterInformationUsecase) {
26 | mGetCharacterInformationUsecase = getCharacterInformationUsecase;
27 | }
28 |
29 | @Override
30 | public void onCreate() {
31 | if (mCharacterId == -1 || mCharacterName == null)
32 | throw new IllegalStateException();
33 |
34 | mCharacterDetailView.disableScroll();
35 | askForCharacterDetails();
36 | }
37 |
38 | public void askForCharacterDetails() {
39 | mCharacterSubscription = mGetCharacterInformationUsecase.execute()
40 | .subscribe(this::onCharacterReceived, this::manageCharacterError);
41 | }
42 |
43 | @Override
44 | public void onStart() {
45 | // Unused
46 | }
47 |
48 | @Override
49 | public void onStop() {
50 | if (!mCharacterSubscription.isUnsubscribed())
51 | mCharacterSubscription.unsubscribe();
52 | }
53 |
54 | @Override
55 | public void onPause() {
56 | // Unused
57 | }
58 |
59 | @Override
60 | public void attachView(View v) {
61 | mCharacterDetailView = (CharacterDetailView) v;
62 | }
63 |
64 | public void initializePresenter(int characterId, String characterName) {
65 | mCharacterId = characterId;
66 | mCharacterName = characterName;
67 | }
68 |
69 | private void manageCharacterError(Throwable error) {
70 | }
71 |
72 | private void onCharacterReceived(MarvelCharacter character) {
73 | mCharacterDetailView.bindCharacter(character);
74 |
75 | if (character.getDescription() != null && !character.getDescription().equals(""))
76 | mCharacterDetailView.enableScroll();
77 | }
78 |
79 | public void onComicsIndicatorPressed() {
80 | mCharacterDetailView.goToCharacterComicsView(mCharacterId);
81 | }
82 |
83 | public void onSeriesIndicatorPressed() {
84 | mCharacterDetailView.goToCharacterSeriesView(mCharacterId);
85 | }
86 |
87 | public void onStoriesIndicatorPressed() {
88 | mCharacterDetailView.goToCharacterStoriesView(mCharacterId);
89 | }
90 |
91 | public void onEventIndicatorPressed() {
92 | mCharacterDetailView.goToCharacterEventsView(mCharacterId);
93 | }
94 |
95 | public void setCharacterId(int characterId) {
96 | mCharacterId = characterId;
97 | }
98 |
99 | public void onImageReceived() {
100 | mCharacterDetailView.hideRevealViewByAlpha();
101 | }
102 | }
--------------------------------------------------------------------------------
/app/src/main/java/saulmm/avengers/mvp/presenters/CharacterListPresenter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | */
6 | package saulmm.avengers.mvp.presenters;
7 |
8 | import java.util.ArrayList;
9 | import java.util.List;
10 | import javax.inject.Inject;
11 | import rx.Subscription;
12 | import saulmm.avengers.GetCharactersUsecase;
13 | import saulmm.avengers.entities.MarvelCharacter;
14 | import saulmm.avengers.mvp.views.CharacterListView;
15 | import saulmm.avengers.mvp.views.View;
16 |
17 | public class CharacterListPresenter implements Presenter {
18 | private final GetCharactersUsecase mCharactersUsecase;
19 | private boolean mIsTheCharacterRequestRunning;
20 | private Subscription mCharactersSubscription;
21 |
22 | private List mCharacters = new ArrayList<>();
23 | private CharacterListView mAvengersView;
24 |
25 | @Inject
26 | public CharacterListPresenter(GetCharactersUsecase charactersUsecase) {
27 | mCharactersUsecase = charactersUsecase;
28 | }
29 |
30 | @Override
31 | public void onCreate() {
32 | askForCharacters();
33 | }
34 |
35 |
36 | @Override
37 | public void onStart() {
38 | // Unused
39 | }
40 |
41 | @Override
42 | public void onStop() {
43 | // Unused
44 | }
45 |
46 | @Override
47 | public void onPause() {
48 | mAvengersView.hideLoadingMoreCharactersIndicator();
49 | mCharactersSubscription.unsubscribe();
50 | mIsTheCharacterRequestRunning = false;
51 | }
52 |
53 | @Override
54 | public void attachView(View v) {
55 | mAvengersView = (CharacterListView) v;
56 | }
57 |
58 | public void onListEndReached() {
59 | if (!mIsTheCharacterRequestRunning) askForNewCharacters();
60 | }
61 |
62 | public void askForCharacters() {
63 | mIsTheCharacterRequestRunning = true;
64 | mAvengersView.hideErrorView();
65 | mAvengersView.showLoadingView();
66 |
67 | mCharactersSubscription = mCharactersUsecase.execute()
68 | .subscribe(this::onCharactersReceived, this::showErrorView);
69 | }
70 |
71 | public void onCharactersReceived(List characters) {
72 | mCharacters.addAll(characters);
73 | mAvengersView.bindCharacterList(mCharacters);
74 | mAvengersView.showCharacterList();
75 | mAvengersView.hideEmptyIndicator();
76 | mIsTheCharacterRequestRunning = false;
77 | }
78 |
79 | public void askForNewCharacters() {
80 | mAvengersView.showLoadingMoreCharactersIndicator();
81 | mIsTheCharacterRequestRunning = true;
82 |
83 | mCharactersSubscription = mCharactersUsecase.execute()
84 | .subscribe(this::onNewCharactersReceived, this::onNewCharactersError);
85 | }
86 |
87 | private void onNewCharactersError(Throwable error) {
88 | showGenericError();
89 | mIsTheCharacterRequestRunning = false;
90 | }
91 |
92 | private void onNewCharactersReceived(List newCharacters) {
93 | mCharacters.addAll(newCharacters);
94 | mAvengersView.updateCharacterList(GetCharactersUsecase.DEFAULT_CHARACTERS_LIMIT);
95 |
96 | mAvengersView.hideLoadingIndicator();
97 | mIsTheCharacterRequestRunning = false;
98 | }
99 |
100 | public void showErrorView(Throwable error) {
101 | mAvengersView.showUknownErrorMessage();
102 | mAvengersView.hideLoadingMoreCharactersIndicator();
103 | mAvengersView.hideEmptyIndicator();
104 | mAvengersView.hideCharactersList();
105 | }
106 |
107 | public void showGenericError() {
108 | mAvengersView.hideLoadingIndicator();
109 | mAvengersView.showLightError();
110 | }
111 |
112 | public void onErrorRetryRequest() {
113 | if (mCharacters.isEmpty())
114 | askForCharacters();
115 | else
116 | askForNewCharacters();
117 | }
118 |
119 | public void onElementClick(int position) {
120 | int characterId = mCharacters.get(position).getId();
121 | String characterName = mCharacters.get(position).getName();
122 | mAvengersView.showDetailScreen(characterName, characterId);
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/app/src/main/java/saulmm/avengers/mvp/presenters/CollectionPresenter.java:
--------------------------------------------------------------------------------
1 | package saulmm.avengers.mvp.presenters;
2 |
3 | import android.content.Context;
4 | import java.util.List;
5 | import javax.inject.Inject;
6 | import rx.android.schedulers.AndroidSchedulers;
7 | import rx.schedulers.Schedulers;
8 | import saulmm.avengers.GetCollectionUsecase;
9 | import saulmm.avengers.entities.CollectionItem;
10 | import saulmm.avengers.mvp.views.CollectionView;
11 | import saulmm.avengers.mvp.views.View;
12 |
13 | public class CollectionPresenter implements Presenter {
14 | private final GetCollectionUsecase mGetCollectionUsecase;
15 | private final Context mActivityContext;
16 | private int mCharacterId;
17 | private String mCollectionType;
18 | private CollectionView mCollectionView;
19 |
20 | @Inject
21 | public CollectionPresenter(GetCollectionUsecase getCollectionUsecase, Context activityContext) {
22 | mGetCollectionUsecase = getCollectionUsecase;
23 | mActivityContext = activityContext;
24 | }
25 |
26 | @Override public void onStart() {}
27 |
28 | @Override public void onStop() {}
29 |
30 | @Override
31 | public void onPause() {}
32 |
33 | @Override
34 | public void attachView(View v) {
35 | mCollectionView = (CollectionView) v;
36 | }
37 |
38 | @Override
39 | public void onCreate() {
40 | mGetCollectionUsecase.setType(mCollectionType);
41 | mGetCollectionUsecase.execute()
42 | .subscribe(this::onCollectionItemsReceived);
43 | }
44 |
45 | public void initialisePresenters(String collectionType, int characterId) {
46 | mCollectionType = collectionType;
47 | mCharacterId = characterId;
48 | }
49 |
50 | private void onCollectionItemsReceived(List items) {
51 | mCollectionView.hideLoadingIndicator();
52 | mCollectionView.showItems(items);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/app/src/main/java/saulmm/avengers/mvp/presenters/Presenter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | */
6 | package saulmm.avengers.mvp.presenters;
7 |
8 | import saulmm.avengers.mvp.views.View;
9 |
10 | public interface Presenter {
11 | void onStart();
12 |
13 | void onStop();
14 |
15 | void onPause();
16 |
17 | void attachView (View v);
18 |
19 | void onCreate();
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/saulmm/avengers/mvp/views/CharacterDetailView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | */
6 | package saulmm.avengers.mvp.views;
7 |
8 | import android.graphics.Bitmap;
9 | import saulmm.avengers.entities.MarvelCharacter;
10 |
11 | public interface CharacterDetailView extends View {
12 | void disableScroll();
13 |
14 | void hideRevealViewByAlpha();
15 |
16 | void bindCharacter(MarvelCharacter character);
17 |
18 | void enableScroll();
19 |
20 | void goToCharacterComicsView(int characterId);
21 |
22 | void goToCharacterSeriesView(int characterId);
23 |
24 | void goToCharacterEventsView(int characterId);
25 |
26 | void goToCharacterStoriesView(int characterId);
27 |
28 | void showError(String s);
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/app/src/main/java/saulmm/avengers/mvp/views/CharacterListView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | */
6 | package saulmm.avengers.mvp.views;
7 |
8 | import android.app.ActivityOptions;
9 | import java.util.List;
10 | import saulmm.avengers.entities.MarvelCharacter;
11 |
12 | @SuppressWarnings("unused")
13 | public interface CharacterListView extends View {
14 |
15 | void bindCharacterList(List avengers);
16 |
17 | void showCharacterList();
18 |
19 | void hideCharactersList();
20 |
21 | void showLoadingMoreCharactersIndicator();
22 |
23 | void hideLoadingMoreCharactersIndicator();
24 |
25 | void hideLoadingIndicator ();
26 |
27 | void showLoadingView();
28 |
29 | void hideLoadingView();
30 |
31 | void showLightError();
32 |
33 | void hideErrorView();
34 |
35 | void showEmptyIndicator();
36 |
37 | void hideEmptyIndicator();
38 |
39 | void updateCharacterList(int charactersLimit);
40 |
41 | void showConnectionErrorMessage();
42 |
43 | void showServerErrorMessage();
44 |
45 | void showUknownErrorMessage();
46 |
47 | void showDetailScreen(String characterName, int characterId);
48 | }
49 |
--------------------------------------------------------------------------------
/app/src/main/java/saulmm/avengers/mvp/views/CollectionView.java:
--------------------------------------------------------------------------------
1 | package saulmm.avengers.mvp.views;
2 |
3 | import java.util.List;
4 | import saulmm.avengers.entities.CollectionItem;
5 |
6 | public interface CollectionView extends View {
7 |
8 | void showLoadingIndicator();
9 |
10 | void hideLoadingIndicator();
11 |
12 | void showItems(List items);
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/java/saulmm/avengers/mvp/views/View.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | */
6 | package saulmm.avengers.mvp.views;
7 |
8 | public interface View {
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/java/saulmm/avengers/utils/DatabindingUtils.java:
--------------------------------------------------------------------------------
1 | package saulmm.avengers.utils;
2 |
3 | import android.databinding.BindingAdapter;
4 | import android.view.View;
5 |
6 | public class DatabindingUtils {
7 | @BindingAdapter({"app:onClick"})
8 | public static void bindOnClick(View view, final Runnable runnable) {
9 | view.setOnClickListener(v -> runnable.run());
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/saulmm/avengers/utils/OnCharacterImageCallback.java:
--------------------------------------------------------------------------------
1 | package saulmm.avengers.utils;
2 |
3 | import android.graphics.Bitmap;
4 |
5 | public interface OnCharacterImageCallback {
6 | void onReceive(Bitmap bitmap);
7 | }
--------------------------------------------------------------------------------
/app/src/main/java/saulmm/avengers/utils/TransitionUtils.java:
--------------------------------------------------------------------------------
1 | package saulmm.avengers.utils;
2 |
3 | import android.transition.Explode;
4 | import android.transition.Slide;
5 | import android.transition.Transition;
6 |
7 | public class TransitionUtils {
8 |
9 | public static Transition buildExplodeTransition (Integer... exlcudeIds) {
10 | Explode explodeTransition = new Explode();
11 | excludeTransitionIds(explodeTransition, exlcudeIds);
12 | return explodeTransition;
13 | }
14 |
15 | public static Transition buildSlideTransition (int gravity, Integer... excludeIds) {
16 | Slide explodeTransition = new Slide();
17 | excludeTransitionIds(explodeTransition, excludeIds);
18 | return explodeTransition;
19 | }
20 |
21 | private static void excludeTransitionIds(Transition transition, Integer[] exlcudeIds) {
22 | transition.excludeTarget(android.R.id.statusBarBackground, true);
23 | transition.excludeTarget(android.R.id.navigationBarBackground, true);
24 |
25 | for (Integer exlcudeId : exlcudeIds) {
26 | transition.excludeTarget(exlcudeId, true);
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/java/saulmm/avengers/utils/Utils.java:
--------------------------------------------------------------------------------
1 | package saulmm.avengers.utils;
2 |
3 | public class Utils {
4 |
5 | public final static String CHARACTER_IMG_NAME = "ch_thumb";
6 | public final static String BANGERS_TTF = "fonts/Bangers.ttf";
7 |
8 | public static final int DIALOG_ACCEPT = -1;
9 | public static final int DIALOG_CANCEL = -2;
10 |
11 | public static String getListTransitionName (int position) {
12 | return CHARACTER_IMG_NAME + position;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/saulmm/avengers/views/RecyclerClickListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | */
6 | package saulmm.avengers.views;
7 |
8 | import android.view.View;
9 | import android.widget.ImageView;
10 |
11 | public interface RecyclerClickListener {
12 |
13 | void onElementClick(int position, View sharedView, ImageView characterImageView);
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/saulmm/avengers/views/activities/CharacterDetailActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | */
6 | package saulmm.avengers.views.activities;
7 |
8 | import android.animation.ValueAnimator;
9 | import android.content.Context;
10 | import android.content.Intent;
11 | import android.databinding.BindingAdapter;
12 | import android.databinding.DataBindingUtil;
13 | import android.graphics.Bitmap;
14 | import android.os.Bundle;
15 | import android.support.design.widget.AppBarLayout;
16 | import android.support.design.widget.CollapsingToolbarLayout;
17 | import android.support.v7.app.AlertDialog;
18 | import android.support.v7.app.AppCompatActivity;
19 | import android.support.v7.graphics.Palette;
20 | import android.transition.Transition;
21 | import android.view.Gravity;
22 | import android.view.View;
23 | import android.view.ViewTreeObserver;
24 | import android.widget.ImageView;
25 |
26 | import butterknife.Bind;
27 | import butterknife.BindColor;
28 | import butterknife.BindInt;
29 | import butterknife.ButterKnife;
30 | import com.bumptech.glide.Glide;
31 | import com.bumptech.glide.request.animation.GlideAnimation;
32 | import com.bumptech.glide.request.target.BitmapImageViewTarget;
33 | import javax.inject.Inject;
34 | import saulmm.avengers.AvengersApplication;
35 | import saulmm.avengers.R;
36 | import saulmm.avengers.databinding.ActivityAvengerDetailBinding;
37 | import saulmm.avengers.entities.CollectionItem;
38 | import saulmm.avengers.entities.MarvelCharacter;
39 | import saulmm.avengers.injector.components.DaggerAvengerInformationComponent;
40 | import saulmm.avengers.injector.modules.ActivityModule;
41 | import saulmm.avengers.injector.modules.AvengerInformationModule;
42 | import saulmm.avengers.mvp.presenters.CharacterDetailPresenter;
43 | import saulmm.avengers.mvp.views.CharacterDetailView;
44 | import saulmm.avengers.utils.OnCharacterImageCallback;
45 | import saulmm.avengers.utils.TransitionUtils;
46 | import saulmm.avengers.views.utils.AnimUtils;
47 |
48 | public class CharacterDetailActivity extends AppCompatActivity implements CharacterDetailView {
49 | private static final String EXTRA_CHARACTER_NAME = "character.name";
50 | public static final String EXTRA_CHARACTER_ID = "character.id";
51 |
52 | @Bind(R.id.character_collapsing) CollapsingToolbarLayout mCollapsing;
53 | @BindInt(R.integer.duration_medium) int mAnimMediumDuration;
54 | @BindInt(R.integer.duration_huge) int mAnimHugeDuration;
55 | @BindColor(R.color.brand_primary_dark) int mColorPrimaryDark;
56 |
57 | @Inject CharacterDetailPresenter mCharacterDetailPresenter;
58 | private ActivityAvengerDetailBinding mBinding;
59 |
60 | private OnCharacterImageCallback onCharacterImageCallback = new OnCharacterImageCallback() {
61 |
62 | @Override
63 | public void onReceive(Bitmap bitmap) {
64 | initActivityColors(bitmap);
65 | }
66 | };
67 |
68 | private int mScrolleableAppbarLayoutFlags;
69 |
70 | @Override
71 | public void onCreate(Bundle savedInstanceState) {
72 | super.onCreate(savedInstanceState);
73 | initializeDependencyInjector();
74 | initializeBinding();
75 | initButterknife();
76 | initializePresenter();
77 | initToolbar();
78 | initTransitions();
79 | }
80 |
81 | @Override
82 | public void disableScroll() {
83 | mScrolleableAppbarLayoutFlags = ((AppBarLayout.LayoutParams) mCollapsing.getLayoutParams()).getScrollFlags();
84 | AppBarLayout.LayoutParams layoutParams = ((AppBarLayout.LayoutParams) mCollapsing.getLayoutParams());
85 | layoutParams.setScrollFlags(0);
86 | mCollapsing.setLayoutParams(layoutParams);
87 | }
88 |
89 | private void initializeBinding() {
90 | mBinding = DataBindingUtil.setContentView(
91 | this, R.layout.activity_avenger_detail);
92 |
93 | mBinding.setImageCallback(onCharacterImageCallback);
94 | }
95 |
96 |
97 | public void initActivityColors(Bitmap sourceBitmap) {
98 | Palette.from(sourceBitmap)
99 | .generate(palette -> {
100 | View statsRevealView = mBinding.characterStatsReveal;
101 | View imageCover = mBinding.characterImageReveal;
102 | int darkVibrant = palette.getDarkVibrantColor(mColorPrimaryDark);
103 |
104 | mBinding.setDarkSwatch(palette.getDarkVibrantSwatch());
105 |
106 | ValueAnimator colorAnimation = ValueAnimator.ofArgb(mColorPrimaryDark, darkVibrant);
107 | colorAnimation.setDuration(mAnimHugeDuration);
108 | colorAnimation.addUpdateListener(animator -> {
109 | imageCover.setBackgroundColor((Integer) animator.getAnimatedValue());
110 | });
111 |
112 | colorAnimation.start();
113 |
114 | statsRevealView.getViewTreeObserver().addOnGlobalLayoutListener(
115 | new ViewTreeObserver.OnGlobalLayoutListener() {
116 | @Override public void onGlobalLayout() {
117 | statsRevealView.getViewTreeObserver()
118 | .removeOnGlobalLayoutListener(this);
119 |
120 | AnimUtils.showRevealEffect(statsRevealView,
121 | statsRevealView.getWidth() / 2, 0, null);
122 |
123 | }
124 | });
125 |
126 | getWindow().setStatusBarColor(darkVibrant);
127 | });
128 | }
129 |
130 |
131 | private void initButterknife() {
132 | ButterKnife.bind(this);
133 | }
134 |
135 | @Override
136 | protected void onStart() {
137 | super.onStart();
138 | mCharacterDetailPresenter.onStart();
139 | }
140 |
141 | private void initializePresenter() {
142 | int characterId = getIntent().getIntExtra(EXTRA_CHARACTER_ID, -1);
143 | String characterName = getIntent().getStringExtra(EXTRA_CHARACTER_NAME);
144 |
145 | mCharacterDetailPresenter.attachView(this);
146 | mCharacterDetailPresenter.setCharacterId(characterId);
147 | mCharacterDetailPresenter.initializePresenter(characterId, characterName);
148 | mCharacterDetailPresenter.onCreate();
149 | mBinding.setPresenter(mCharacterDetailPresenter);
150 | }
151 |
152 | private void initializeDependencyInjector() {
153 | AvengersApplication avengersApplication = (AvengersApplication) getApplication();
154 |
155 | int avengerId = getIntent().getIntExtra(EXTRA_CHARACTER_ID, -1);
156 |
157 | DaggerAvengerInformationComponent.builder()
158 | .activityModule(new ActivityModule(this))
159 | .appComponent(avengersApplication.getAppComponent())
160 | .avengerInformationModule(new AvengerInformationModule(avengerId))
161 | .build().inject(this);
162 | }
163 |
164 | private void initTransitions() {
165 | final String sharedViewName = getIntent().getStringExtra(
166 | CharacterListActivity.EXTRA_IMAGE_TRANSITION_NAME);
167 |
168 | String characterTitle = getIntent().getStringExtra(
169 | CharacterListActivity.EXTRA_CHARACTER_NAME);
170 |
171 | Transition enterTransition = TransitionUtils.buildSlideTransition(Gravity.BOTTOM);
172 | enterTransition.setDuration(mAnimMediumDuration);
173 |
174 | getWindow().setEnterTransition(enterTransition);
175 | getWindow().setReturnTransition(TransitionUtils.buildSlideTransition(Gravity.BOTTOM));
176 |
177 | View collapsingToolbar = mBinding.characterCollapsing;
178 | View imageReveal = mBinding.characterImageReveal;
179 | collapsingToolbar.getViewTreeObserver().addOnGlobalLayoutListener(
180 | new ViewTreeObserver.OnGlobalLayoutListener() {
181 | @Override public void onGlobalLayout() {
182 | collapsingToolbar.getViewTreeObserver().removeOnGlobalLayoutListener(this);
183 | int width = imageReveal.getWidth();
184 | int height = imageReveal.getHeight();
185 |
186 | AnimUtils.showRevealEffect(imageReveal, width / 2, height / 2, null);
187 | }
188 | });
189 | }
190 |
191 | @Override
192 | public void hideRevealViewByAlpha() {
193 | mBinding.characterImageReveal.animate().alpha(0f).setDuration(
194 | mAnimHugeDuration).start();
195 | }
196 |
197 | private void initToolbar() {
198 | mBinding.characterToolbar.setNavigationOnClickListener(v -> onBackPressed());
199 | }
200 |
201 | @Override
202 | public void showError(String errorMessage) {
203 | new AlertDialog.Builder(this)
204 | .setTitle(getString(R.string.dialog_error))
205 | .setPositiveButton(getString(R.string.action_accept), (dialog, which) -> finish())
206 | .setMessage(errorMessage)
207 | .setCancelable(false).show();
208 | }
209 |
210 | @Override
211 | public void bindCharacter(MarvelCharacter character) {
212 | mBinding.setCharacter(character);
213 | }
214 |
215 | @Override
216 | public void enableScroll() {
217 | AppBarLayout.LayoutParams layoutParams = (AppBarLayout.LayoutParams) mCollapsing.getLayoutParams();
218 | layoutParams.setScrollFlags(mScrolleableAppbarLayoutFlags);
219 | mCollapsing.setLayoutParams(layoutParams);
220 | }
221 |
222 | @Override
223 | public void goToCharacterComicsView(int characterId) {
224 | CollectionActivity.start(this, characterId, CollectionItem.COMICS);
225 | }
226 |
227 | @Override
228 | public void goToCharacterSeriesView(int characterId) {
229 | CollectionActivity.start(this, characterId, CollectionItem.SERIES);
230 | }
231 |
232 | @Override
233 | public void goToCharacterEventsView(int characterId) {
234 | CollectionActivity.start(this, characterId, CollectionItem.EVENTS);
235 | }
236 |
237 | @Override
238 | public void goToCharacterStoriesView(int characterId) {
239 | CollectionActivity.start(this, characterId, CollectionItem.STORIES);
240 | }
241 |
242 | @BindingAdapter({"source", "presenter", "callback"})
243 | public static void setImageSource(ImageView v, String url,
244 | CharacterDetailPresenter detailPresenter, OnCharacterImageCallback imageCallback) {
245 |
246 | Glide.with(v.getContext()).load(url).asBitmap().into(new BitmapImageViewTarget(v) {
247 | @Override public void onResourceReady(Bitmap resource,
248 | GlideAnimation super Bitmap> glideAnimation) {
249 | super.onResourceReady(resource, glideAnimation);
250 | v.setImageBitmap(resource);
251 | imageCallback.onReceive(resource);
252 | detailPresenter.onImageReceived();
253 |
254 | }
255 | });
256 | }
257 |
258 | @Override
259 | protected void onStop() {
260 | super.onStop();
261 | mCharacterDetailPresenter.onStop();
262 | }
263 |
264 | public static void start(Context context, String characterName, int characterId) {
265 | Intent characterDetailItent = new Intent(context, CharacterDetailActivity.class);
266 | characterDetailItent.putExtra(EXTRA_CHARACTER_NAME, characterName);
267 | characterDetailItent.putExtra(EXTRA_CHARACTER_ID, characterId);
268 | context.startActivity(characterDetailItent);
269 | }
270 | }
271 |
--------------------------------------------------------------------------------
/app/src/main/java/saulmm/avengers/views/activities/CharacterListActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | */
6 | package saulmm.avengers.views.activities;
7 |
8 | import android.app.ActivityOptions;
9 | import android.os.Bundle;
10 | import android.support.design.widget.CollapsingToolbarLayout;
11 | import android.support.design.widget.Snackbar;
12 | import android.support.v7.app.AppCompatActivity;
13 | import android.support.v7.widget.LinearLayoutManager;
14 | import android.support.v7.widget.RecyclerView;
15 | import android.support.v7.widget.RecyclerView.OnScrollListener;
16 | import android.support.v7.widget.Toolbar;
17 | import android.view.View;
18 | import android.widget.ProgressBar;
19 | import android.widget.TextView;
20 | import butterknife.Bind;
21 | import butterknife.ButterKnife;
22 | import butterknife.OnClick;
23 | import java.util.List;
24 | import javax.inject.Inject;
25 | import saulmm.avengers.AvengersApplication;
26 | import saulmm.avengers.R;
27 | import saulmm.avengers.entities.MarvelCharacter;
28 | import saulmm.avengers.injector.components.DaggerAvengersComponent;
29 | import saulmm.avengers.injector.modules.ActivityModule;
30 |
31 | import saulmm.avengers.mvp.presenters.CharacterListPresenter;
32 | import saulmm.avengers.mvp.views.CharacterListView;
33 | import saulmm.avengers.utils.Utils;
34 | import saulmm.avengers.views.adapter.AvengersListAdapter;
35 | import saulmm.avengers.views.views.RecyclerInsetsDecoration;
36 |
37 |
38 | public class CharacterListActivity extends AppCompatActivity
39 | implements CharacterListView {
40 |
41 | public final static String EXTRA_CHARACTER_NAME = "character_name";
42 | public final static String EXTRA_IMAGE_TRANSITION_NAME = "transition_name";
43 |
44 | @Bind(R.id.activity_avengers_recycler) RecyclerView mAvengersRecycler;
45 | @Bind(R.id.activity_avengers_toolbar) Toolbar mAvengersToolbar;
46 | @Bind(R.id.activity_avengers_progress) ProgressBar mAvengersProgress;
47 | @Bind(R.id.activity_avengers_collapsing) CollapsingToolbarLayout mCollapsingToolbar;
48 | @Bind(R.id.activity_avengers_empty_indicator) View mEmptyIndicator;
49 | @Bind(R.id.activity_avengers_error_view) View mErrorView;
50 |
51 | @Inject CharacterListPresenter mAvengersListPresenter;
52 |
53 | private AvengersListAdapter mCharacterListAdapter;
54 | private Snackbar mLoadingMoreCharactersSnack;
55 |
56 | @Override
57 | protected void onCreate(Bundle savedInstanceState) {
58 | super.onCreate(savedInstanceState);
59 | initUi();
60 | initializeToolbar();
61 | initializeRecyclerView();
62 | initializeDependencyInjector();
63 | initializePresenter();
64 | }
65 |
66 | private void initUi() {
67 | setContentView(R.layout.activity_avengers_list);
68 | ButterKnife.bind(this);
69 | }
70 |
71 | private void initializeToolbar() {
72 | mCollapsingToolbar.setTitle("");
73 | }
74 |
75 | @OnClick(R.id.view_error_retry_button)
76 | public void onRetryButtonClicked(View v) {
77 | mAvengersListPresenter.onErrorRetryRequest();
78 | }
79 |
80 | @Override
81 | protected void onStart() {
82 | super.onStart();
83 | mAvengersListPresenter.onStart();
84 | }
85 |
86 | @Override
87 | protected void onPause() {
88 | super.onPause();
89 | mAvengersListPresenter.onPause();
90 | }
91 |
92 | private void initializePresenter() {
93 | mAvengersListPresenter.attachView(this);
94 | mAvengersListPresenter.onCreate();
95 | }
96 |
97 | private void initializeDependencyInjector() {
98 | AvengersApplication avengersApplication = (AvengersApplication) getApplication();
99 |
100 | DaggerAvengersComponent.builder()
101 | .activityModule(new ActivityModule(this))
102 | .appComponent(avengersApplication.getAppComponent())
103 | .build().inject(this);
104 | }
105 |
106 | private void initializeRecyclerView() {
107 | mAvengersRecycler.setLayoutManager(new LinearLayoutManager(this));
108 | mAvengersRecycler.addItemDecoration(new RecyclerInsetsDecoration(this));
109 | mAvengersRecycler.addOnScrollListener(mOnScrollListener);
110 | }
111 |
112 | @Override
113 | public void bindCharacterList(List avengers) {
114 | mCharacterListAdapter = new AvengersListAdapter(avengers, this,
115 | (position, sharedView, characterImageView) -> {
116 | mAvengersListPresenter.onElementClick(position);
117 | });
118 |
119 | mAvengersRecycler.setAdapter(mCharacterListAdapter);
120 | }
121 |
122 | @Override
123 | public void showCharacterList() {
124 | if (mAvengersRecycler.getVisibility() == View.GONE ||
125 | mAvengersRecycler.getVisibility() == View.INVISIBLE)
126 |
127 | mAvengersRecycler.setVisibility(View.VISIBLE);
128 | }
129 |
130 | @Override
131 | public void updateCharacterList(int charactersAdded) {
132 | mCharacterListAdapter.notifyItemRangeInserted(
133 | mCharacterListAdapter.getItemCount() + charactersAdded, charactersAdded);
134 | }
135 |
136 | @Override
137 | public void hideCharactersList() {
138 | mAvengersRecycler.setVisibility(View.GONE);
139 | }
140 |
141 | @Override
142 | public void showLoadingMoreCharactersIndicator() {
143 | mLoadingMoreCharactersSnack = Snackbar.make(mAvengersRecycler,
144 | getString(R.string.message_loading_more_characters), Snackbar.LENGTH_INDEFINITE);
145 |
146 | mLoadingMoreCharactersSnack.show();
147 | }
148 |
149 | @Override
150 | public void hideLoadingMoreCharactersIndicator() {
151 | if (mLoadingMoreCharactersSnack != null) mLoadingMoreCharactersSnack.dismiss();
152 | }
153 |
154 | @Override
155 | public void hideLoadingIndicator() {
156 | mLoadingMoreCharactersSnack.dismiss();
157 | }
158 |
159 | @Override
160 | public void showLoadingView() {
161 | mEmptyIndicator.setVisibility(View.VISIBLE);
162 | }
163 |
164 | @Override
165 | public void hideLoadingView() {
166 | mEmptyIndicator.setVisibility(View.GONE);
167 | }
168 |
169 | @Override
170 | public void showLightError() {
171 | Snackbar.make(mAvengersRecycler, getString(R.string.error_loading_characters), Snackbar.LENGTH_LONG)
172 | .setAction(R.string.try_again, v -> mAvengersListPresenter.onErrorRetryRequest())
173 | .show();
174 | }
175 |
176 | @Override
177 | public void hideErrorView() {
178 | mErrorView.setVisibility(View.GONE);
179 | }
180 |
181 | @Override
182 | public void showEmptyIndicator() {
183 | mEmptyIndicator.setVisibility(View.VISIBLE);
184 | }
185 |
186 | @Override
187 | public void hideEmptyIndicator() {
188 | mEmptyIndicator.setVisibility(View.GONE);
189 | }
190 |
191 | @Override
192 | protected void onStop() {
193 | super.onStop();
194 | mAvengersListPresenter.onStop();
195 | }
196 |
197 | private OnScrollListener mOnScrollListener = new OnScrollListener() {
198 | @Override
199 | public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
200 | LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
201 | int visibleItemsCount = layoutManager.getChildCount();
202 | int totalItemsCount = layoutManager.getItemCount();
203 | int firstVisibleItemPos = layoutManager.findFirstVisibleItemPosition();
204 |
205 | if (visibleItemsCount + firstVisibleItemPos >= totalItemsCount) {
206 | mAvengersListPresenter.onListEndReached();
207 | }
208 | }
209 | };
210 |
211 | @Override
212 | public void showConnectionErrorMessage() {
213 | TextView errorTextView = ButterKnife.findById(mErrorView, R.id.view_error_message);
214 | errorTextView.setText(R.string.error_network_uknownhost);
215 | mErrorView.setVisibility(View.VISIBLE);
216 | }
217 |
218 | @Override
219 | public void showServerErrorMessage() {
220 | TextView errorTextView = ButterKnife.findById(mErrorView, R.id.view_error_message);
221 | errorTextView.setText(R.string.error_network_marvel_server);
222 | mErrorView.setVisibility(View.VISIBLE);
223 | }
224 |
225 | @Override
226 | public void showUknownErrorMessage() {
227 | TextView errorTextView = ButterKnife.findById(mErrorView, R.id.view_error_message);
228 | errorTextView.setText("Uknown error");
229 | mErrorView.setVisibility(View.VISIBLE);
230 | }
231 |
232 | @Override
233 | public void showDetailScreen(String characterName, int characterId) {
234 | CharacterDetailActivity.start(this, characterName, characterId);
235 | }
236 | }
237 |
--------------------------------------------------------------------------------
/app/src/main/java/saulmm/avengers/views/activities/CollectionActivity.java:
--------------------------------------------------------------------------------
1 | package saulmm.avengers.views.activities;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 | import android.support.v7.app.AppCompatActivity;
7 | import android.support.v7.widget.RecyclerView;
8 | import android.view.LayoutInflater;
9 | import android.view.View;
10 | import android.view.ViewGroup;
11 | import android.widget.ImageView;
12 | import android.widget.ProgressBar;
13 | import android.widget.TextView;
14 | import butterknife.Bind;
15 | import butterknife.ButterKnife;
16 | import com.bumptech.glide.Glide;
17 | import java.util.List;
18 | import javax.inject.Inject;
19 | import saulmm.avengers.AvengersApplication;
20 | import saulmm.avengers.R;
21 | import saulmm.avengers.entities.CollectionItem;
22 | import saulmm.avengers.injector.components.DaggerAvengerInformationComponent;
23 | import saulmm.avengers.injector.modules.ActivityModule;
24 | import saulmm.avengers.injector.modules.AvengerInformationModule;
25 | import saulmm.avengers.mvp.presenters.CollectionPresenter;
26 | import saulmm.avengers.mvp.views.CollectionView;
27 |
28 | public class CollectionActivity extends AppCompatActivity implements CollectionView {
29 | private final static String EXTRA_CHARACTER_ID = "character_id";
30 | private final static String EXTRA_COLLECTION_TYPE = "collection_type";
31 |
32 | @Bind(R.id.collection_list) RecyclerView mCollectionRecycler;
33 | @Bind(R.id.collection_loading) ProgressBar mLoadingIndicator;
34 | @Inject CollectionPresenter mCollectionPresenter;
35 |
36 | @Override
37 | public void onCreate(Bundle savedInstanceState) {
38 | super.onCreate(savedInstanceState);
39 | setContentView(R.layout.activity_character_collection);
40 | ButterKnife.bind(this);
41 |
42 | initDependencyInjector();
43 | initializePresenter();
44 | }
45 |
46 | private void initDependencyInjector() {
47 | int avengerId = getIntent().getIntExtra(EXTRA_CHARACTER_ID, -1);
48 |
49 | DaggerAvengerInformationComponent.builder()
50 | .activityModule(new ActivityModule(this))
51 | .appComponent(((AvengersApplication) getApplication()).getAppComponent())
52 | .avengerInformationModule(new AvengerInformationModule(avengerId))
53 | .build().inject(this);
54 | }
55 |
56 | private void initializePresenter() {
57 | int characterId = getIntent().getIntExtra(EXTRA_CHARACTER_ID, -1);
58 | String collectionType = getIntent().getStringExtra(EXTRA_COLLECTION_TYPE);
59 | mCollectionPresenter.attachView(this);
60 | mCollectionPresenter.initialisePresenters(collectionType, characterId);
61 | mCollectionPresenter.onCreate();
62 | }
63 |
64 | @Override
65 | public void showLoadingIndicator() {
66 | mLoadingIndicator.setVisibility(View.VISIBLE);
67 | }
68 |
69 | @Override
70 | public void hideLoadingIndicator() {
71 | mLoadingIndicator.setVisibility(View.GONE);
72 | }
73 |
74 | @Override
75 | public void showItems(List items) {
76 | mCollectionRecycler.setAdapter(new CollectionAdapter(items));
77 | }
78 |
79 | public static void start(Context context, int characterId, String type) {
80 | Intent collectionIntent = new Intent(context, CollectionActivity.class);
81 | collectionIntent.putExtra(EXTRA_CHARACTER_ID, characterId);
82 | collectionIntent.putExtra(EXTRA_COLLECTION_TYPE, type);
83 | context.startActivity(collectionIntent);
84 | }
85 |
86 |
87 | private class CollectionAdapter extends RecyclerView.Adapter {
88 | private List mCollectionItems;
89 |
90 | public CollectionAdapter(List collectionItems) {
91 | mCollectionItems = collectionItems;
92 | }
93 |
94 | @Override
95 | public CollectionItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
96 | View rootView = LayoutInflater.from(parent.getContext()).inflate(
97 | R.layout.item_comic, parent, false);
98 |
99 | return new CollectionItemViewHolder(rootView);
100 | }
101 |
102 | @Override
103 | public void onBindViewHolder(CollectionItemViewHolder holder, int position) {
104 | holder.bindItem(mCollectionItems.get(position));
105 | }
106 |
107 | @Override public int getItemCount() {
108 | return mCollectionItems.size();
109 | }
110 | }
111 |
112 | class CollectionItemViewHolder extends RecyclerView.ViewHolder {
113 | @Bind(R.id.item_title) TextView itemTitleTextView;
114 | @Bind(R.id.item_image) ImageView itemImageView;
115 | @Bind(R.id.item_text) TextView itemTextTextView;
116 |
117 | public CollectionItemViewHolder(View itemView) {
118 | super(itemView);
119 | ButterKnife.bind(this, itemView);
120 | }
121 |
122 | public void bindItem(CollectionItem collectionItem) {
123 | itemTitleTextView.setText(collectionItem.getTitle());
124 | itemTextTextView.setText(collectionItem.getDescription());
125 |
126 | if (collectionItem.getThumbnail() != null) {
127 | Glide.with(CollectionActivity.this)
128 | .load(collectionItem.getThumbnail().getImageUrl())
129 | .error(R.drawable.error_placeholder)
130 | .into(itemImageView);
131 |
132 | } else {
133 | itemTextTextView.setVisibility(View.GONE);
134 | }
135 | }
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/app/src/main/java/saulmm/avengers/views/adapter/AvengersListAdapter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | */
6 | package saulmm.avengers.views.adapter;
7 |
8 | import android.content.Context;
9 | import android.graphics.drawable.ColorDrawable;
10 | import android.support.v7.widget.RecyclerView;
11 | import android.view.LayoutInflater;
12 | import android.view.View;
13 | import android.view.ViewGroup;
14 | import android.widget.ImageView;
15 | import android.widget.TextView;
16 | import butterknife.Bind;
17 | import butterknife.BindColor;
18 | import butterknife.ButterKnife;
19 | import com.bumptech.glide.Glide;
20 | import java.util.List;
21 | import saulmm.avengers.R;
22 | import saulmm.avengers.entities.MarvelCharacter;
23 | import saulmm.avengers.utils.Utils;
24 | import saulmm.avengers.views.RecyclerClickListener;
25 |
26 | public class AvengersListAdapter extends RecyclerView.Adapter {
27 | private final String NOT_AVAILABLE_URL = "http://i.annihil.us/u/prod/marvel/i/mg/b/40/image_not_available.jpg";
28 | private final RecyclerClickListener mRecyclerListener;
29 | private final List mCharacters;
30 |
31 | private Context mContext;
32 |
33 | public AvengersListAdapter(List avengers, Context context,
34 | RecyclerClickListener recyclerClickListener) {
35 |
36 | mCharacters = avengers;
37 | mContext = context;
38 | mRecyclerListener = recyclerClickListener;
39 | }
40 |
41 | @Override
42 | public CharacterViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
43 | View rootView = LayoutInflater.from(mContext).inflate(
44 | R.layout.item_character, parent, false);
45 |
46 | return new CharacterViewHolder(rootView, mRecyclerListener);
47 | }
48 |
49 | @Override
50 | public void onBindViewHolder(CharacterViewHolder holder, int position) {
51 | holder.bindAvenger(mCharacters.get(position));
52 | }
53 |
54 | @Override
55 | public int getItemCount() {
56 | return mCharacters.size();
57 | }
58 |
59 | public class CharacterViewHolder extends RecyclerView.ViewHolder {
60 | @Bind(R.id.item_avenger_title) TextView avengerTitleTextView;
61 | @Bind(R.id.item_avenger_thumb) ImageView avengerThumbImageView;
62 | @Bind(R.id.item_avenger_placeholder_name) TextView avengerPlaceholderTitleTextView;
63 | @BindColor(R.color.brand_primary) int mColorPrimary;
64 |
65 | public CharacterViewHolder(View itemView, final RecyclerClickListener recyclerClickListener) {
66 | super(itemView);
67 | ButterKnife.bind(this, itemView);
68 | bindListener(itemView, recyclerClickListener);
69 | }
70 |
71 | public void bindAvenger(MarvelCharacter character) {
72 | avengerTitleTextView.setText(character.getName());
73 | avengerTitleTextView.setTransitionName(Utils.getListTransitionName(getPosition()));
74 |
75 | if (character.getImageUrl().equals(NOT_AVAILABLE_URL)) {
76 | ColorDrawable colorDrawable = new ColorDrawable(mColorPrimary);
77 | avengerThumbImageView.setDrawingCacheEnabled(true);
78 | avengerThumbImageView.setImageDrawable(colorDrawable);
79 |
80 | } else {
81 | Glide.with(mContext)
82 | .load(character.getImageUrl())
83 | .crossFade()
84 | .into(avengerThumbImageView);
85 | }
86 | }
87 |
88 | private void bindListener(View itemView, final RecyclerClickListener recyclerClickListener) {
89 | itemView.setOnClickListener(v ->
90 | recyclerClickListener.onElementClick(getPosition(), avengerTitleTextView, avengerThumbImageView));
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/app/src/main/java/saulmm/avengers/views/utils/AnimUtils.java:
--------------------------------------------------------------------------------
1 | package saulmm.avengers.views.utils;
2 |
3 | import android.animation.Animator;
4 | import android.support.annotation.Nullable;
5 | import android.view.View;
6 | import android.view.ViewAnimationUtils;
7 |
8 | public class AnimUtils {
9 |
10 | public static final int REVEAL_DURATION = 500;
11 |
12 | public static void showRevealEffect(final View v, int centerX, int centerY,
13 | @Nullable Animator.AnimatorListener lis) {
14 |
15 | v.setVisibility(View.VISIBLE);
16 |
17 | int finalRadius = Math.max(v.getWidth(), v.getHeight());
18 |
19 | Animator anim = ViewAnimationUtils.createCircularReveal(
20 | v, centerX, centerY, 0, finalRadius);
21 |
22 | anim.setDuration(REVEAL_DURATION);
23 |
24 | if (lis != null)
25 | anim.addListener(lis);
26 |
27 | anim.start();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/java/saulmm/avengers/views/utils/TransitionListenerAdapter.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | *
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to in writing, software
9 | * distributed under the License is distributed on an "AS IS" BASIS,
10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | * See the License for the specific language governing permissions and
12 | * limitations under the License.
13 | */
14 | package saulmm.avengers.views.utils;
15 |
16 | import android.transition.Transition;
17 |
18 |
19 | public class TransitionListenerAdapter implements Transition.TransitionListener {
20 |
21 | @Override
22 | public void onTransitionStart(Transition transition) {
23 |
24 | }
25 |
26 | @Override
27 | public void onTransitionEnd(Transition transition) {
28 |
29 | }
30 |
31 | @Override
32 | public void onTransitionCancel(Transition transition) {
33 |
34 | }
35 |
36 | @Override
37 | public void onTransitionPause(Transition transition) {
38 |
39 | }
40 |
41 | @Override
42 | public void onTransitionResume(Transition transition) {
43 |
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/app/src/main/java/saulmm/avengers/views/views/CustomTextView.java:
--------------------------------------------------------------------------------
1 | package saulmm.avengers.views.views;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Typeface;
6 | import android.util.AttributeSet;
7 | import android.widget.TextView;
8 | import saulmm.avengers.R;
9 |
10 | public class CustomTextView extends TextView {
11 | public CustomTextView(Context context) {
12 | super(context);
13 | }
14 |
15 | public CustomTextView(Context context, AttributeSet attrs) {
16 | super(context, attrs);
17 | init(context, attrs);
18 | }
19 |
20 | public CustomTextView(Context context, AttributeSet attrs, int defStyleAttr) {
21 | super(context, attrs, defStyleAttr);
22 | init(context, attrs);
23 | }
24 |
25 | private void init(Context context, AttributeSet attrs) {
26 | TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
27 | R.styleable.CustomTextView, 0, 0);
28 |
29 | String typefaceName = a.getString(R.styleable.CustomTextView_typeface);
30 |
31 | if (typefaceName != null && !typefaceName.equals("")) {
32 | setTypeface(Typeface.createFromAsset(context.getAssets(), typefaceName));
33 | }
34 |
35 | a.recycle();
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/src/main/java/saulmm/avengers/views/views/RecyclerInsetsDecoration.java:
--------------------------------------------------------------------------------
1 | package saulmm.avengers.views.views;
2 |
3 | import android.content.Context;
4 | import android.graphics.Rect;
5 | import android.support.v7.widget.RecyclerView;
6 | import android.view.View;
7 |
8 | import saulmm.avengers.R;
9 |
10 |
11 | /**
12 | * ItemDecoration implementation that applies an inset margin
13 | * around each child of the RecyclerView. The inset value is controlled
14 | * by a dimension resource.
15 | *
16 | * by Dave Smith at: https://github.com/devunwired/recyclerview-playground
17 | */
18 | public class RecyclerInsetsDecoration extends RecyclerView.ItemDecoration {
19 |
20 | private int mInsets;
21 |
22 | public RecyclerInsetsDecoration(Context context) {
23 | mInsets = context.getResources().getDimensionPixelSize(R.dimen.insets);
24 | }
25 |
26 | @Override
27 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
28 |
29 | //We can supply forced insets for each item view here in the Rect
30 | super.getItemOffsets(outRect, view, parent, state);
31 | outRect.set(mInsets, mInsets, mInsets, mInsets);
32 | }
33 | }
--------------------------------------------------------------------------------
/app/src/main/java/saulmm/avengers/views/views/SimpleDivider.java:
--------------------------------------------------------------------------------
1 | package saulmm.avengers.views.views;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.drawable.Drawable;
6 | import android.support.v7.widget.RecyclerView;
7 | import android.view.View;
8 |
9 | import saulmm.avengers.R;
10 |
11 | public class SimpleDivider extends RecyclerView.ItemDecoration {
12 |
13 | private final int mPadding;
14 |
15 | private Drawable mDivider;
16 |
17 | public SimpleDivider(Context context) {
18 |
19 | mDivider = context.getDrawable(R.drawable.divider);
20 | mPadding = context.getResources().getDimensionPixelOffset(R.dimen.spacing_normal);
21 | }
22 |
23 | @Override
24 | public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
25 |
26 | super.onDrawOver(c, parent, state);
27 |
28 | int left = mPadding;
29 | int right = parent.getWidth() - mPadding;
30 |
31 | int childCount = parent.getChildCount();
32 |
33 | for (int i = 0; i < childCount; i++) {
34 |
35 | View child = parent.getChildAt(i);
36 |
37 | RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
38 | .getLayoutParams();
39 |
40 | int top = child.getBottom() + params.bottomMargin;
41 | int bottom = top + mDivider.getIntrinsicHeight();
42 |
43 | mDivider.setBounds(left, top, right, bottom);
44 | mDivider.draw(c);
45 | }
46 | }
47 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_action_favorite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saulmm/Avengers/2b02f19d5a0ca3e36b5370f545036b30fd62a114/app/src/main/res/drawable-hdpi/ic_action_favorite.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_action_favorite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saulmm/Avengers/2b02f19d5a0ca3e36b5370f545036b30fd62a114/app/src/main/res/drawable-mdpi/ic_action_favorite.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/avengers_header.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saulmm/Avengers/2b02f19d5a0ca3e36b5370f545036b30fd62a114/app/src/main/res/drawable-nodpi/avengers_header.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/error_placeholder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saulmm/Avengers/2b02f19d5a0ca3e36b5370f545036b30fd62a114/app/src/main/res/drawable-nodpi/error_placeholder.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/header_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saulmm/Avengers/2b02f19d5a0ca3e36b5370f545036b30fd62a114/app/src/main/res/drawable-nodpi/header_background.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/marvel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saulmm/Avengers/2b02f19d5a0ca3e36b5370f545036b30fd62a114/app/src/main/res/drawable-nodpi/marvel.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_action_favorite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saulmm/Avengers/2b02f19d5a0ca3e36b5370f545036b30fd62a114/app/src/main/res/drawable-xhdpi/ic_action_favorite.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_action_favorite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saulmm/Avengers/2b02f19d5a0ca3e36b5370f545036b30fd62a114/app/src/main/res/drawable-xxhdpi/ic_action_favorite.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_action_favorite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saulmm/Avengers/2b02f19d5a0ca3e36b5370f545036b30fd62a114/app/src/main/res/drawable-xxxhdpi/ic_action_favorite.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/divider.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/gradient_dark.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/gradient_dark_up.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_more.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_avenger_detail.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
26 |
27 |
35 |
36 |
44 |
45 |
58 |
59 |
60 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
83 |
84 |
85 |
86 |
94 |
95 |
102 |
103 |
111 |
112 |
123 |
124 |
130 |
131 |
140 |
141 |
142 |
153 |
154 |
160 |
161 |
170 |
171 |
182 |
183 |
189 |
190 |
199 |
200 |
201 |
212 |
213 |
219 |
220 |
229 |
230 |
231 |
232 |
233 |
234 |
240 |
241 |
247 |
248 |
257 |
258 |
270 |
271 |
279 |
280 |
281 |
282 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_avengers_list.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
15 |
16 |
22 |
23 |
27 |
28 |
34 |
35 |
41 |
42 |
49 |
50 |
59 |
60 |
61 |
62 |
63 |
64 |
71 |
72 |
80 |
81 |
90 |
91 |
92 |
93 |
102 |
103 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_character_collection.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
22 |
23 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_character.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
16 |
17 |
26 |
27 |
38 |
39 |
40 |
54 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_comic.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
14 |
15 |
22 |
23 |
30 |
31 |
38 |
39 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/view_error.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
19 |
20 |
29 |
30 |
37 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/view_filter_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/view_loading_characters.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saulmm/Avengers/2b02f19d5a0ca3e36b5370f545036b30fd62a114/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saulmm/Avengers/2b02f19d5a0ca3e36b5370f545036b30fd62a114/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saulmm/Avengers/2b02f19d5a0ca3e36b5370f545036b30fd62a114/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saulmm/Avengers/2b02f19d5a0ca3e36b5370f545036b30fd62a114/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #28292C
5 | #28292C
6 | #ff2900
7 | #ff441a
8 |
9 | #3B0000
10 |
11 | #FFFFFF
12 | #e8e8e8
13 | #bcbcbc
14 | #ff808080
15 | #ff2c2c2c
16 |
17 | #202020
18 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 56dp
3 | 112dp
4 | 168dp
5 |
6 |
7 | 32dp
8 | 16dp
9 | 8dp
10 | 4dp
11 |
12 |
13 | 12dp
14 | 8dp
15 | 4dp
16 | 2dp
17 |
18 | 48dp
19 | 400dp
20 |
21 | 2dp
22 | 150dp
23 | 56dp
24 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/values/integer.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 250
4 | 500
5 | 750
6 | 1000
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/string-arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - Select year
5 | - 2015
6 | - 2014
7 | - 2013
8 | - 2012
9 | - 2011
10 | - 2010
11 | - 2009
12 | - 2008
13 | - 2007
14 | - 2006
15 | - 2005
16 | - 2004
17 | - 2003
18 | - 2002
19 | - 2001
20 | - 2000
21 | - 1999
22 | - 1998
23 | - 1997
24 | - 1996
25 | - 1995
26 | - 1994
27 | - 1993
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Amazing heroes of Marvel
3 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi imperdiet condimentum libero, sit amet tincidunt felis sollicitudin non. Suspendisse malesuada augue ut nibh pharetra finibus. Quisque ut metus id nulla tincidunt faucibus. Morbi eget nisi finibus, maximus est vel, malesuada lacus. Curabitur tincidunt, velit eget venenatis rhoncus, lectus sem commodo erat, et ultrices metus leo eget urna. Integer semper erat ac erat tempus, tincidunt posuere lacus placerat. Morbi lobortis odio tortor, eu euismod leo pretium et. Fusce tempor felis condimentum, elementum justo sed, commodo tellus. Vivamus et tellus tincidunt, placerat velit vitae, fringilla arcu. Nullam luctus varius iaculis.
4 |
5 | Something happened loading characters
6 | Image not found
7 | Can\'t connect with marvel API
8 | Marvel API Server has failed
9 | Loading more characters
10 | No information available
11 | Accept
12 | Cancel
13 | Filter
14 | Error
15 | Comics
16 | Events
17 | Series
18 | Stories
19 | Avenger information
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings_character_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | An error has occurred loading characters
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings_common.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Try again
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
16 |
17 |
22 |
23 |
26 |
27 |
28 |
29 |
32 |
33 |
40 |
41 |
44 |
45 |
50 |
51 |
54 |
55 |
59 |
60 |
63 |
64 |
70 |
71 |
78 |
79 |
84 |
85 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
15 |
18 |
--------------------------------------------------------------------------------
/app/src/test/java/DetailPresenterTest.java:
--------------------------------------------------------------------------------
1 | import org.junit.Before;
2 | import org.junit.Test;
3 | import org.mockito.Mock;
4 | import org.mockito.MockitoAnnotations;
5 |
6 |
7 | import rx.Observable;
8 | import saulmm.avengers.CharacterDetailsUsecase;
9 | import saulmm.avengers.entities.MarvelCharacter;
10 | import saulmm.avengers.mvp.presenters.CharacterDetailPresenter;
11 | import saulmm.avengers.mvp.views.CharacterDetailView;
12 |
13 | import static org.mockito.Mockito.*;
14 |
15 | public class DetailPresenterTest {
16 | @Mock CharacterDetailView mockDetailView;
17 | @Mock CharacterDetailsUsecase mockDetailsUsecase;
18 | final int FAKE_CHARACTER_ID = 0;
19 |
20 | @Before public void setUp() throws Exception {
21 | MockitoAnnotations.initMocks(this);
22 | }
23 |
24 | @Test public void testThatPresenterAsksForCharacterDetails() throws Exception {
25 | CharacterDetailPresenter characterListPresenter = givenACharacterDetailPresenter();
26 |
27 | when(mockDetailsUsecase.execute()).thenReturn(getFakeObservableCharacter());
28 | characterListPresenter.askForCharacterDetails();
29 |
30 | verify(mockDetailsUsecase, times(1)).execute();
31 | }
32 |
33 | @Test public void testThatPresentersOpensComicsView() throws Exception {
34 | CharacterDetailPresenter characterDetailPresenter = givenACharacterDetailPresenter();
35 |
36 | characterDetailPresenter.onComicsIndicatorPressed();
37 |
38 | verify(mockDetailView, times(1)).goToCharacterComicsView(FAKE_CHARACTER_ID);
39 | }
40 |
41 | @Test public void testThatPresentersOpensSeriesView() throws Exception {
42 | CharacterDetailPresenter characterDetailPresenter = givenACharacterDetailPresenter();
43 |
44 | characterDetailPresenter.onSeriesIndicatorPressed();
45 |
46 | verify(mockDetailView, times(1)).goToCharacterSeriesView(FAKE_CHARACTER_ID);
47 | }
48 |
49 | @Test public void testThatPresentersOpensStoriesView() throws Exception {
50 | CharacterDetailPresenter characterDetailPresenter = givenACharacterDetailPresenter();
51 |
52 | characterDetailPresenter.onStoriesIndicatorPressed();
53 |
54 | verify(mockDetailView, times(1)).goToCharacterStoriesView(FAKE_CHARACTER_ID);
55 | }
56 |
57 | @Test public void testThatPresentersOpensEventsView() throws Exception {
58 | CharacterDetailPresenter characterDetailPresenter = givenACharacterDetailPresenter();
59 |
60 | characterDetailPresenter.onEventIndicatorPressed();
61 |
62 | verify(mockDetailView, times(1)).goToCharacterEventsView(FAKE_CHARACTER_ID);
63 | }
64 |
65 | private CharacterDetailPresenter givenACharacterDetailPresenter(){
66 | CharacterDetailPresenter characterDetailPresenter = new CharacterDetailPresenter(mockDetailsUsecase);
67 | characterDetailPresenter.attachView(mockDetailView);
68 | return characterDetailPresenter;
69 | }
70 |
71 | private Observable getFakeObservableCharacter() {
72 | return Observable.just(new MarvelCharacter("", -1));
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/app/src/test/java/ListPresenterTest.java:
--------------------------------------------------------------------------------
1 | import java.util.ArrayList;
2 | import java.util.List;
3 |
4 | import org.junit.Before;
5 | import org.junit.Test;
6 | import org.mockito.Mock;
7 | import org.mockito.MockitoAnnotations;
8 |
9 | import rx.Observable;
10 | import saulmm.avengers.GetCharactersUsecase;
11 | import saulmm.avengers.entities.MarvelCharacter;
12 | import saulmm.avengers.mvp.presenters.CharacterListPresenter;
13 | import saulmm.avengers.mvp.views.CharacterListView;
14 |
15 | import static org.mockito.Mockito.only;
16 | import static org.mockito.Mockito.times;
17 | import static org.mockito.Mockito.verify;
18 | import static org.mockito.Mockito.when;
19 |
20 | public class ListPresenterTest {
21 |
22 | @Mock CharacterListView mockCharacterListView;
23 | @Mock GetCharactersUsecase mockGetCharacterUsecase;
24 |
25 | @Before public void setUp() throws Exception {
26 | MockitoAnnotations.initMocks(this);
27 | }
28 |
29 | @Test public void testThatCharactersArePassedToTheView() throws Exception {
30 | CharacterListPresenter listPresenter = givenAListPresenter();
31 | ArrayList fakeCharacterList = givenAFakeCharacterList();
32 |
33 | listPresenter.onCharactersReceived(fakeCharacterList);
34 |
35 | verify(mockCharacterListView, times(1))
36 | .bindCharacterList(fakeCharacterList);
37 | }
38 |
39 | @Test public void testThatPresenterRequestCharacters() throws Exception {
40 | CharacterListPresenter listPresenter = givenAListPresenter();
41 |
42 | when(mockGetCharacterUsecase.execute()).thenReturn(getFakeObservableCharacterList());
43 | listPresenter.askForCharacters();
44 |
45 | verify(mockGetCharacterUsecase, times(1)).execute();
46 | }
47 |
48 | @Test public void testThatPresenterShowsErrorWhenLoadingCharacters() throws Exception {
49 | CharacterListPresenter listPresenter = givenAListPresenter();
50 |
51 | when(mockGetCharacterUsecase.execute()).thenReturn(Observable.error(new Exception()));
52 | listPresenter.askForCharacters();
53 |
54 | verify(mockCharacterListView, times(1)).showUknownErrorMessage();
55 | }
56 |
57 | @Test public void testThatPresenterShowsALightErrorLoadingMoreCharacters() throws Exception {
58 | CharacterListPresenter listPresenter = givenAListPresenter();
59 |
60 | when(mockGetCharacterUsecase.execute()).thenReturn(Observable.error(new Exception()));
61 | listPresenter.askForNewCharacters();
62 |
63 | verify(mockCharacterListView, times(1)).showLightError();
64 | }
65 |
66 | @Test public void testThatPresenterRequestMoreCharacters() throws Exception {
67 | CharacterListPresenter listPresenter = givenAListPresenter();
68 |
69 | when(mockGetCharacterUsecase.execute()).thenReturn(getFakeObservableCharacterList());
70 | listPresenter.askForNewCharacters();
71 |
72 | verify(mockGetCharacterUsecase, only()).execute();
73 | }
74 |
75 | private ArrayList givenAFakeCharacterList() {
76 | ArrayList marvelCharacters = new ArrayList<>();
77 | marvelCharacters.add(new MarvelCharacter("", -1));
78 | marvelCharacters.add(new MarvelCharacter("", -1));
79 | return marvelCharacters;
80 | }
81 |
82 | private CharacterListPresenter givenAListPresenter() {
83 | CharacterListPresenter listPresenter = new CharacterListPresenter(mockGetCharacterUsecase);
84 | listPresenter.attachView(mockCharacterListView);
85 | return listPresenter;
86 | }
87 |
88 | private Observable> getFakeObservableCharacterList() {
89 | List test = new ArrayList();
90 | return Observable.just(test);
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/art/cap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saulmm/Avengers/2b02f19d5a0ca3e36b5370f545036b30fd62a114/art/cap.png
--------------------------------------------------------------------------------
/art/screen_detail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saulmm/Avengers/2b02f19d5a0ca3e36b5370f545036b30fd62a114/art/screen_detail.png
--------------------------------------------------------------------------------
/art/screen_main.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saulmm/Avengers/2b02f19d5a0ca3e36b5370f545036b30fd62a114/art/screen_main.png
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | ext {
2 | configuration = [
3 | package : "saulmm.avengers",
4 | buildToolsVersion : "23.0.2",
5 | compileVersion : 23,
6 | minSdk : 21,
7 | targetSdk : 23,
8 | version_code : 4,
9 | version_name : "0.4",
10 | ]
11 |
12 | libraries = [
13 | supportVersion : "23.3.0",
14 | dagger : "2.0",
15 | rxjava : "1.0.0",
16 | rxandroid : "0.24.0",
17 | retrofit : "2.0.0-beta2",
18 | butterknife : "7.0.1",
19 | recycler_animators : "2.0.0",
20 | javax_annotation : "10.0-b28",
21 | glide : "3.6.0",
22 | circleimageview : "1.3.0",
23 | loggin_interceptor : "2.6.0",
24 | okhttp : "2.7.1"
25 | ]
26 |
27 | testingLibraries = [
28 | junit : "4.11",
29 | mockito : "1.+",
30 | espresso : "2.2.1",
31 | espressoRunner : "0.4.1",
32 | espressoRules : "0.4.1"
33 | ]
34 | }
35 |
36 | buildscript {
37 | repositories {
38 | jcenter()
39 | }
40 |
41 | dependencies {
42 | classpath 'com.android.tools.build:gradle:2.1.0-rc1'
43 | classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
44 | classpath 'me.tatarka:gradle-retrolambda:3.3.0-beta3'
45 | classpath 'com.android.databinding:dataBinder:1.0-rc4'
46 | }
47 | }
48 |
49 | allprojects {
50 | repositories {
51 | jcenter()
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/domain/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/domain/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'java'
2 |
3 | def libs = rootProject.ext.libraries;
4 |
5 | dependencies {
6 | compile fileTree(dir: 'libs', include: ['*.jar'])
7 | compile project(':model')
8 |
9 | //test dependencies
10 | testCompile 'junit:junit:4.11'
11 | testCompile 'org.mockito:mockito-core:1.9.5'
12 | }
--------------------------------------------------------------------------------
/domain/src/main/java/saulmm/avengers/CharacterDetailsUsecase.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | */
6 | package saulmm.avengers;
7 |
8 | import javax.inject.Inject;
9 | import javax.inject.Named;
10 |
11 | import rx.Observable;
12 | import rx.Scheduler;
13 | import saulmm.avengers.entities.MarvelCharacter;
14 | import saulmm.avengers.repository.CharacterRepository;
15 |
16 | public class CharacterDetailsUsecase extends Usecase {
17 | private final CharacterRepository mRepository;
18 | private final Scheduler mUiThread;
19 | private final Scheduler mExecutorThread;
20 | private int mCharacterId;
21 |
22 | @Inject public CharacterDetailsUsecase(int characterId,
23 | CharacterRepository repository,
24 | @Named("ui_thread") Scheduler uiThread,
25 | @Named("executor_thread") Scheduler executorThread) {
26 |
27 | mCharacterId = characterId;
28 | mRepository = repository;
29 | mUiThread = uiThread;
30 | mExecutorThread = executorThread;
31 | }
32 |
33 | @Override
34 | public Observable buildObservable() {
35 | return mRepository.getCharacter(mCharacterId)
36 | .observeOn(mUiThread)
37 | .subscribeOn(mExecutorThread);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/domain/src/main/java/saulmm/avengers/GetCharactersUsecase.java:
--------------------------------------------------------------------------------
1 | package saulmm.avengers;
2 |
3 | import java.util.List;
4 | import javax.inject.Inject;
5 | import javax.inject.Named;
6 | import rx.Observable;
7 | import rx.Scheduler;
8 | import rx.functions.Action1;
9 | import saulmm.avengers.entities.MarvelCharacter;
10 | import saulmm.avengers.repository.CharacterRepository;
11 |
12 | public class GetCharactersUsecase extends Usecase> {
13 | public final static int DEFAULT_CHARACTERS_LIMIT = 20;
14 | private int mCharactersLimit = DEFAULT_CHARACTERS_LIMIT;
15 | private final CharacterRepository mRepository;
16 | private int mCurrentOffset;
17 |
18 | private final Scheduler mUiThread;
19 | private final Scheduler mExecutorThread;
20 |
21 | @Inject public GetCharactersUsecase(CharacterRepository repository,
22 | @Named("ui_thread") Scheduler uiThread,
23 | @Named("executor_thread") Scheduler executorThread) {
24 |
25 | mRepository = repository;
26 | mUiThread = uiThread;
27 | mExecutorThread = executorThread;
28 | }
29 |
30 | @Override
31 | public Observable> buildObservable() {
32 | return mRepository.getCharacters(mCurrentOffset)
33 | .observeOn(mUiThread)
34 | .subscribeOn(mExecutorThread)
35 | .doOnError(new Action1() {
36 | @Override
37 | public void call(Throwable throwable) {
38 | mCurrentOffset -= mCharactersLimit;
39 | }
40 | });
41 | }
42 |
43 | @Override
44 | public Observable> execute() {
45 | increaseOffset();
46 | return super.execute();
47 | }
48 |
49 | public void increaseOffset() {
50 | mCurrentOffset += mCharactersLimit;
51 | }
52 |
53 | public int getCurrentOffset() {
54 | return mCurrentOffset;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/domain/src/main/java/saulmm/avengers/GetCollectionUsecase.java:
--------------------------------------------------------------------------------
1 | package saulmm.avengers;
2 |
3 | import java.util.List;
4 | import javax.inject.Inject;
5 | import javax.inject.Named;
6 |
7 | import rx.Observable;
8 | import rx.Scheduler;
9 | import saulmm.avengers.entities.CollectionItem;
10 | import saulmm.avengers.repository.CharacterRepository;
11 |
12 | public class GetCollectionUsecase extends Usecase> {
13 | private final CharacterRepository mRepository;
14 | private final int mCharacterId;
15 | private final Scheduler mUIThread;
16 | private final Scheduler mExecutorThread;
17 | private String mType;
18 |
19 | @Inject public GetCollectionUsecase(int characterId,
20 | CharacterRepository repository,
21 | @Named("ui_thread") Scheduler uiThread,
22 | @Named("executor_thread") Scheduler executorThread) {
23 |
24 | mRepository = repository;
25 | mCharacterId = characterId;
26 | mUIThread = uiThread;
27 | mExecutorThread = executorThread;
28 | }
29 |
30 | public void setType(String type) {
31 | if (!type.equals(CollectionItem.COMICS) && !type.equals(CollectionItem.EVENTS) && !type.equals(
32 | CollectionItem.SERIES) && !type.equals(CollectionItem.STORIES))
33 |
34 | throw new IllegalArgumentException("Collection type must be events|series|comics|stories");
35 |
36 | mType = type;
37 | }
38 |
39 | @Override
40 | public Observable> buildObservable() {
41 | return mRepository.getCharacterCollection(mCharacterId, mType)
42 | .observeOn(mUIThread).subscribeOn(mExecutorThread);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/domain/src/main/java/saulmm/avengers/Usecase.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | */
6 | package saulmm.avengers;
7 |
8 | import javax.inject.Inject;
9 | import javax.inject.Named;
10 |
11 | import rx.Observable;
12 | import rx.Scheduler;
13 | import rx.Subscriber;
14 |
15 | public abstract class Usecase {
16 | public abstract Observable buildObservable();
17 |
18 | public Observable execute() {
19 | return buildObservable();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/domain/src/test/java/GetCharacterDetailsTest.java:
--------------------------------------------------------------------------------
1 | import org.junit.Before;
2 | import org.junit.Test;
3 | import org.mockito.Mock;
4 | import org.mockito.Mockito;
5 | import org.mockito.MockitoAnnotations;
6 |
7 |
8 | import rx.Observable;
9 | import rx.Scheduler;
10 | import saulmm.avengers.CharacterDetailsUsecase;
11 | import saulmm.avengers.Usecase;
12 | import saulmm.avengers.entities.MarvelCharacter;
13 | import saulmm.avengers.repository.CharacterRepository;
14 |
15 | import static org.hamcrest.CoreMatchers.instanceOf;
16 | import static org.hamcrest.MatcherAssert.assertThat;
17 | import static org.mockito.internal.verification.VerificationModeFactory.only;
18 |
19 | public class GetCharacterDetailsTest {
20 | private final static int FAKE_CHARACTER_ID = 69;
21 | @Mock CharacterRepository mRepository;
22 | @Mock Scheduler mockScheduler;
23 |
24 | @Before public void setUp() {
25 | MockitoAnnotations.initMocks(this);
26 | }
27 |
28 | @Test public void testThatDetailUsecaseIsCalledOnce() throws Exception {
29 | CharacterDetailsUsecase detailsUsecase = givenACharacterUsecase();
30 |
31 | Mockito.when(mRepository.getCharacter(FAKE_CHARACTER_ID)).thenReturn(getFakeCharacterObservable());
32 | detailsUsecase.execute();
33 |
34 | Mockito.verify(mRepository, only()).getCharacter(FAKE_CHARACTER_ID);
35 | }
36 |
37 | @Test public void testThatAConcreteUsecaseImplementsAnUsecase() throws Exception {
38 | CharacterDetailsUsecase detailsUsecase = givenACharacterUsecase();
39 |
40 | assertThat(detailsUsecase, instanceOf(Usecase.class));
41 | }
42 |
43 | private CharacterDetailsUsecase givenACharacterUsecase() {
44 | return new CharacterDetailsUsecase(FAKE_CHARACTER_ID, mRepository);
45 | }
46 |
47 | private Observable getFakeCharacterObservable() {
48 | return Observable.just(new MarvelCharacter("", -1));
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/domain/src/test/java/GetCharactersUsecaseTest.java:
--------------------------------------------------------------------------------
1 | import java.util.ArrayList;
2 | import java.util.List;
3 | import org.junit.Before;
4 | import org.junit.Test;
5 | import org.mockito.Mock;
6 | import org.mockito.Mockito;
7 | import org.mockito.MockitoAnnotations;
8 | import rx.Observable;
9 | import rx.Scheduler;
10 | import rx.schedulers.Schedulers;
11 | import saulmm.avengers.GetCharactersUsecase;
12 | import saulmm.avengers.entities.MarvelCharacter;
13 | import saulmm.avengers.repository.CharacterRepository;
14 |
15 | import static org.hamcrest.CoreMatchers.instanceOf;
16 | import static org.hamcrest.CoreMatchers.is;
17 | import static org.hamcrest.MatcherAssert.assertThat;
18 | import static org.mockito.Matchers.any;
19 | import static org.mockito.Matchers.anyInt;
20 | import static org.mockito.Mockito.mock;
21 | import static org.mockito.Mockito.only;
22 | import static org.mockito.Mockito.when;
23 | import static saulmm.avengers.GetCharactersUsecase.DEFAULT_CHARACTERS_LIMIT;
24 |
25 | public class GetCharactersUsecaseTest {
26 | @Mock CharacterRepository mockRepository;
27 | @Mock Scheduler mockUiScheduler;
28 | @Mock Scheduler mockExecutorScheduler;
29 |
30 | @Before public void setUp() throws Exception {
31 | MockitoAnnotations.initMocks(this);
32 | }
33 |
34 | @Test public void testThatAConcreteUsecaseImplementsAnUsecase() {
35 | GetCharactersUsecase charactersUsecase = givenACharactersUsecase();
36 |
37 | assertThat(charactersUsecase, instanceOf(GetCharactersUsecase.class));
38 | }
39 |
40 | @Test public void testThatCharactersUsecaseIncrementsOffset() throws Exception {
41 | GetCharactersUsecase charactersUsecase = givenACharactersUsecase();
42 | when(mockRepository.getCharacters(anyInt())).thenReturn(getFakeObservableCharacterList());
43 |
44 | charactersUsecase.execute();
45 | charactersUsecase.execute();
46 | charactersUsecase.execute();
47 |
48 | assertThat(charactersUsecase.getCurrentOffset(), is(DEFAULT_CHARACTERS_LIMIT * 3));
49 | }
50 |
51 | @Test public void testThatCharactersUsecaseWithOffsetIsCalledOnce() throws Exception {
52 | GetCharactersUsecase charactersUsecase = givenACharactersUsecase();
53 | when(mockRepository.getCharacters(anyInt())).thenReturn(getFakeObservableCharacterList());
54 |
55 | charactersUsecase.execute();
56 |
57 | Mockito.verify(mockRepository, only()).getCharacters(anyInt());
58 | }
59 |
60 | private GetCharactersUsecase givenACharactersUsecase() {
61 | return new GetCharactersUsecase(mockRepository, mockUiScheduler, mockExecutorScheduler);
62 | }
63 |
64 | private Observable> getFakeObservableCharacterList() {
65 | List test = new ArrayList();
66 | return Observable.just(test);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Marvel API
2 | marvel_public_key = 74129ef99c9fd5f7692608f17abb88f9
3 | marvel_private_key = 281eb4f077e191f7863a11620fa1865f2940ebeb
--------------------------------------------------------------------------------
/model/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/model/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'java'
2 |
3 | def libs = rootProject.ext.libraries;
4 |
5 | dependencies {
6 | compile fileTree(dir: 'libs', include: ['*.jar'])
7 |
8 | // ReactiveX
9 | compile "io.reactivex:rxjava:${libs.rxjava}"
10 |
11 | // Square
12 | compile "com.squareup.retrofit:retrofit:${libs.retrofit}"
13 | compile "com.squareup.retrofit:converter-gson:${libs.retrofit}"
14 | compile "com.squareup.retrofit:adapter-rxjava:${libs.retrofit}"
15 |
16 | compile "com.google.dagger:dagger:${libs.dagger}"
17 | compile "com.squareup.okhttp:logging-interceptor:${libs.loggin_interceptor}"
18 | compile "org.glassfish:javax.annotation:${libs.javax_annotation}"
19 |
20 | //test dependencies
21 | testCompile 'junit:junit:4.11'
22 | testCompile 'org.mockito:mockito-core:1.9.5'
23 |
24 | }
--------------------------------------------------------------------------------
/model/src/main/java/saulmm/avengers/entities/CollectionItem.java:
--------------------------------------------------------------------------------
1 | package saulmm.avengers.entities;
2 |
3 | public class CollectionItem {
4 | public final static String COMICS = "comics";
5 | public final static String SERIES = "series";
6 | public final static String STORIES = "stories";
7 | public final static String EVENTS = "events";
8 |
9 | protected int id;
10 | protected String title;
11 | protected String description;
12 | protected String resourceURI;
13 | protected Thumbnail thumbnail;
14 |
15 | public int getId() {
16 | return id;
17 | }
18 |
19 | public String getTitle() {
20 | return title;
21 | }
22 |
23 | public String getResourceURI() {
24 | return resourceURI;
25 | }
26 |
27 | public Thumbnail getThumbnail() {
28 | return thumbnail;
29 | }
30 |
31 | public String getDescription() {
32 | return description;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/model/src/main/java/saulmm/avengers/entities/Comic.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | */
6 | package saulmm.avengers.entities;
7 |
8 | import java.util.List;
9 |
10 | public class Comic {
11 |
12 | private String id;
13 | private String title;
14 | private String desccription;
15 | private List images;
16 | private List textObjects;
17 | private List dates;
18 | private int pageCount;
19 |
20 | public String getId() {
21 |
22 | return id;
23 | }
24 |
25 | public int getPageCount() {
26 |
27 | return pageCount;
28 | }
29 |
30 | public String getFirstImageUrl () {
31 |
32 | if (!images.isEmpty() && images.get(0) != null)
33 | return images.get(0).getImageUrl();
34 |
35 | return null;
36 | }
37 |
38 | public String getTitle() {
39 |
40 | return title;
41 | }
42 |
43 | public String getDesccription() {
44 |
45 | return desccription;
46 | }
47 |
48 | public String getFirstTextObject () {
49 |
50 | if (!textObjects.isEmpty())
51 | return textObjects.get(0).getText();
52 |
53 | return null;
54 | }
55 |
56 | public List getDates() {
57 |
58 | return dates;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/model/src/main/java/saulmm/avengers/entities/ComicDate.java:
--------------------------------------------------------------------------------
1 | package saulmm.avengers.entities;
2 |
3 | public class ComicDate {
4 |
5 | private String type;
6 | private String date;
7 |
8 | public String getType() {
9 |
10 | return type;
11 | }
12 |
13 | public String getDate() {
14 |
15 | return date;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/model/src/main/java/saulmm/avengers/entities/ComicsCollection.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | */
6 | package saulmm.avengers.entities;
7 |
8 | @SuppressWarnings("unused")
9 | public class ComicsCollection {
10 | private int available;
11 | private String collectionURI;
12 |
13 | public int getAvailable() {
14 | return available;
15 | }
16 |
17 | public String getCollectionURI() {
18 | return collectionURI;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/model/src/main/java/saulmm/avengers/entities/ItemPreview.java:
--------------------------------------------------------------------------------
1 | package saulmm.avengers.entities;
2 |
3 | public class ItemPreview {
4 | private String resourceURI;
5 | private String name;
6 | }
7 |
--------------------------------------------------------------------------------
/model/src/main/java/saulmm/avengers/entities/MarvelCharacter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | */
6 | package saulmm.avengers.entities;
7 |
8 | @SuppressWarnings("unused")
9 | public class MarvelCharacter {
10 | private int id;
11 | private int imageResource;
12 | private String name;
13 | private String description;
14 | private Thumbnail thumbnail;
15 | private String resourceURI;
16 | private ComicsCollection comics;
17 | private ComicsCollection series;
18 | private ComicsCollection stories;
19 | private ComicsCollection events;
20 |
21 |
22 | public MarvelCharacter(String name, int thumb_resource, int id) {
23 | this.name = name;
24 | this.imageResource = thumb_resource;
25 | this.id = id;
26 | }
27 |
28 | public MarvelCharacter(String name, int imageResource) {
29 | this.name = name;
30 | this.imageResource = imageResource;
31 | }
32 |
33 | public int getId() {
34 | return id;
35 | }
36 |
37 | public String getName() {
38 | return name;
39 | }
40 |
41 | public String getDescription() {
42 | return description;
43 | }
44 |
45 | public String getImageUrl() {
46 | return (thumbnail != null) ? thumbnail.getImageUrl() : null;
47 | }
48 |
49 | public int getImageResource() {
50 | return imageResource;
51 | }
52 |
53 | public ComicsCollection getSeries() {
54 | return series;
55 | }
56 |
57 | public ComicsCollection getStories() {
58 | return stories;
59 | }
60 |
61 | public ComicsCollection getEvents() {
62 | return events;
63 | }
64 |
65 | public ComicsCollection getComics() {
66 | return comics;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/model/src/main/java/saulmm/avengers/entities/TextObject.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | */
6 | package saulmm.avengers.entities;
7 |
8 | public class TextObject {
9 |
10 | private String text;
11 |
12 | public String getText() {
13 |
14 | return text;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/model/src/main/java/saulmm/avengers/entities/Thumbnail.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | */
6 | package saulmm.avengers.entities;
7 |
8 | public class Thumbnail {
9 |
10 | private String path;
11 | private String extension;
12 |
13 | public String getImageUrl () {
14 |
15 | return String.format("%s.%s", path, extension);
16 | }
17 | }
--------------------------------------------------------------------------------
/model/src/main/java/saulmm/avengers/repository/CharacterRepository.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | */
6 | package saulmm.avengers.repository;
7 |
8 | import java.util.List;
9 | import rx.Observable;
10 | import saulmm.avengers.entities.CollectionItem;
11 | import saulmm.avengers.entities.MarvelCharacter;
12 |
13 | public interface CharacterRepository {
14 | Observable getCharacter (final int characterId);
15 |
16 | Observable> getCharacters (int offset);
17 |
18 | Observable> getCharacterCollection(int characterId, String type);
19 | }
20 |
--------------------------------------------------------------------------------
/model/src/main/java/saulmm/avengers/repository/wrappers/ComicsWrapper.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | */
6 | package saulmm.avengers.model.repository.wrappers;
7 |
8 | import java.util.ArrayList;
9 | import saulmm.avengers.entities.Comic;
10 |
11 | public class ComicsWrapper {
12 |
13 | private ArrayList mComics;
14 |
15 | public ComicsWrapper(ArrayList comicsList) {
16 |
17 | mComics = comicsList;
18 | }
19 |
20 | public ArrayList getmComics() {
21 |
22 | return mComics;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/model/src/main/java/saulmm/avengers/repository/wrappers/MarvelApiWrapper.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | */
6 | package saulmm.avengers.model.repository.wrappers;
7 |
8 | public class MarvelApiWrapper {
9 |
10 | private String code;
11 | private String status;
12 |
13 | private MarvelDataWrapper data;
14 |
15 | public String getCode() {
16 |
17 | return code;
18 | }
19 |
20 | public String getStatus() {
21 |
22 | return status;
23 | }
24 |
25 | public MarvelDataWrapper getData() {
26 |
27 | return data;
28 | }
29 | }
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/model/src/main/java/saulmm/avengers/repository/wrappers/MarvelDataWrapper.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | */
6 | package saulmm.avengers.model.repository.wrappers;
7 |
8 | import java.util.List;
9 | import saulmm.avengers.entities.MarvelCharacter;
10 |
11 | public class MarvelDataWrapper {
12 |
13 | private int count;
14 | private List results;
15 |
16 | public int getCount() {
17 |
18 | return count;
19 | }
20 |
21 | public List getResults() {
22 |
23 | return results;
24 | }
25 | }
--------------------------------------------------------------------------------
/model/src/main/java/saulmm/avengers/rest/Endpoint.java:
--------------------------------------------------------------------------------
1 | package saulmm.avengers.rest;
2 |
3 | /**
4 | * Created by saulmm on 12/01/16.
5 | */
6 | public class Endpoint {
7 | private final String endpoint;
8 |
9 | public Endpoint(String endpoint) {
10 | this.endpoint = endpoint;
11 | }
12 |
13 | public String getEndpoint() {
14 | return endpoint;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/model/src/main/java/saulmm/avengers/rest/HttpErrors.java:
--------------------------------------------------------------------------------
1 | package saulmm.avengers.rest;
2 |
3 | public class HttpErrors {
4 | public final static String SERVER_ERROR = "HTTP 503 Service Unavailable";
5 | }
6 |
--------------------------------------------------------------------------------
/model/src/main/java/saulmm/avengers/rest/MarvelApi.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | */
6 | package saulmm.avengers.rest;
7 |
8 | import java.util.List;
9 | import retrofit.http.GET;
10 | import retrofit.http.Path;
11 | import retrofit.http.Query;
12 | import rx.Observable;
13 | import saulmm.avengers.entities.CollectionItem;
14 | import saulmm.avengers.entities.MarvelCharacter;
15 |
16 | public interface MarvelApi {
17 | @GET("/v1/public/characters")
18 | Observable> getCharacters (@Query("offset") int offset);
19 |
20 | @GET("/v1/public/characters")
21 | Observable> getCharacterById(@Query("id") int id);
22 |
23 | @GET("/v1/public/characters/{characterId}/{collectionType}")
24 | Observable> getCharacterCollection(
25 | @Path("characterId") int id,
26 | @Path("collectionType") String collectionType);
27 | }
28 |
--------------------------------------------------------------------------------
/model/src/main/java/saulmm/avengers/rest/MarvelAuthorizer.java:
--------------------------------------------------------------------------------
1 | package saulmm.avengers.rest;
2 |
3 | public class MarvelAuthorizer {
4 | private String mApiClient;
5 | private String mApiSecret;
6 |
7 | public MarvelAuthorizer(String apiClient, String apiSecret) {
8 | mApiClient = apiClient;
9 | mApiSecret = apiSecret;
10 | }
11 |
12 | public void setApiClient(String mApiClient) {
13 | this.mApiClient = mApiClient;
14 | }
15 |
16 | public void setApiSecret(String mApiSecret) {
17 | this.mApiSecret = mApiSecret;
18 | }
19 |
20 | public String getApiClient() {
21 | return mApiClient;
22 | }
23 |
24 | public String getApiSecret() {
25 | return mApiSecret;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/model/src/main/java/saulmm/avengers/rest/RestDataSource.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | */
6 | package saulmm.avengers.rest;
7 |
8 | import com.google.gson.Gson;
9 | import com.google.gson.GsonBuilder;
10 | import com.google.gson.reflect.TypeToken;
11 | import com.squareup.okhttp.OkHttpClient;
12 | import com.squareup.okhttp.logging.HttpLoggingInterceptor;
13 |
14 | import java.lang.reflect.Type;
15 | import java.util.List;
16 | import javax.inject.Inject;
17 | import retrofit.GsonConverterFactory;
18 | import retrofit.Retrofit;
19 | import retrofit.RxJavaCallAdapterFactory;
20 | import rx.Observable;
21 | import rx.functions.Action1;
22 | import rx.functions.Func1;
23 | import saulmm.avengers.entities.CollectionItem;
24 | import saulmm.avengers.entities.MarvelCharacter;
25 | import saulmm.avengers.repository.CharacterRepository;
26 | import saulmm.avengers.rest.exceptions.ServerErrorException;
27 | import saulmm.avengers.rest.exceptions.UknownErrorException;
28 | import saulmm.avengers.rest.utils.deserializers.MarvelResultsDeserializer;
29 | import saulmm.avengers.rest.utils.interceptors.MarvelSigningIterceptor;
30 |
31 | import static com.squareup.okhttp.logging.HttpLoggingInterceptor.*;
32 | import static saulmm.avengers.entities.CollectionItem.COMICS;
33 | import static saulmm.avengers.entities.CollectionItem.EVENTS;
34 | import static saulmm.avengers.entities.CollectionItem.SERIES;
35 | import static saulmm.avengers.entities.CollectionItem.STORIES;
36 |
37 | public class RestDataSource implements CharacterRepository {
38 | public static String END_POINT = "http://gateway.marvel.com/";
39 | public static String PARAM_API_KEY = "apikey";
40 | public static String PARAM_HASH = "hash";
41 | public static String PARAM_TIMESTAMP = "ts";
42 |
43 | private final MarvelApi mMarvelApi;
44 | public final static int MAX_ATTEMPS = 3;
45 |
46 | @Inject
47 | public RestDataSource(MarvelAuthorizer marvelAuthorizer) {
48 | OkHttpClient client = new OkHttpClient();
49 |
50 | MarvelSigningIterceptor signingIterceptor =
51 | new MarvelSigningIterceptor(
52 | marvelAuthorizer.getApiClient(),
53 | marvelAuthorizer.getApiSecret());
54 |
55 | HttpLoggingInterceptor logginInterceptor = new HttpLoggingInterceptor();
56 | logginInterceptor.setLevel(Level.BODY);
57 |
58 | client.interceptors().add(signingIterceptor);
59 | client.interceptors().add(logginInterceptor);
60 |
61 | Gson customGsonInstance = new GsonBuilder()
62 | .registerTypeAdapter(new TypeToken>() {}.getType(),
63 | new MarvelResultsDeserializer())
64 |
65 | .registerTypeAdapter(new TypeToken>() {}.getType(),
66 | new MarvelResultsDeserializer())
67 |
68 | .create();
69 |
70 | Retrofit marvelApiAdapter = new Retrofit.Builder()
71 | .baseUrl(END_POINT)
72 | .addConverterFactory(GsonConverterFactory.create(customGsonInstance))
73 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
74 | .client(client)
75 | .build();
76 |
77 | mMarvelApi = marvelApiAdapter.create(MarvelApi.class);
78 | }
79 |
80 | @Override
81 | public Observable getCharacter(final int characterId) {
82 | return mMarvelApi.getCharacterById(characterId)
83 | .flatMap(new Func1, Observable>() {
84 | @Override public Observable call(List characters) {
85 | return Observable.just(characters.get(0));
86 | }
87 | });
88 | }
89 |
90 | @Override
91 | public Observable> getCharacters(int currentOffset) {
92 | return mMarvelApi.getCharacters(currentOffset)
93 | .onErrorResumeNext(new Func1>>() {
94 | @Override
95 | public Observable extends List> call(Throwable throwable) {
96 | boolean serverError = throwable.getMessage().equals(HttpErrors.SERVER_ERROR);
97 | return Observable.error(
98 | (serverError) ? new ServerErrorException() : new UknownErrorException());
99 | }
100 | });
101 | }
102 |
103 | @Override
104 | public Observable> getCharacterCollection(int characterId, String type) {
105 | if (!type.equals(COMICS) && !type.equals(EVENTS) && !type.equals(SERIES) && !type.equals(STORIES))
106 | throw new IllegalArgumentException("Collection type must be: events|series|comics|stories");
107 |
108 | return mMarvelApi.getCharacterCollection(characterId, type);
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/model/src/main/java/saulmm/avengers/rest/exceptions/NetworkErrorException.java:
--------------------------------------------------------------------------------
1 | package saulmm.avengers.rest.exceptions;
2 |
3 | public class NetworkErrorException extends Exception {
4 | }
5 |
--------------------------------------------------------------------------------
/model/src/main/java/saulmm/avengers/rest/exceptions/NetworkTimeOutException.java:
--------------------------------------------------------------------------------
1 | package saulmm.avengers.rest.exceptions;
2 |
3 | public class NetworkTimeOutException extends Exception {
4 | }
5 |
--------------------------------------------------------------------------------
/model/src/main/java/saulmm/avengers/rest/exceptions/NetworkUknownHostException.java:
--------------------------------------------------------------------------------
1 | package saulmm.avengers.rest.exceptions;
2 |
3 | public class NetworkUknownHostException extends Exception{
4 | }
5 |
--------------------------------------------------------------------------------
/model/src/main/java/saulmm/avengers/rest/exceptions/ServerErrorException.java:
--------------------------------------------------------------------------------
1 | package saulmm.avengers.rest.exceptions;
2 |
3 | public class ServerErrorException extends Exception {
4 | }
5 |
--------------------------------------------------------------------------------
/model/src/main/java/saulmm/avengers/rest/exceptions/UknownErrorException.java:
--------------------------------------------------------------------------------
1 | package saulmm.avengers.rest.exceptions;
2 |
3 | public class UknownErrorException extends Exception {
4 | }
5 |
--------------------------------------------------------------------------------
/model/src/main/java/saulmm/avengers/rest/utils/MarvelApiUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | */
6 | package saulmm.avengers.rest.utils;
7 |
8 | import java.security.MessageDigest;
9 | import java.security.NoSuchAlgorithmException;
10 |
11 | public class MarvelApiUtils {
12 |
13 | public static String generateMarvelHash (String publicKey, String privateKey) {
14 |
15 | String marvelHash = "";
16 |
17 | try {
18 |
19 | String timeStamp = getUnixTimeStamp();
20 | String marvelData = timeStamp + privateKey + publicKey;
21 |
22 | MessageDigest messageDigest = MessageDigest.getInstance("MD5");
23 | byte[] hash = messageDigest.digest(marvelData.getBytes());
24 |
25 | StringBuilder stringBuilder = new StringBuilder(2 * hash.length);
26 |
27 | for (byte b : hash)
28 | stringBuilder.append(String.format("%02x", b&0xff));
29 |
30 | marvelHash = stringBuilder.toString();
31 |
32 | } catch (NoSuchAlgorithmException e) {
33 |
34 | System.out.println("[DEBUG]" + " MarvelApiUtils generateMarvelHash - " +
35 | "NoSuchAlgorithmException");
36 | }
37 |
38 | return marvelHash;
39 | }
40 |
41 | public static String getUnixTimeStamp () {
42 |
43 | return String.valueOf(System.currentTimeMillis() / 1000L);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/model/src/main/java/saulmm/avengers/rest/utils/deserializers/MarvelResultsComicsDeserialiser.java:
--------------------------------------------------------------------------------
1 | package saulmm.avengers.rest.utils.deserializers;
2 |
3 | import com.google.gson.Gson;
4 | import com.google.gson.JsonArray;
5 | import com.google.gson.JsonDeserializationContext;
6 | import com.google.gson.JsonDeserializer;
7 | import com.google.gson.JsonElement;
8 | import com.google.gson.JsonParseException;
9 | import com.google.gson.reflect.TypeToken;
10 | import java.lang.reflect.Type;
11 | import java.util.List;
12 | import saulmm.avengers.entities.Comic;
13 |
14 | public class MarvelResultsComicsDeserialiser implements JsonDeserializer> {
15 |
16 | @Override
17 | public List deserialize(JsonElement je, Type typeOfT,
18 | JsonDeserializationContext context) throws JsonParseException {
19 |
20 | Type listType = new TypeToken>() {}.getType();
21 |
22 | JsonElement data = je.getAsJsonObject().get("data");
23 | JsonElement results = je.getAsJsonObject().get("results");
24 | JsonArray resultsArray = results.getAsJsonArray();
25 | JsonElement comicsObject = resultsArray.get(0);
26 | JsonElement items = comicsObject.getAsJsonObject().get("items");
27 |
28 | return new Gson().fromJson(items, listType);
29 | }
30 | }
--------------------------------------------------------------------------------
/model/src/main/java/saulmm/avengers/rest/utils/deserializers/MarvelResultsDeserializer.java:
--------------------------------------------------------------------------------
1 | package saulmm.avengers.rest.utils.deserializers;
2 |
3 | import com.google.gson.Gson;
4 | import com.google.gson.JsonDeserializationContext;
5 | import com.google.gson.JsonDeserializer;
6 | import com.google.gson.JsonElement;
7 | import com.google.gson.JsonParseException;
8 | import java.lang.reflect.Type;
9 | import java.util.List;
10 |
11 | public class MarvelResultsDeserializer implements JsonDeserializer> {
12 |
13 | @Override
14 | public List deserialize(JsonElement je, Type typeOfT,
15 | JsonDeserializationContext context) throws JsonParseException {
16 |
17 | JsonElement results = je.getAsJsonObject().get("data")
18 | .getAsJsonObject().get("results");
19 |
20 | return new Gson().fromJson(results, typeOfT);
21 | }
22 | }
--------------------------------------------------------------------------------
/model/src/main/java/saulmm/avengers/rest/utils/interceptors/MarvelSigningIterceptor.java:
--------------------------------------------------------------------------------
1 | package saulmm.avengers.rest.utils.interceptors;
2 |
3 | import com.squareup.okhttp.HttpUrl;
4 | import com.squareup.okhttp.Interceptor;
5 | import com.squareup.okhttp.Request;
6 | import com.squareup.okhttp.Response;
7 | import java.io.IOException;
8 | import saulmm.avengers.rest.MarvelApi;
9 | import saulmm.avengers.rest.RestDataSource;
10 | import saulmm.avengers.rest.utils.MarvelApiUtils;
11 |
12 | public class MarvelSigningIterceptor implements Interceptor {
13 | private final String mApiKey;
14 | private final String mApiSecret;
15 |
16 | public MarvelSigningIterceptor(String apiKey, String apiSecret) {
17 | mApiKey = apiKey;
18 | mApiSecret = apiSecret;
19 | }
20 |
21 | @Override public Response intercept(Chain chain) throws IOException {
22 | String marvelHash = MarvelApiUtils.generateMarvelHash(mApiKey, mApiSecret);
23 | Request oldRequest = chain.request();
24 |
25 | HttpUrl.Builder authorizedUrlBuilder = oldRequest.httpUrl().newBuilder()
26 | .scheme(oldRequest.httpUrl().scheme())
27 | .host(oldRequest.httpUrl().host());
28 |
29 | authorizedUrlBuilder.addQueryParameter(RestDataSource.PARAM_API_KEY, mApiKey)
30 | .addQueryParameter(RestDataSource.PARAM_TIMESTAMP, MarvelApiUtils.getUnixTimeStamp())
31 | .addQueryParameter(RestDataSource.PARAM_HASH, marvelHash);
32 |
33 | Request newRequest = oldRequest.newBuilder()
34 | .method(oldRequest.method(), oldRequest.body())
35 | .url(authorizedUrlBuilder.build())
36 | .build();
37 |
38 | return chain.proceed(newRequest);
39 | }
40 | }
41 |
42 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':domain', ':model'
2 |
--------------------------------------------------------------------------------