├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ └── styles.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ └── drawable │ │ │ │ └── ic_launcher_background.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── themovies │ │ │ │ └── sample │ │ │ │ ├── di │ │ │ │ ├── module │ │ │ │ │ ├── ViewModelModule.kt │ │ │ │ │ ├── UseCaseModule.kt │ │ │ │ │ ├── movies │ │ │ │ │ │ └── MoviesActivityModule.kt │ │ │ │ │ ├── ActivityBindingModule.kt │ │ │ │ │ ├── RepositoryModule.kt │ │ │ │ │ └── NetWorkModule.kt │ │ │ │ ├── qualifier │ │ │ │ │ └── ViewModelKey.kt │ │ │ │ ├── MovieComponent.kt │ │ │ │ └── ViewModelFactory.kt │ │ │ │ └── MovieApplication.kt │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── themovies │ │ │ └── sample │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── com │ │ └── themovies │ │ └── sample │ │ └── ExampleInstrumentedTest.kt ├── proguard-rules.pro └── build.gradle ├── common ├── .gitignore ├── src │ └── main │ │ ├── res │ │ └── values │ │ │ └── strings.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── themovies │ │ └── common │ │ ├── ReactiveExtension.kt │ │ ├── Extension.kt │ │ └── ViewExtension.kt ├── build.gradle └── proguard-rules.pro ├── data ├── .gitignore ├── src │ └── main │ │ ├── res │ │ └── values │ │ │ └── strings.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── themovies │ │ └── data │ │ ├── mapper │ │ ├── Mapper.kt │ │ └── MoviesMapper.kt │ │ ├── MovieService.kt │ │ ├── MovieRepositoryImpl.kt │ │ └── entity │ │ └── DataEntity.kt ├── build.gradle └── proguard-rules.pro ├── domain ├── .gitignore ├── src │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── themovies │ │ │ │ └── domain │ │ │ │ ├── UseCase.kt │ │ │ │ ├── SingleUseCase.kt │ │ │ │ ├── FlowableUseCase.kt │ │ │ │ ├── ObservableUseCase.kt │ │ │ │ ├── MovieRepository.kt │ │ │ │ ├── usecase │ │ │ │ └── MovieUseCase.kt │ │ │ │ └── entitiy │ │ │ │ └── DomainEntity.kt │ │ ├── res │ │ │ └── values │ │ │ │ └── strings.xml │ │ └── AndroidManifest.xml │ └── androidTest │ │ └── java │ │ └── com │ │ └── themovies │ │ └── domain │ │ └── ExampleInstrumentedTest.java ├── build.gradle └── proguard-rules.pro ├── presentation ├── .gitignore ├── src │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── res │ │ │ ├── drawable │ │ │ │ ├── img_placeholder.png │ │ │ │ ├── star.xml │ │ │ │ └── person.xml │ │ │ ├── values │ │ │ │ ├── styles.xml │ │ │ │ ├── colors.xml │ │ │ │ └── strings.xml │ │ │ └── layout │ │ │ │ ├── activity_movie.xml │ │ │ │ ├── item_movie.xml │ │ │ │ └── activity_movie_detail.xml │ │ └── java │ │ │ └── com │ │ │ └── themovies │ │ │ └── presentation │ │ │ ├── BaseViewModel.kt │ │ │ ├── ui │ │ │ ├── movies │ │ │ │ ├── adapter │ │ │ │ │ ├── MovieViewHolder.kt │ │ │ │ │ ├── DividerItemDecoration.kt │ │ │ │ │ └── MovieAdapter.kt │ │ │ │ ├── MovieViewModel.kt │ │ │ │ └── MoviesActivity.kt │ │ │ ├── MovieBindingAdapter.kt │ │ │ └── detail │ │ │ │ └── MovieDetailActivity.kt │ │ │ └── BaseActivity.kt │ └── test │ │ └── java │ │ └── com │ │ └── themovies │ │ └── presentation │ │ └── ExampleUnitTest.java ├── proguard-rules.pro └── build.gradle ├── demo.gif ├── settings.gradle ├── dependency.png ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .idea ├── encodings.xml ├── vcs.xml ├── misc.xml ├── runConfigurations.xml └── gradle.xml ├── .gitignore ├── android_network_dependencies.gradle ├── README.md ├── android_commons.gradle ├── android_core_dependencies.gradle ├── gradle.properties ├── gradlew.bat └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /common/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /data/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /domain/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /presentation/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkw8263/TheMovies_Demo/HEAD/demo.gif -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':common', ':data', ':domain', ':presentation' 2 | -------------------------------------------------------------------------------- /dependency.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkw8263/TheMovies_Demo/HEAD/dependency.png -------------------------------------------------------------------------------- /domain/src/main/java/com/themovies/domain/UseCase.kt: -------------------------------------------------------------------------------- 1 | package com.themovies.domain 2 | 3 | interface UseCase -------------------------------------------------------------------------------- /data/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | data 3 | 4 | -------------------------------------------------------------------------------- /common/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | common 3 | 4 | -------------------------------------------------------------------------------- /domain/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | domain 3 | 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkw8263/TheMovies_Demo/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /presentation/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkw8263/TheMovies_Demo/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkw8263/TheMovies_Demo/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkw8263/TheMovies_Demo/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /data/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkw8263/TheMovies_Demo/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkw8263/TheMovies_Demo/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /common/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /domain/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkw8263/TheMovies_Demo/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkw8263/TheMovies_Demo/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkw8263/TheMovies_Demo/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkw8263/TheMovies_Demo/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkw8263/TheMovies_Demo/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/img_placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkw8263/TheMovies_Demo/HEAD/presentation/src/main/res/drawable/img_placeholder.png -------------------------------------------------------------------------------- /data/src/main/java/com/themovies/data/mapper/Mapper.kt: -------------------------------------------------------------------------------- 1 | package com.themovies.data.mapper 2 | 3 | interface Mapper { 4 | fun mapFromEntity(type: E): D 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | TheMovies 3 | https://api.themoviedb.org/ 4 | 5 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /common/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply from: '../android_commons.gradle' 3 | apply from: '../android_core_dependencies.gradle' 4 | apply from: '../android_network_dependencies.gradle' 5 | -------------------------------------------------------------------------------- /domain/src/main/java/com/themovies/domain/SingleUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.themovies.domain 2 | 3 | import io.reactivex.Single 4 | 5 | interface SingleUseCase : UseCase { 6 | fun execute(params: Params? = null): Single 7 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/themovies/domain/FlowableUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.themovies.domain 2 | 3 | import io.reactivex.Flowable 4 | 5 | interface FlowableUseCase : UseCase { 6 | fun execute(params: Params? = null): Flowable 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #ffe812 6 | 7 | -------------------------------------------------------------------------------- /domain/src/main/java/com/themovies/domain/ObservableUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.themovies.domain 2 | 3 | import io.reactivex.Observable 4 | 5 | interface ObservableUseCase : UseCase { 6 | fun execute(params: Params? = null): Observable 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | -------------------------------------------------------------------------------- /domain/src/main/java/com/themovies/domain/MovieRepository.kt: -------------------------------------------------------------------------------- 1 | package com.themovies.domain 2 | 3 | import com.themovies.domain.entitiy.DomainEntityMovie 4 | import io.reactivex.Single 5 | 6 | interface MovieRepository { 7 | fun getMovies(movieId: Int): Single> 8 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jul 26 21:23:57 KST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android_network_dependencies.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation Libraries.retrofitRxAdapter 3 | implementation Libraries.rxAndroid 4 | implementation Libraries.gson 5 | implementation Libraries.retrofit 6 | implementation Libraries.retrofitGsonConverter 7 | implementation Libraries.httpLoggingInterceptor 8 | } -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /presentation/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /common/src/main/java/com/themovies/common/ReactiveExtension.kt: -------------------------------------------------------------------------------- 1 | package com.themovies.common 2 | 3 | import io.reactivex.Single 4 | import io.reactivex.android.schedulers.AndroidSchedulers 5 | import io.reactivex.schedulers.Schedulers 6 | 7 | fun Single.networkThreadWithMainThread(): Single { 8 | return this.subscribeOn(Schedulers.io()) 9 | .observeOn(AndroidSchedulers.mainThread()) 10 | } -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/star.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/themovies/presentation/BaseViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.themovies.presentation 2 | 3 | import androidx.lifecycle.ViewModel 4 | import io.reactivex.disposables.CompositeDisposable 5 | 6 | open class BaseViewModel : ViewModel() { 7 | val compositeDisposable = CompositeDisposable() 8 | 9 | override fun onCleared() { 10 | super.onCleared() 11 | compositeDisposable.clear() 12 | } 13 | } -------------------------------------------------------------------------------- /presentation/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #ffe812 6 | 7 | #EEF7F8FA 8 | 9 | 10 | #AE818181 11 | 12 | -------------------------------------------------------------------------------- /presentation/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | presentation 3 | Recommendation\nMovies 4 | https://image.tmdb.org/t/p/w500/ 5 | 1edeb9ac8156f4fd1ebd4bb7081667a1 6 | 7 | 8 | network error 9 | 10 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/themovies/presentation/ui/movies/adapter/MovieViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.themovies.presentation.ui.movies.adapter 2 | 3 | import android.view.View 4 | import androidx.recyclerview.widget.RecyclerView 5 | import com.themovies.presentation.databinding.ItemMovieBinding 6 | 7 | class MovieViewHolder(view: View) : RecyclerView.ViewHolder(view) { 8 | val binding: ItemMovieBinding = ItemMovieBinding.bind(view) 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/themovies/sample/di/module/ViewModelModule.kt: -------------------------------------------------------------------------------- 1 | package com.themovies.sample.di.module 2 | 3 | import androidx.lifecycle.ViewModelProvider 4 | import com.themovies.sample.di.ViewModelFactory 5 | import dagger.Binds 6 | import dagger.Module 7 | 8 | @Module 9 | abstract class ViewModelModule { 10 | @Binds 11 | abstract fun bindViewModelFactory( 12 | factory: ViewModelFactory 13 | ): ViewModelProvider.Factory 14 | } -------------------------------------------------------------------------------- /app/src/test/java/com/themovies/sample/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.themovies.sample 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/themovies/sample/MovieApplication.kt: -------------------------------------------------------------------------------- 1 | package com.themovies.sample 2 | 3 | import com.themovies.sample.di.DaggerMovieComponent 4 | import dagger.android.AndroidInjector 5 | import dagger.android.support.DaggerApplication 6 | 7 | class MovieApplication : DaggerApplication() { 8 | 9 | override fun applicationInjector(): AndroidInjector { 10 | return DaggerMovieComponent.factory().create(applicationContext) 11 | } 12 | } -------------------------------------------------------------------------------- /data/src/main/java/com/themovies/data/MovieService.kt: -------------------------------------------------------------------------------- 1 | package com.themovies.data 2 | 3 | import com.themovies.data.entity.DataEntityMovies 4 | import io.reactivex.Single 5 | import retrofit2.http.GET 6 | import retrofit2.http.Path 7 | import retrofit2.http.Query 8 | 9 | interface MovieService { 10 | 11 | @GET("3/movie/{movie_id}/recommendations") 12 | fun getMovies(@Path("movie_id") movieId: Int, @Query("api_key") apiKey: String): Single 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/themovies/sample/di/qualifier/ViewModelKey.kt: -------------------------------------------------------------------------------- 1 | package com.themovies.sample.di.qualifier 2 | 3 | import androidx.lifecycle.ViewModel 4 | import dagger.MapKey 5 | import kotlin.reflect.KClass 6 | 7 | @Target( 8 | AnnotationTarget.FUNCTION, 9 | AnnotationTarget.PROPERTY_GETTER, 10 | AnnotationTarget.PROPERTY_SETTER 11 | ) 12 | @Retention(AnnotationRetention.RUNTIME) 13 | @MapKey 14 | annotation class ViewModelKey(val value: KClass) 15 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/person.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## TheMovies_Demo 2 | ## Demo 3 | ![gif](https://github.com/mkw8263/TheMovies_Demo/blob/master/demo.gif) 4 | 5 | ## REST API 6 | https://www.themoviedb.org/ 7 | 8 | ## Languages, libraries and tools used 9 | * Kotlin 10 | * Dagger2 11 | * RxKotlin 12 | * AAC ViewModel 13 | * LiveData 14 | * AndroidX Support Libraries 15 | * Databinding 16 | * Retrofit 17 | * OkHttp3 18 | * Gson 19 | * Glide 20 | 21 | ## Dependency 22 | ![imge](https://github.com/mkw8263/TheMovies_Demo/blob/master/dependency.png) 23 | -------------------------------------------------------------------------------- /presentation/src/test/java/com/themovies/presentation/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.themovies.presentation; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /common/src/main/java/com/themovies/common/Extension.kt: -------------------------------------------------------------------------------- 1 | package com.themovies.common 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import androidx.lifecycle.* 5 | 6 | fun AppCompatActivity.createViewModel( 7 | factory: ViewModelProvider.Factory, classType: Class 8 | ): T = ViewModelProviders.of(this, factory)[classType] 9 | 10 | 11 | inline fun LifecycleOwner.ob(liveData: LiveData, crossinline action: (t: T) -> Unit) { 12 | liveData.observe(this, Observer { it?.let { t -> action(t) } }) 13 | } 14 | -------------------------------------------------------------------------------- /data/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply from: '../android_commons.gradle' 3 | apply from: '../android_network_dependencies.gradle' 4 | apply plugin: 'kotlin-kapt' 5 | 6 | 7 | dependencies { 8 | implementation project(Modules.common) 9 | implementation project(Modules.domain) 10 | 11 | implementation Libraries.dagger 12 | implementation Libraries.daggerAndroid 13 | implementation Libraries.daggerAndroidSupport 14 | kapt Libraries.daggerCompiler 15 | kapt Libraries.daggerProcessor 16 | } 17 | -------------------------------------------------------------------------------- /domain/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply from: '../android_commons.gradle' 3 | apply from: '../android_core_dependencies.gradle' 4 | apply from: '../android_network_dependencies.gradle' 5 | apply plugin: 'kotlin-kapt' 6 | 7 | dependencies { 8 | implementation project(Modules.common) 9 | 10 | 11 | implementation Libraries.dagger 12 | implementation Libraries.daggerAndroid 13 | implementation Libraries.daggerAndroidSupport 14 | kapt Libraries.daggerCompiler 15 | kapt Libraries.daggerProcessor 16 | } 17 | -------------------------------------------------------------------------------- /android_commons.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'kotlin-android' 2 | apply plugin: 'kotlin-android-extensions' 3 | 4 | android { 5 | compileSdkVersion Versions.compileSdk 6 | defaultConfig { 7 | minSdkVersion Versions.minSdk 8 | targetSdkVersion Versions.targetSdk 9 | versionCode Releases.versionCode 10 | versionName Releases.versionName 11 | } 12 | buildTypes { 13 | release { 14 | minifyEnabled true 15 | } 16 | } 17 | } 18 | 19 | dependencies { 20 | implementation KotlinLibraries.kotlin 21 | } -------------------------------------------------------------------------------- /android_core_dependencies.gradle: -------------------------------------------------------------------------------- 1 | /** 2 | * Shared dependencies between modules 3 | */ 4 | dependencies { 5 | // KOTLIN 6 | implementation KotlinLibraries.kotlin 7 | 8 | // ANDROID 9 | implementation AndroidLibraries.appCompat 10 | implementation AndroidLibraries.materialDesign 11 | implementation AndroidLibraries.coreKtx 12 | implementation AndroidLibraries.constraintLayout 13 | implementation AndroidLibraries.lifecycleViewModel 14 | implementation AndroidLibraries.lifecycleExtensions 15 | implementation AndroidLibraries.recyclerView 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/themovies/sample/di/module/UseCaseModule.kt: -------------------------------------------------------------------------------- 1 | package com.themovies.sample.di.module 2 | 3 | import com.themovies.domain.MovieRepository 4 | import com.themovies.domain.usecase.MovieUseCase 5 | import dagger.Module 6 | import dagger.Provides 7 | import javax.inject.Named 8 | 9 | @Module(includes = [RepositoryModule::class]) 10 | class UseCaseModule { 11 | @Provides 12 | fun provideMovieUseCase( 13 | @Named("MovieRepositoryImpl") movieRepository: MovieRepository 14 | ): MovieUseCase { 15 | return MovieUseCase(movieRepository) 16 | } 17 | } -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/themovies/sample/di/module/movies/MoviesActivityModule.kt: -------------------------------------------------------------------------------- 1 | package com.themovies.sample.di.module.movies 2 | 3 | import androidx.lifecycle.ViewModel 4 | import com.themovies.presentation.ui.movies.MovieViewModel 5 | import com.themovies.sample.di.module.UseCaseModule 6 | import com.themovies.sample.di.qualifier.ViewModelKey 7 | import dagger.Binds 8 | import dagger.Module 9 | import dagger.multibindings.IntoMap 10 | 11 | @Module(includes = [UseCaseModule::class]) 12 | abstract class MoviesActivityModule { 13 | @Binds 14 | @IntoMap 15 | @ViewModelKey(MovieViewModel::class) 16 | abstract fun bindViewModel(viewModel: MovieViewModel): ViewModel 17 | } -------------------------------------------------------------------------------- /presentation/src/main/java/com/themovies/presentation/ui/movies/adapter/DividerItemDecoration.kt: -------------------------------------------------------------------------------- 1 | package com.themovies.presentation.ui.movies.adapter 2 | 3 | import android.graphics.Rect 4 | import android.view.View 5 | import androidx.recyclerview.widget.RecyclerView 6 | 7 | class DividerItemDecoration(private val space: Int) : RecyclerView.ItemDecoration() { 8 | 9 | override fun getItemOffsets( 10 | outRect: Rect, 11 | view: View, 12 | parent: RecyclerView, 13 | state: RecyclerView.State 14 | ) { 15 | if (parent.getChildAdapterPosition(view) != parent.adapter?.itemCount ?: 0 - 1) { 16 | outRect.right = space 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /presentation/src/main/java/com/themovies/presentation/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package com.themovies.presentation 2 | 3 | import android.os.Bundle 4 | import androidx.databinding.DataBindingUtil 5 | import androidx.databinding.ViewDataBinding 6 | import dagger.android.support.DaggerAppCompatActivity 7 | 8 | abstract class BaseActivity : DaggerAppCompatActivity() { 9 | abstract val layoutResource: Int 10 | lateinit var viewDataBinding: B 11 | 12 | 13 | abstract fun setupViewDataBinding() 14 | override fun onCreate(savedInstanceState: Bundle?) { 15 | super.onCreate(savedInstanceState) 16 | viewDataBinding = DataBindingUtil.setContentView(this, layoutResource) 17 | setupViewDataBinding() 18 | } 19 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/themovies/sample/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.themovies.sample 2 | 3 | import androidx.test.InstrumentationRegistry 4 | import androidx.test.runner.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getTargetContext() 22 | assertEquals("com.themovies.sample", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/themovies/sample/di/module/ActivityBindingModule.kt: -------------------------------------------------------------------------------- 1 | package com.themovies.sample.di.module 2 | 3 | import com.themovies.presentation.ui.detail.MovieDetailActivity 4 | import com.themovies.presentation.ui.movies.MoviesActivity 5 | import com.themovies.sample.di.module.movies.MoviesActivityModule 6 | import dagger.Module 7 | import dagger.android.ContributesAndroidInjector 8 | 9 | @Module 10 | abstract class ActivityBindingModule { 11 | 12 | @ContributesAndroidInjector( 13 | modules = [ 14 | ViewModelModule::class, 15 | MoviesActivityModule::class 16 | ] 17 | ) 18 | abstract fun bindMoviesActivity(): MoviesActivity 19 | 20 | @ContributesAndroidInjector 21 | abstract fun bindMovieDetailActivity(): MovieDetailActivity 22 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/themovies/domain/usecase/MovieUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.themovies.domain.usecase 2 | 3 | import com.themovies.domain.MovieRepository 4 | import com.themovies.domain.SingleUseCase 5 | import com.themovies.domain.entitiy.DomainEntityMovie 6 | import io.reactivex.Single 7 | import javax.inject.Inject 8 | 9 | class MovieUseCase @Inject constructor( 10 | private val movieRepository: MovieRepository 11 | ) : SingleUseCase> { 12 | 13 | override fun execute(params: Params?): Single> { 14 | return params?.let { 15 | movieRepository.getMovies(params.movieId) 16 | } ?: run { 17 | Single.never>() 18 | } 19 | } 20 | 21 | data class Params(val movieId: Int) 22 | } 23 | -------------------------------------------------------------------------------- /data/src/main/java/com/themovies/data/MovieRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.themovies.data 2 | 3 | import com.themovies.common.networkThreadWithMainThread 4 | import com.themovies.data.mapper.MoviesMapper 5 | import com.themovies.domain.MovieRepository 6 | import com.themovies.domain.entitiy.DomainEntityMovie 7 | import io.reactivex.Single 8 | import javax.inject.Inject 9 | 10 | class MovieRepositoryImpl @Inject constructor( 11 | private val movieService: MovieService, 12 | private val moviesMapper: MoviesMapper, 13 | private val apiKey: String 14 | ) : MovieRepository { 15 | override fun getMovies(movieId: Int): Single> { 16 | return movieService.getMovies(movieId, apiKey) 17 | .networkThreadWithMainThread() 18 | .map(moviesMapper::mapFromEntity) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/themovies/sample/di/module/RepositoryModule.kt: -------------------------------------------------------------------------------- 1 | package com.themovies.sample.di.module 2 | 3 | import android.content.Context 4 | import com.themovies.data.MovieRepositoryImpl 5 | import com.themovies.data.MovieService 6 | import com.themovies.data.mapper.MoviesMapper 7 | import com.themovies.domain.MovieRepository 8 | import com.themovies.sample.R 9 | import dagger.Module 10 | import dagger.Provides 11 | import javax.inject.Named 12 | 13 | @Module 14 | class RepositoryModule { 15 | @Provides 16 | @Named("MovieRepositoryImpl") 17 | fun provideMovieRepository( 18 | movieService: MovieService, 19 | moviesMapper: MoviesMapper, 20 | context: Context 21 | ): MovieRepository { 22 | return MovieRepositoryImpl(movieService, moviesMapper, context.getString(R.string.api_key)) 23 | } 24 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /common/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /data/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /domain/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /presentation/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply from: '../android_commons.gradle' 3 | apply from: '../android_network_dependencies.gradle' 4 | apply plugin: 'kotlin-kapt' 5 | android { 6 | defaultConfig { 7 | applicationId ApplicationId.id 8 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 9 | } 10 | dataBinding { 11 | enabled true 12 | } 13 | } 14 | 15 | dependencies { 16 | implementation project(Modules.domain) 17 | implementation project(Modules.presentation) 18 | implementation project(Modules.data) 19 | implementation project(Modules.common) 20 | 21 | implementation Libraries.dagger 22 | implementation Libraries.daggerAndroid 23 | implementation Libraries.daggerAndroidSupport 24 | kapt Libraries.daggerCompiler 25 | kapt Libraries.daggerProcessor 26 | } 27 | -------------------------------------------------------------------------------- /domain/src/androidTest/java/com/themovies/domain/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.themovies.domain; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.InstrumentationRegistry; 6 | import androidx.test.runner.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getTargetContext(); 24 | 25 | assertEquals("com.themovies.domain.test", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/themovies/sample/di/MovieComponent.kt: -------------------------------------------------------------------------------- 1 | package com.themovies.sample.di 2 | 3 | import android.content.Context 4 | import com.themovies.sample.MovieApplication 5 | import com.themovies.sample.di.module.ActivityBindingModule 6 | import com.themovies.sample.di.module.NetWorkModule 7 | import dagger.BindsInstance 8 | import dagger.Component 9 | import dagger.android.AndroidInjector 10 | import dagger.android.support.AndroidSupportInjectionModule 11 | import javax.inject.Singleton 12 | 13 | @Singleton 14 | @Component( 15 | modules = [ 16 | AndroidSupportInjectionModule::class, 17 | ActivityBindingModule::class, 18 | NetWorkModule::class] 19 | ) 20 | interface MovieComponent : AndroidInjector { 21 | 22 | @Component.Factory 23 | interface Factory { 24 | fun create(@BindsInstance applicationContext: Context): MovieComponent 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/themovies/sample/di/ViewModelFactory.kt: -------------------------------------------------------------------------------- 1 | package com.themovies.sample.di 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.ViewModelProvider 5 | import javax.inject.Inject 6 | import javax.inject.Provider 7 | 8 | class ViewModelFactory @Inject constructor( 9 | private val creators: @JvmSuppressWildcards Map, Provider> 10 | ) : ViewModelProvider.Factory { 11 | 12 | override fun create(modelClass: Class): T { 13 | val found = creators.entries.find { modelClass.isAssignableFrom(it.key) } 14 | val creator = found?.value 15 | ?: throw IllegalArgumentException("unknown model class $modelClass") 16 | try { 17 | @Suppress("UNCHECKED_CAST") 18 | return creator.get() as T 19 | } catch (e: Exception) { 20 | throw RuntimeException(e) 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /presentation/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-kapt' 4 | apply from: '../android_commons.gradle' 5 | apply from: '../android_core_dependencies.gradle' 6 | apply from: '../android_network_dependencies.gradle' 7 | android { 8 | defaultConfig { 9 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 10 | } 11 | dataBinding { 12 | enabled true 13 | } 14 | } 15 | 16 | dependencies { 17 | implementation project(Modules.common) 18 | implementation project(Modules.domain) 19 | 20 | implementation Libraries.rxKotlin 21 | 22 | implementation Libraries.glide 23 | kapt Libraries.glideCompiler 24 | implementation Libraries.dagger 25 | implementation Libraries.daggerAndroid 26 | implementation Libraries.daggerAndroidSupport 27 | kapt Libraries.daggerCompiler 28 | kapt Libraries.daggerProcessor 29 | } 30 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 23 | -------------------------------------------------------------------------------- /common/src/main/java/com/themovies/common/ViewExtension.kt: -------------------------------------------------------------------------------- 1 | package com.themovies.common 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import android.widget.Toast 9 | import androidx.annotation.LayoutRes 10 | import androidx.core.app.ActivityOptionsCompat 11 | import androidx.core.view.ViewCompat 12 | 13 | fun ViewGroup.recyclerViewInflate(@LayoutRes layout: Int, attach: Boolean = false): View { 14 | return LayoutInflater.from(this.context).inflate(layout, this, attach) 15 | } 16 | 17 | fun Activity.sceneTransitionAnimation(targetView: View): ActivityOptionsCompat { 18 | return ActivityOptionsCompat.makeSceneTransitionAnimation( 19 | this, 20 | targetView, 21 | ViewCompat.getTransitionName(targetView).orEmpty() 22 | ) 23 | } 24 | 25 | 26 | fun Context.toast(message: String) { 27 | Toast.makeText(this, message, Toast.LENGTH_SHORT).show() 28 | } 29 | -------------------------------------------------------------------------------- /data/src/main/java/com/themovies/data/mapper/MoviesMapper.kt: -------------------------------------------------------------------------------- 1 | package com.themovies.data.mapper 2 | 3 | import com.themovies.data.entity.DataEntityMovies 4 | import com.themovies.domain.entitiy.DomainEntityMovie 5 | import javax.inject.Inject 6 | 7 | class MoviesMapper @Inject constructor() : 8 | Mapper> { 9 | override fun mapFromEntity(type: DataEntityMovies): List { 10 | return type.results.map { 11 | with(it) { 12 | DomainEntityMovie( 13 | adult, 14 | backdropPath, 15 | genreIds, 16 | id, 17 | originalLanguage, 18 | originalTitle, 19 | overview, 20 | popularity, 21 | posterPath, 22 | releaseDate, 23 | title, 24 | video, 25 | voteAverage, 26 | voteCount 27 | ) 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 23 | 24 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /data/src/main/java/com/themovies/data/entity/DataEntity.kt: -------------------------------------------------------------------------------- 1 | package com.themovies.data.entity 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | 6 | data class DataEntityMovies( 7 | @SerializedName("page") 8 | val page: Int, 9 | @SerializedName("results") 10 | val results: List, 11 | @SerializedName("total_pages") 12 | val totalPage: Int, 13 | @SerializedName("total_results") 14 | val totalResult: Int 15 | ) 16 | 17 | data class Result( 18 | @SerializedName("adult") 19 | val adult: Boolean, 20 | @SerializedName("backdrop_path") 21 | val backdropPath: String, 22 | @SerializedName("genre_ids") 23 | val genreIds: List, 24 | @SerializedName("id") 25 | val id: Int, 26 | @SerializedName("original_language") 27 | val originalLanguage: String, 28 | @SerializedName("original_title") 29 | val originalTitle: String, 30 | @SerializedName("overview") 31 | val overview: String, 32 | @SerializedName("popularity") 33 | val popularity: Double, 34 | @SerializedName("poster_path") 35 | val posterPath: String, 36 | @SerializedName("release_date") 37 | val releaseDate: String, 38 | @SerializedName("title") 39 | val title: String, 40 | @SerializedName("video") 41 | val video: Boolean, 42 | @SerializedName("vote_average") 43 | val voteAverage: Double, 44 | @SerializedName("vote_count") 45 | val voteCount: Int 46 | ) -------------------------------------------------------------------------------- /presentation/src/main/java/com/themovies/presentation/ui/movies/adapter/MovieAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.themovies.presentation.ui.movies.adapter 2 | 3 | import android.view.ViewGroup 4 | import androidx.recyclerview.widget.RecyclerView 5 | import com.bumptech.glide.RequestManager 6 | import com.themovies.common.recyclerViewInflate 7 | import com.themovies.domain.entitiy.DomainEntityMovie 8 | import com.themovies.presentation.R 9 | import com.themovies.presentation.ui.movies.MovieViewModel 10 | 11 | class MovieAdapter( 12 | private val viewModel: MovieViewModel, 13 | private val glideRequestManager: RequestManager 14 | ) : RecyclerView.Adapter() { 15 | 16 | private var list: List = emptyList() 17 | 18 | fun setupList(countries: List?) { 19 | list = countries.orEmpty() 20 | notifyDataSetChanged() 21 | } 22 | 23 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MovieViewHolder { 24 | return MovieViewHolder(parent.recyclerViewInflate(R.layout.item_movie)) 25 | } 26 | 27 | override fun getItemCount(): Int = list.count() 28 | 29 | override fun onBindViewHolder(holder: MovieViewHolder, position: Int) { 30 | holder.binding.run { 31 | item = list[position] 32 | movieViewModel = viewModel 33 | requestManager = glideRequestManager 34 | } 35 | } 36 | 37 | override fun onViewRecycled(holder: MovieViewHolder) { 38 | glideRequestManager.clear(holder.binding.imgMovie) 39 | super.onViewRecycled(holder) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/themovies/presentation/ui/MovieBindingAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.themovies.presentation.ui 2 | 3 | import android.widget.ImageView 4 | import androidx.databinding.BindingAdapter 5 | import androidx.recyclerview.widget.PagerSnapHelper 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.bumptech.glide.Glide 8 | import com.bumptech.glide.RequestManager 9 | import com.themovies.domain.entitiy.DomainEntityMovie 10 | import com.themovies.presentation.R 11 | import com.themovies.presentation.ui.movies.MovieViewModel 12 | import com.themovies.presentation.ui.movies.adapter.DividerItemDecoration 13 | import com.themovies.presentation.ui.movies.adapter.MovieAdapter 14 | 15 | @BindingAdapter(value = ["movies", "viewModel"]) 16 | fun RecyclerView.setupMovies(items: List?, vm: MovieViewModel) { 17 | this.adapter?.let { 18 | if (it is MovieAdapter) it.setupList(items) 19 | } ?: run { 20 | PagerSnapHelper().attachToRecyclerView(this) 21 | addItemDecoration(DividerItemDecoration(20)) 22 | MovieAdapter(vm, Glide.with(context)).apply { 23 | this@setupMovies.adapter = this 24 | setupList(items) 25 | } 26 | } 27 | } 28 | 29 | @BindingAdapter(value = ["img", "requestManager"]) 30 | fun ImageView.movieImage(path: String, manager: RequestManager) { 31 | manager.load(this.context.getString(R.string.image_base_url) + path) 32 | .placeholder(R.drawable.img_placeholder) 33 | .into(this) 34 | } 35 | 36 | @BindingAdapter(value = ["detailImage"]) 37 | fun ImageView.detailMovieImage(path: String) { 38 | Glide.with(this).load(this.context.getString(R.string.image_base_url) + path) 39 | .placeholder(R.drawable.img_placeholder) 40 | .into(this) 41 | } -------------------------------------------------------------------------------- /presentation/src/main/java/com/themovies/presentation/ui/detail/MovieDetailActivity.kt: -------------------------------------------------------------------------------- 1 | package com.themovies.presentation.ui.detail 2 | 3 | import android.os.Bundle 4 | import android.view.MenuItem 5 | import com.bumptech.glide.Glide 6 | import com.themovies.domain.entitiy.DomainEntityMovie 7 | import com.themovies.presentation.BaseActivity 8 | import com.themovies.presentation.R 9 | import com.themovies.presentation.databinding.ActivityMovieDetailBinding 10 | import kotlinx.android.synthetic.main.activity_movie_detail.* 11 | 12 | class MovieDetailActivity : BaseActivity() { 13 | 14 | companion object { 15 | val EXTRA_MOVIE_DATA = MovieDetailActivity::class.java.name 16 | } 17 | 18 | override val layoutResource: Int 19 | get() = R.layout.activity_movie_detail 20 | 21 | override fun setupViewDataBinding() { 22 | Glide.with(this) 23 | viewDataBinding.apply { 24 | movie = intent.getParcelableExtra(EXTRA_MOVIE_DATA) 25 | lifecycleOwner = this@MovieDetailActivity 26 | } 27 | } 28 | 29 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 30 | when (item.itemId) { 31 | android.R.id.home -> { 32 | finish() 33 | return true 34 | } 35 | } 36 | return super.onOptionsItemSelected(item) 37 | } 38 | 39 | override fun onCreate(savedInstanceState: Bundle?) { 40 | super.onCreate(savedInstanceState) 41 | 42 | setupToolbar() 43 | } 44 | 45 | private fun setupToolbar() { 46 | setSupportActionBar(toolbar) 47 | supportActionBar?.apply { 48 | setDisplayHomeAsUpEnabled(true) 49 | setDisplayShowTitleEnabled(false) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/themovies/presentation/ui/movies/MovieViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.themovies.presentation.ui.movies 2 | 3 | import androidx.annotation.StringRes 4 | import androidx.lifecycle.LiveData 5 | import androidx.lifecycle.MutableLiveData 6 | import com.themovies.domain.entitiy.DomainEntityMovie 7 | import com.themovies.domain.usecase.MovieUseCase 8 | import com.themovies.presentation.BaseViewModel 9 | import com.themovies.presentation.R 10 | import io.reactivex.rxkotlin.addTo 11 | import javax.inject.Inject 12 | 13 | class MovieViewModel @Inject constructor( 14 | private val movieUseCase: MovieUseCase 15 | ) : BaseViewModel() { 16 | sealed class State { 17 | data class ErrorMessage(@StringRes val msg: Int) : State() 18 | data class AdapterOnClick(val domainEntityMovie: DomainEntityMovie) : State() 19 | } 20 | 21 | private val _liveResult = MutableLiveData() 22 | val liveResult: LiveData = _liveResult 23 | 24 | private val _liveLoading = MutableLiveData() 25 | val liveLoading: LiveData = _liveLoading 26 | 27 | private val _liveMovies = MutableLiveData>() 28 | val liveMovies: LiveData> = _liveMovies 29 | 30 | init { 31 | getMovies() 32 | } 33 | 34 | fun adapterOnclick(domainEntityMovie: DomainEntityMovie) { 35 | _liveResult.value = State.AdapterOnClick(domainEntityMovie) 36 | } 37 | 38 | private fun getMovies() { 39 | _liveLoading.value = true 40 | movieUseCase.execute(MovieUseCase.Params(550)) 41 | .subscribe { response, error -> 42 | _liveLoading.postValue(false) 43 | if (error != null) { 44 | _liveResult.value = State.ErrorMessage(R.string.network_error) 45 | } else { 46 | _liveMovies.value = response 47 | } 48 | }.addTo(compositeDisposable) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/themovies/presentation/ui/movies/MoviesActivity.kt: -------------------------------------------------------------------------------- 1 | package com.themovies.presentation.ui.movies 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import androidx.lifecycle.ViewModelProvider 6 | import com.themovies.common.createViewModel 7 | import com.themovies.common.ob 8 | import com.themovies.common.sceneTransitionAnimation 9 | import com.themovies.common.toast 10 | import com.themovies.domain.entitiy.DomainEntityMovie 11 | import com.themovies.presentation.BaseActivity 12 | import com.themovies.presentation.R 13 | import com.themovies.presentation.databinding.ActivityMovieBinding 14 | import com.themovies.presentation.ui.detail.MovieDetailActivity 15 | import kotlinx.android.synthetic.main.activity_movie.* 16 | import javax.inject.Inject 17 | 18 | 19 | class MoviesActivity : BaseActivity() { 20 | @Inject 21 | lateinit var factory: ViewModelProvider.Factory 22 | 23 | private val viewModel: MovieViewModel by lazy { 24 | createViewModel(factory, MovieViewModel::class.java) 25 | } 26 | override val layoutResource: Int 27 | get() = R.layout.activity_movie 28 | 29 | override fun setupViewDataBinding() { 30 | viewDataBinding.apply { 31 | movieViewModel = viewModel 32 | lifecycleOwner = this@MoviesActivity 33 | } 34 | } 35 | 36 | override fun onCreate(savedInstanceState: Bundle?) { 37 | super.onCreate(savedInstanceState) 38 | 39 | ob(viewModel.liveResult, ::result) 40 | } 41 | 42 | private fun result(result: MovieViewModel.State) { 43 | when (result) { 44 | is MovieViewModel.State.AdapterOnClick -> startMovieDetailActivity(result.domainEntityMovie) 45 | is MovieViewModel.State.ErrorMessage -> toast(getString(result.msg)) 46 | } 47 | } 48 | 49 | private fun startMovieDetailActivity(domainEntityMovie: DomainEntityMovie) { 50 | val intent = Intent(this, MovieDetailActivity::class.java).apply { 51 | putExtra(MovieDetailActivity.EXTRA_MOVIE_DATA, domainEntityMovie) 52 | } 53 | 54 | startActivity(intent, sceneTransitionAnimation(rv_movies).toBundle()) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /domain/src/main/java/com/themovies/domain/entitiy/DomainEntity.kt: -------------------------------------------------------------------------------- 1 | package com.themovies.domain.entitiy 2 | 3 | import android.os.Parcel 4 | import android.os.Parcelable 5 | 6 | 7 | data class DomainEntityMovie( 8 | val adult: Boolean, 9 | val backdropPath: String, 10 | val genreIds: List, 11 | val id: Int, 12 | val originalLanguage: String, 13 | val originalTitle: String, 14 | val overview: String, 15 | val popularity: Double, 16 | val posterPath: String, 17 | val releaseDate: String, 18 | val title: String, 19 | val video: Boolean, 20 | val voteAverage: Double, 21 | val voteCount: Int 22 | ) : Parcelable { 23 | constructor(parcel: Parcel) : this( 24 | parcel.readByte() != 0.toByte(), 25 | parcel.readString().orEmpty(), 26 | parcel.createIntArray()?.toList().orEmpty(), 27 | parcel.readInt(), 28 | parcel.readString().orEmpty(), 29 | parcel.readString().orEmpty(), 30 | parcel.readString().orEmpty(), 31 | parcel.readDouble(), 32 | parcel.readString().orEmpty(), 33 | parcel.readString().orEmpty(), 34 | parcel.readString().orEmpty(), 35 | parcel.readByte() != 0.toByte(), 36 | parcel.readDouble(), 37 | parcel.readInt() 38 | ) 39 | 40 | override fun writeToParcel(parcel: Parcel, flags: Int) { 41 | parcel.writeByte(if (adult) 1 else 0) 42 | parcel.writeString(backdropPath) 43 | parcel.writeIntArray(genreIds.toIntArray()) 44 | parcel.writeInt(id) 45 | parcel.writeString(originalLanguage) 46 | parcel.writeString(originalTitle) 47 | parcel.writeString(overview) 48 | parcel.writeDouble(popularity) 49 | parcel.writeString(posterPath) 50 | parcel.writeString(releaseDate) 51 | parcel.writeString(title) 52 | parcel.writeByte(if (video) 1 else 0) 53 | parcel.writeDouble(voteAverage) 54 | parcel.writeInt(voteCount) 55 | } 56 | 57 | override fun describeContents(): Int = 0 58 | 59 | companion object CREATOR : Parcelable.Creator { 60 | override fun createFromParcel(parcel: Parcel): DomainEntityMovie = DomainEntityMovie(parcel) 61 | override fun newArray(size: Int): Array = arrayOfNulls(size) 62 | } 63 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /app/src/main/java/com/themovies/sample/di/module/NetWorkModule.kt: -------------------------------------------------------------------------------- 1 | package com.themovies.sample.di.module 2 | 3 | import android.content.Context 4 | import com.google.gson.Gson 5 | import com.google.gson.GsonBuilder 6 | import com.themovies.data.MovieService 7 | import com.themovies.sample.BuildConfig 8 | import com.themovies.sample.R 9 | import dagger.Module 10 | import dagger.Provides 11 | import okhttp3.Interceptor 12 | import okhttp3.OkHttpClient 13 | import okhttp3.logging.HttpLoggingInterceptor 14 | import retrofit2.Retrofit 15 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory 16 | import retrofit2.converter.gson.GsonConverterFactory 17 | import java.util.concurrent.TimeUnit 18 | import javax.inject.Singleton 19 | 20 | @Module 21 | class NetWorkModule { 22 | companion object { 23 | const val NETWORK_TIME_OUT: Long = 15 24 | } 25 | 26 | 27 | @Provides 28 | @Singleton 29 | fun provideInterceptor(): Interceptor { 30 | return Interceptor { chain -> 31 | val request = chain.request() 32 | chain.proceed(request.newBuilder().build()) 33 | } 34 | } 35 | 36 | @Provides 37 | @Singleton 38 | fun createClient(interceptor: Interceptor): OkHttpClient { 39 | return OkHttpClient.Builder().apply { 40 | addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.HEADERS)) 41 | if (BuildConfig.DEBUG) addInterceptor( 42 | HttpLoggingInterceptor().setLevel( 43 | HttpLoggingInterceptor.Level.BODY 44 | ) 45 | ) 46 | connectTimeout(NETWORK_TIME_OUT, TimeUnit.SECONDS) 47 | readTimeout(NETWORK_TIME_OUT, TimeUnit.SECONDS) 48 | addInterceptor(interceptor) 49 | }.build() 50 | } 51 | 52 | @Provides 53 | @Singleton 54 | fun provideGson(): Gson { 55 | return GsonBuilder() 56 | .setLenient() 57 | .create() 58 | } 59 | 60 | @Singleton 61 | @Provides 62 | fun provideApiService( 63 | context: Context, 64 | okHttpClient: OkHttpClient, 65 | gson: Gson 66 | ): MovieService { 67 | return Retrofit.Builder() 68 | .baseUrl(context.getString(R.string.base_url_movies_api)) 69 | .client(okHttpClient) 70 | .addConverterFactory(GsonConverterFactory.create(gson)) 71 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 72 | .build() 73 | .create(MovieService::class.java) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /presentation/src/main/res/layout/activity_movie.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 19 | 20 | 33 | 34 | 42 | 43 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /presentation/src/main/res/layout/item_movie.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 13 | 14 | 17 | 18 | 19 | 27 | 28 | 31 | 32 | 43 | 44 | 56 | 57 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /presentation/src/main/res/layout/activity_movie_detail.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 17 | 18 | 21 | 22 | 27 | 28 | 35 | 36 | 43 | 44 | 45 | 46 | 50 | 51 | 56 | 57 | 66 | 67 | 75 | 76 | 85 | 86 | 96 | 97 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | --------------------------------------------------------------------------------