├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ └── styles.xml │ │ │ ├── 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 │ │ │ ├── layout │ │ │ │ ├── content_home.xml │ │ │ │ └── activity_main.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ └── drawable │ │ │ │ └── ic_launcher_background.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── raqun │ │ │ │ └── movies │ │ │ │ ├── injection │ │ │ │ ├── modules │ │ │ │ │ ├── DomainModule.kt │ │ │ │ │ ├── DataModule.kt │ │ │ │ │ ├── ApplicationModule.kt │ │ │ │ │ ├── ViewModelModule.kt │ │ │ │ │ └── ActivityModule.kt │ │ │ │ └── components │ │ │ │ │ └── AppComponent.kt │ │ │ │ ├── MoviesApp.kt │ │ │ │ └── ui │ │ │ │ └── MainActivity.kt │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── raqun │ │ │ └── movies │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── com │ │ └── raqun │ │ └── movies │ │ └── ExampleInstrumentedTest.kt ├── proguard-rules.pro └── build.gradle ├── base ├── core │ ├── .gitignore │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ └── values │ │ │ │ │ └── strings.xml │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── raqun │ │ │ │ └── movies │ │ │ │ └── core │ │ │ │ ├── injection │ │ │ │ ├── scope │ │ │ │ │ ├── ActivityScope.kt │ │ │ │ │ └── FragmentScope.kt │ │ │ │ └── modules │ │ │ │ │ └── CoreModule.kt │ │ │ │ ├── error │ │ │ │ ├── Error.kt │ │ │ │ ├── ErrorFactory.kt │ │ │ │ └── DefaultErrorFactory.kt │ │ │ │ └── model │ │ │ │ └── DataHolder.kt │ │ ├── test │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── raqun │ │ │ │ └── movies │ │ │ │ └── core │ │ │ │ └── ExampleUnitTest.java │ │ └── androidTest │ │ │ └── java │ │ │ └── com │ │ │ └── raqun │ │ │ └── movies │ │ │ └── core │ │ │ └── ExampleInstrumentedTest.java │ ├── build.gradle │ └── proguard-rules.pro ├── core_data │ ├── .gitignore │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ └── values │ │ │ │ │ └── strings.xml │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── raqun │ │ │ │ └── movies │ │ │ │ └── core │ │ │ │ └── data │ │ │ │ ├── db │ │ │ │ ├── entity │ │ │ │ │ ├── DbEntity.kt │ │ │ │ │ ├── TvShowEntity.kt │ │ │ │ │ └── TvShowDetailEntity.kt │ │ │ │ ├── dao │ │ │ │ │ ├── TvShowDetailsDao.kt │ │ │ │ │ └── TvShowsDao.kt │ │ │ │ ├── MoviesDb.kt │ │ │ │ └── Db.kt │ │ │ │ ├── api │ │ │ │ ├── ApiConstants.kt │ │ │ │ ├── PagedApiResponse.kt │ │ │ │ └── DefaultRequestInterceptor.kt │ │ │ │ ├── injection │ │ │ │ └── modules │ │ │ │ │ ├── DbModule.kt │ │ │ │ │ └── ApiModule.kt │ │ │ │ └── source │ │ │ │ └── DataSource.kt │ │ ├── test │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── raqun │ │ │ │ └── movies │ │ │ │ └── core │ │ │ │ └── data │ │ │ │ └── ExampleUnitTest.java │ │ └── androidTest │ │ │ └── java │ │ │ └── com │ │ │ └── raqun │ │ │ └── movies │ │ │ └── core │ │ │ └── data │ │ │ └── ExampleInstrumentedTest.java │ ├── build.gradle │ └── proguard-rules.pro ├── core_domain │ ├── .gitignore │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ └── values │ │ │ │ │ └── strings.xml │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── raqun │ │ │ │ └── movies │ │ │ │ └── core │ │ │ │ └── domain │ │ │ │ └── Interactor.kt │ │ ├── test │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── raqun │ │ │ │ └── movies │ │ │ │ └── core │ │ │ │ └── domain │ │ │ │ └── ExampleUnitTest.java │ │ └── androidTest │ │ │ └── java │ │ │ └── com │ │ │ └── raqun │ │ │ └── movies │ │ │ └── core │ │ │ └── domain │ │ │ └── ExampleInstrumentedTest.java │ ├── build.gradle │ └── proguard-rules.pro ├── core_navigation │ ├── .gitignore │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ └── values │ │ │ │ │ └── strings.xml │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── raqun │ │ │ │ └── movies │ │ │ │ └── core │ │ │ │ └── navigation │ │ │ │ ├── features │ │ │ │ ├── Feature.kt │ │ │ │ └── TvShowDetails.kt │ │ │ │ ├── FragmentLoader.kt │ │ │ │ ├── IntentLoader.kt │ │ │ │ └── Loader.kt │ │ ├── test │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── raqun │ │ │ │ └── movies │ │ │ │ └── core │ │ │ │ └── navigation │ │ │ │ └── ExampleUnitTest.java │ │ └── androidTest │ │ │ └── java │ │ │ └── com │ │ │ └── raqun │ │ │ └── movies │ │ │ └── core │ │ │ └── navigation │ │ │ └── ExampleInstrumentedTest.java │ ├── build.gradle │ └── proguard-rules.pro └── core_presentation │ ├── .gitignore │ ├── src │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── raqun │ │ │ │ └── movies │ │ │ │ └── core │ │ │ │ └── presentation │ │ │ │ ├── entity │ │ │ │ └── ViewEntity.kt │ │ │ │ ├── recyclerview │ │ │ │ ├── DisplayItem.kt │ │ │ │ ├── ViewHolderBinder.kt │ │ │ │ ├── ViewHolderFactory.kt │ │ │ │ ├── DisplayItemComperator.kt │ │ │ │ ├── DefaultDisplayItemComperator.kt │ │ │ │ ├── ViewHolder.kt │ │ │ │ ├── DiffAdapter.kt │ │ │ │ ├── DiffUtilImpl.kt │ │ │ │ └── RecyclerViewAdapter.kt │ │ │ │ ├── navigation │ │ │ │ └── UiNavigation.kt │ │ │ │ ├── base │ │ │ │ ├── BaseView.kt │ │ │ │ ├── BaseInjectionFragment.kt │ │ │ │ ├── BaseViewModelFragment.kt │ │ │ │ ├── BaseInjectionActivity.kt │ │ │ │ ├── BaseActivity.kt │ │ │ │ └── BaseFragment.kt │ │ │ │ ├── Constants.kt │ │ │ │ ├── extensions │ │ │ │ ├── FragmentManagerExt.kt │ │ │ │ ├── ImageViewExt.kt │ │ │ │ └── RecyclerViewExt.kt │ │ │ │ └── viewmodel │ │ │ │ ├── ViewModelKey.kt │ │ │ │ ├── ReactiveViewModel.kt │ │ │ │ └── VmFactory.kt │ │ └── res │ │ │ ├── values │ │ │ ├── strings.xml │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ └── styles.xml │ │ │ └── layout │ │ │ └── toolbar_default.xml │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── raqun │ │ │ └── movies │ │ │ └── core │ │ │ └── presentation │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── raqun │ │ └── movies │ │ └── core │ │ └── presentation │ │ └── ExampleInstrumentedTest.java │ ├── build.gradle │ └── proguard-rules.pro ├── shows ├── shows_data │ ├── .gitignore │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ └── values │ │ │ │ │ └── strings.xml │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── raqun │ │ │ │ └── movies │ │ │ │ └── shows │ │ │ │ └── data │ │ │ │ ├── TvShowMapper.kt │ │ │ │ ├── TvShowEntityMapper.kt │ │ │ │ ├── TvShowDetailMapper.kt │ │ │ │ ├── TvShowsServices.kt │ │ │ │ ├── TvShowDetailEntityMapper.kt │ │ │ │ ├── GetTvShowDetailRemoteDataSource.kt │ │ │ │ ├── GetPopularTvShowsRemoteDataSource.kt │ │ │ │ ├── TvShowDetailsLocalDataSource.kt │ │ │ │ ├── TvShowsLocalDataSource.kt │ │ │ │ ├── TvShowsRepositoryImpl.kt │ │ │ │ └── ShowsDataModule.kt │ │ ├── test │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── raqun │ │ │ │ └── movies │ │ │ │ └── shows │ │ │ │ └── data │ │ │ │ └── ExampleUnitTest.java │ │ └── androidTest │ │ │ └── java │ │ │ └── com │ │ │ └── raqun │ │ │ └── movies │ │ │ └── shows │ │ │ └── data │ │ │ └── ExampleInstrumentedTest.java │ ├── build.gradle │ └── proguard-rules.pro ├── shows_domain │ ├── .gitignore │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ └── values │ │ │ │ │ └── strings.xml │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── raqun │ │ │ │ └── movies │ │ │ │ └── shows │ │ │ │ └── domain │ │ │ │ ├── TvShowsRepository.kt │ │ │ │ ├── PagedTvShows.kt │ │ │ │ ├── TvShow.kt │ │ │ │ ├── TvShowDetail.kt │ │ │ │ ├── GetPopularTvShowsInteractor.kt │ │ │ │ ├── ShowsDomainModule.kt │ │ │ │ └── GetTvShowDetailInteractor.kt │ │ ├── test │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── raqun │ │ │ │ └── movies │ │ │ │ └── shows │ │ │ │ └── domain │ │ │ │ ├── util │ │ │ │ └── DataProvider.kt │ │ │ │ └── GetPopularTvShowsInteractorTest.kt │ │ └── androidTest │ │ │ └── java │ │ │ └── com │ │ │ └── raqun │ │ │ └── movies │ │ │ └── shows │ │ │ └── domain │ │ │ └── ExampleInstrumentedTest.java │ ├── build.gradle │ └── proguard-rules.pro ├── shows_presentation │ ├── .gitignore │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── values │ │ │ │ │ ├── strings.xml │ │ │ │ │ └── styles.xml │ │ │ │ ├── drawable │ │ │ │ │ └── ic_baseline_star_24px.xml │ │ │ │ └── layout │ │ │ │ │ ├── fragment_popular_tv_shows.xml │ │ │ │ │ └── item_popular_tv_show.xml │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── raqun │ │ │ │ └── movie │ │ │ │ └── shows │ │ │ │ └── presentation │ │ │ │ ├── PopularTvShowsPresentationConstants.kt │ │ │ │ ├── PopularTvShowsFragmentModule.kt │ │ │ │ ├── PopularTvShowViewEntity.kt │ │ │ │ ├── PopularTvShowsViewEntityMapper.kt │ │ │ │ ├── PopularTvShowsViewModelModule.kt │ │ │ │ ├── PopularTvShowsPresentationModule.kt │ │ │ │ ├── PopularTvShowsViewHolder.kt │ │ │ │ ├── PopularTvShowsFragment.kt │ │ │ │ └── PopularTvShowsViewModel.kt │ │ ├── test │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── raqun │ │ │ │ └── movie │ │ │ │ └── shows │ │ │ │ └── presentation │ │ │ │ └── ExampleUnitTest.java │ │ └── androidTest │ │ │ └── java │ │ │ └── com │ │ │ └── raqun │ │ │ └── movie │ │ │ └── shows │ │ │ └── presentation │ │ │ └── ExampleInstrumentedTest.java │ ├── build.gradle │ └── proguard-rules.pro └── shows_details_presentation │ ├── .gitignore │ ├── build.gradle │ ├── src │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── raqun │ │ │ │ └── movies │ │ │ │ └── shows │ │ │ │ └── details │ │ │ │ └── presentation │ │ │ │ ├── TvShowDetailsPresentationModule.kt │ │ │ │ ├── TvShowsDetailsFragmentModule.kt │ │ │ │ ├── TvShowDetailsActivityModule.kt │ │ │ │ ├── TvShowDetailsViewEntity.kt │ │ │ │ ├── TvShowDetailsViewEntityMapper.kt │ │ │ │ ├── TvShowDetailsViewModelModule.kt │ │ │ │ ├── TvShowDetailsActivity.kt │ │ │ │ ├── TvShowDetailsViewModel.kt │ │ │ │ └── TvShowDetailsFragment.kt │ │ ├── res │ │ │ ├── layout │ │ │ │ ├── activity_show_details.xml │ │ │ │ └── fragment_show_details.xml │ │ │ └── values │ │ │ │ └── strings.xml │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── raqun │ │ │ └── movies │ │ │ └── shows │ │ │ └── details │ │ │ └── presentation │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── raqun │ │ └── movies │ │ └── shows │ │ └── details │ │ └── presentation │ │ └── ExampleInstrumentedTest.java │ └── proguard-rules.pro ├── art └── ss1.png ├── common-android-library.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── settings.gradle ├── .gitignore ├── gradle.properties ├── common.gradle ├── README.md ├── gradlew.bat └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /base/core/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /base/core_data/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /base/core_domain/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /shows/shows_data/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /base/core_navigation/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /shows/shows_domain/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /base/core_presentation/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /shows/shows_presentation/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /shows/shows_details_presentation/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /art/ss1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/savepopulation/movies/HEAD/art/ss1.png -------------------------------------------------------------------------------- /common-android-library.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: Plugins.androidLibrary 2 | apply from: "$rootDir/common.gradle" -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Backlight 3 | 4 | -------------------------------------------------------------------------------- /base/core/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | core 3 | 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/savepopulation/movies/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /base/core_data/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | core_data 3 | 4 | -------------------------------------------------------------------------------- /shows/shows_data/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | shows_data 3 | 4 | -------------------------------------------------------------------------------- /base/core_domain/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | core_domain 3 | 4 | -------------------------------------------------------------------------------- /shows/shows_domain/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | shows_domain 3 | 4 | -------------------------------------------------------------------------------- /base/core_domain/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "$rootDir/common-android-library.gradle" 2 | dependencies { 3 | api project(Modules.core) 4 | } -------------------------------------------------------------------------------- /base/core_navigation/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | core_navigation 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/savepopulation/movies/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/savepopulation/movies/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/savepopulation/movies/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /shows/shows_domain/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "$rootDir/common-android-library.gradle" 2 | dependencies { 3 | api project(Modules.coreDomain) 4 | } -------------------------------------------------------------------------------- /shows/shows_presentation/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | %1$s \/ 10 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/savepopulation/movies/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/savepopulation/movies/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/savepopulation/movies/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/savepopulation/movies/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/savepopulation/movies/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/savepopulation/movies/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/savepopulation/movies/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /base/core/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /base/core_data/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /base/core_data/src/main/java/com/raqun/movies/core/data/db/entity/DbEntity.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.data.db.entity 2 | 3 | // marker interface 4 | interface DbEntity -------------------------------------------------------------------------------- /base/core_domain/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /shows/shows_data/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /shows/shows_domain/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /base/core_navigation/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /base/core_presentation/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /shows/shows_presentation/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /base/core_presentation/src/main/java/com/raqun/movies/core/presentation/entity/ViewEntity.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.presentation.entity 2 | 3 | // marker interface 4 | interface ViewEntity -------------------------------------------------------------------------------- /base/core_navigation/src/main/java/com/raqun/movies/core/navigation/features/Feature.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.navigation.features 2 | 3 | interface Feature { 4 | val dynamicStart: T? 5 | } -------------------------------------------------------------------------------- /base/core_presentation/src/main/java/com/raqun/movies/core/presentation/recyclerview/DisplayItem.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.presentation.recyclerview 2 | 3 | interface DisplayItem { 4 | fun type(): Int 5 | } -------------------------------------------------------------------------------- /shows/shows_presentation/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "$rootDir/common-android-library.gradle" 2 | dependencies { 3 | implementation project(Modules.corePresentation) 4 | implementation project(Modules.showsDomain) 5 | } -------------------------------------------------------------------------------- /base/core_presentation/src/main/java/com/raqun/movies/core/presentation/navigation/UiNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.presentation.navigation 2 | 3 | enum class UiNavigation { 4 | NONE, 5 | BACK, 6 | ROOT 7 | } -------------------------------------------------------------------------------- /base/core_presentation/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Popular Tv Shows 3 | An unknown error has been occurred! 4 | 5 | -------------------------------------------------------------------------------- /base/core/src/main/java/com/raqun/movies/core/injection/scope/ActivityScope.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.injection.scope 2 | 3 | import javax.inject.Scope 4 | 5 | @Scope 6 | @Retention(AnnotationRetention.RUNTIME) 7 | annotation class ActivityScope -------------------------------------------------------------------------------- /base/core/src/main/java/com/raqun/movies/core/injection/scope/FragmentScope.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.injection.scope 2 | 3 | import javax.inject.Scope 4 | 5 | @Scope 6 | @Retention(AnnotationRetention.RUNTIME) 7 | annotation class FragmentScope -------------------------------------------------------------------------------- /base/core_presentation/src/main/java/com/raqun/movies/core/presentation/base/BaseView.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.presentation.base 2 | 3 | import com.raqun.movies.core.error.Error 4 | 5 | interface BaseView { 6 | fun onError(e: Error) 7 | } -------------------------------------------------------------------------------- /shows/shows_data/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "$rootDir/common-android-library.gradle" 2 | dependencies { 3 | implementation project(Modules.core) 4 | implementation project(Modules.coreData) 5 | implementation project(Modules.showsDomain) 6 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include Modules.app 2 | include Modules.core, Modules.corePresentation, Modules.coreData, Modules.coreDomain, Modules.coreNavigation 3 | include Modules.showsPresentation, Modules.showsData, Modules.showsDomain, Modules.showsDetailsPresentation 4 | -------------------------------------------------------------------------------- /shows/shows_details_presentation/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "$rootDir/common-android-library.gradle" 2 | dependencies { 3 | implementation project(Modules.corePresentation) 4 | implementation project(Modules.showsDomain) 5 | implementation Libraries.scalingLayout 6 | } -------------------------------------------------------------------------------- /shows/shows_details_presentation/src/main/java/com/raqun/movies/shows/details/presentation/TvShowDetailsPresentationModule.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.shows.details.presentation 2 | 3 | import dagger.Module 4 | 5 | @Module 6 | class TvShowDetailsPresentationModule { 7 | 8 | } -------------------------------------------------------------------------------- /shows/shows_presentation/src/main/java/com/raqun/movie/shows/presentation/PopularTvShowsPresentationConstants.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movie.shows.presentation 2 | 3 | class PopularTvShowsPresentationConstants { 4 | internal object TYPES { 5 | const val SHOW = 0 6 | } 7 | } -------------------------------------------------------------------------------- /.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 | /.idea 15 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Mar 11 09:33:05 EET 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-4.10.1-all.zip 7 | -------------------------------------------------------------------------------- /base/core/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "$rootDir/common-android-library.gradle" 2 | dependencies { 3 | // Dagger2 4 | api Libraries.dagger2AndroidSupport 5 | 6 | //Rx 7 | api Libraries.rxKotlin 8 | api Libraries.rxAndroid 9 | 10 | // Gson 11 | api Libraries.gson 12 | } -------------------------------------------------------------------------------- /base/core_presentation/src/main/java/com/raqun/movies/core/presentation/Constants.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.presentation 2 | 3 | class Constants { 4 | companion object { 5 | const val NO_RES = 0 6 | const val IMAGE_BASE_URL = "https://image.tmdb.org/t/p/w500/" 7 | } 8 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/movies/injection/modules/DomainModule.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.injection.modules 2 | 3 | import com.raqun.movies.shows.domain.ShowsDomainModule 4 | import dagger.Module 5 | 6 | @Module( 7 | includes = [ShowsDomainModule::class] 8 | ) 9 | internal abstract class DomainModule -------------------------------------------------------------------------------- /base/core_data/src/main/java/com/raqun/movies/core/data/api/ApiConstants.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.data.api 2 | 3 | class ApiConstants private constructor() { 4 | 5 | companion object { 6 | const val API_KEY = "7f958fd939fa122467dc8c9685e9d223" 7 | const val TIMEOUT_INMILIS = 15000L 8 | } 9 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/content_home.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /base/core_presentation/src/main/java/com/raqun/movies/core/presentation/recyclerview/ViewHolderBinder.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.presentation.recyclerview 2 | 3 | import androidx.recyclerview.widget.RecyclerView 4 | 5 | interface ViewHolderBinder { 6 | fun bind(holder: RecyclerView.ViewHolder, item: DisplayItem) 7 | } -------------------------------------------------------------------------------- /shows/shows_domain/src/main/java/com/raqun/movies/shows/domain/TvShowsRepository.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.shows.domain 2 | 3 | import io.reactivex.Flowable 4 | 5 | interface TvShowsRepository { 6 | fun getPopularTShows(page: Int): Flowable> 7 | 8 | fun getShowDetail(id: Int): Flowable 9 | } -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /base/core/src/main/java/com/raqun/movies/core/error/Error.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.error 2 | 3 | sealed class Error { 4 | 5 | class UnknownError : Error() 6 | 7 | data class ApiError(val code: Int = 1, val message: String? = null) : Error() 8 | 9 | data class BusinessError(val code: Int = 1, val message: String?) : Error() 10 | } -------------------------------------------------------------------------------- /base/core_presentation/src/main/java/com/raqun/movies/core/presentation/recyclerview/ViewHolderFactory.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.presentation.recyclerview 2 | 3 | import android.view.ViewGroup 4 | import androidx.recyclerview.widget.RecyclerView 5 | 6 | interface ViewHolderFactory { 7 | fun createViewHolder(parent: ViewGroup): RecyclerView.ViewHolder 8 | } -------------------------------------------------------------------------------- /base/core_presentation/src/main/java/com/raqun/movies/core/presentation/recyclerview/DisplayItemComperator.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.presentation.recyclerview 2 | 3 | interface DisplayItemComperator { 4 | fun areItemsSame(oldItem: DisplayItem, newItem: DisplayItem): Boolean 5 | 6 | fun areContentsSame(oldItem: DisplayItem, newItem: DisplayItem): Boolean 7 | } -------------------------------------------------------------------------------- /base/core/src/main/java/com/raqun/movies/core/model/DataHolder.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.model 2 | 3 | import com.raqun.movies.core.error.Error 4 | 5 | sealed class DataHolder { 6 | 7 | data class Success(val data: T) : DataHolder() 8 | 9 | data class Fail(val e: Error) : DataHolder() 10 | 11 | class Loading : DataHolder() 12 | } -------------------------------------------------------------------------------- /base/core_navigation/src/main/java/com/raqun/movies/core/navigation/FragmentLoader.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.navigation 2 | 3 | 4 | import androidx.fragment.app.Fragment 5 | 6 | internal fun String.loadFragmentOrReturnNull(): Fragment? = 7 | try { 8 | this.loadClassOrReturnNull()?.newInstance() 9 | } catch (e: ClassNotFoundException) { 10 | null 11 | } -------------------------------------------------------------------------------- /base/core_presentation/src/main/java/com/raqun/movies/core/presentation/extensions/FragmentManagerExt.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.presentation.extensions 2 | 3 | import androidx.fragment.app.FragmentManager 4 | import androidx.fragment.app.FragmentTransaction 5 | 6 | inline fun FragmentManager.transact(func: FragmentTransaction.() -> FragmentTransaction) { 7 | beginTransaction().func().commit() 8 | } -------------------------------------------------------------------------------- /shows/shows_details_presentation/src/main/res/layout/activity_show_details.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /base/core/src/main/java/com/raqun/movies/core/error/ErrorFactory.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.error 2 | 3 | interface ErrorFactory { 4 | 5 | fun createUnknownError(): Error 6 | 7 | fun createApiError(code: Int, message: String?): Error 8 | 9 | fun createErrorFromThrowable(t: Throwable): Error 10 | 11 | fun createBusinessError(code: Int, message: String?): Error 12 | 13 | } 14 | -------------------------------------------------------------------------------- /base/core_data/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "$rootDir/common-android-library.gradle" 2 | dependencies { 3 | 4 | implementation project(Modules.core) 5 | 6 | // RETROFIT 7 | api Libraries.retrofit 8 | api Libraries.logInterceptor 9 | api Libraries.okHttp 10 | api Libraries.retrofitRxAdapter 11 | api Libraries.room 12 | 13 | api 'androidx.room:room-rxjava2:2.1.0-alpha06' 14 | } -------------------------------------------------------------------------------- /base/core_presentation/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "$rootDir/common-android-library.gradle" 2 | dependencies { 3 | 4 | api project(Modules.core) 5 | api project(Modules.coreNavigation) 6 | 7 | // View 8 | api SupportLibraries.appCompat 9 | api SupportLibraries.recyclerView 10 | api SupportLibraries.design 11 | api Libraries.lifecycleExtensions 12 | api Libraries.picasso 13 | } -------------------------------------------------------------------------------- /base/core_presentation/src/main/java/com/raqun/movies/core/presentation/extensions/ImageViewExt.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.presentation.extensions 2 | 3 | import android.widget.ImageView 4 | import com.squareup.picasso.Picasso 5 | 6 | /* 7 | * Loads image into current target 8 | */ 9 | fun ImageView.loadImage(url: String) { 10 | Picasso.get() 11 | .load(url) 12 | .into(this) 13 | } -------------------------------------------------------------------------------- /shows/shows_details_presentation/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /shows/shows_domain/src/main/java/com/raqun/movies/shows/domain/PagedTvShows.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.shows.domain 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class PagedTvShows( 6 | @SerializedName("page") val page: Int, 7 | @SerializedName("total_results") val totalResults: Int, 8 | @SerializedName("total_pages") val totalPages: Int, 9 | @SerializedName("results") val results: List 10 | ) -------------------------------------------------------------------------------- /base/core_data/src/main/java/com/raqun/movies/core/data/api/PagedApiResponse.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.data.api 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class PagedApiResponse( 6 | @SerializedName("page") val page: Int?, 7 | @SerializedName("total_results") val totalResults: Int?, 8 | @SerializedName("total_pages") val totalPages: Int?, 9 | @SerializedName("results") val results: T? 10 | ) 11 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /shows/shows_presentation/src/main/res/drawable/ic_baseline_star_24px.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/test/java/com/raqun/movies/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies 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 | -------------------------------------------------------------------------------- /base/core_presentation/src/main/java/com/raqun/movies/core/presentation/viewmodel/ViewModelKey.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.presentation.viewmodel 2 | 3 | import androidx.lifecycle.ViewModel 4 | import dagger.MapKey 5 | import kotlin.reflect.KClass 6 | 7 | @Retention(AnnotationRetention.RUNTIME) 8 | @Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) 9 | @MapKey 10 | annotation class ViewModelKey(val value: KClass) -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/movies/injection/modules/DataModule.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.injection.modules 2 | 3 | import com.raqun.movies.core.data.injection.modules.ApiModule 4 | import com.raqun.movies.core.data.injection.modules.DbModule 5 | import com.raqun.movies.shows.data.ShowsDataModule 6 | import dagger.Module 7 | 8 | @Module( 9 | includes = [ApiModule::class, 10 | DbModule::class, 11 | ShowsDataModule::class] 12 | ) 13 | internal abstract class DataModule -------------------------------------------------------------------------------- /shows/shows_domain/src/main/java/com/raqun/movies/shows/domain/TvShow.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.shows.domain 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class TvShow( 6 | @SerializedName("id") val id: Int?, 7 | @SerializedName("name") val name: String?, 8 | @SerializedName("vote_average") val votesAverage: Number?, 9 | @SerializedName("poster_path") val posterPath: String?, 10 | @SerializedName("popularity") val popularity: Number 11 | ) -------------------------------------------------------------------------------- /base/core/src/main/java/com/raqun/movies/core/injection/modules/CoreModule.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.injection.modules 2 | 3 | import com.raqun.movies.core.error.DefaultErrorFactory 4 | import com.raqun.movies.core.error.ErrorFactory 5 | import dagger.Module 6 | import dagger.Provides 7 | import javax.inject.Singleton 8 | 9 | @Module 10 | class CoreModule { 11 | 12 | @Provides 13 | @Singleton 14 | internal fun provideErrorFactory(): ErrorFactory = DefaultErrorFactory() 15 | 16 | } -------------------------------------------------------------------------------- /base/core/src/test/java/com/raqun/movies/core/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core; 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 | } -------------------------------------------------------------------------------- /base/core_data/src/test/java/com/raqun/movies/core/data/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.data; 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 | } -------------------------------------------------------------------------------- /shows/shows_data/src/test/java/com/raqun/movies/shows/data/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.shows.data; 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 | } -------------------------------------------------------------------------------- /base/core/src/main/java/com/raqun/movies/core/error/DefaultErrorFactory.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.error 2 | 3 | class DefaultErrorFactory : ErrorFactory { 4 | 5 | override fun createUnknownError() = Error.UnknownError() 6 | 7 | override fun createApiError(code: Int, message: String?) = Error.ApiError(code, message) 8 | 9 | override fun createErrorFromThrowable(t: Throwable) = Error.UnknownError() 10 | 11 | override fun createBusinessError(code: Int, message: String?) = Error.BusinessError(code, message) 12 | } -------------------------------------------------------------------------------- /base/core_domain/src/test/java/com/raqun/movies/core/domain/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.domain; 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 | } -------------------------------------------------------------------------------- /base/core_navigation/src/main/java/com/raqun/movies/core/navigation/IntentLoader.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.navigation 2 | 3 | import android.content.Intent 4 | 5 | private fun intentTo(className: String): Intent = 6 | Intent(Intent.ACTION_VIEW).setClassName(BuildConfig.PACKAGE_NAME, className) 7 | 8 | internal fun String.loadIntentOrReturnNull(): Intent? = 9 | try { 10 | Class.forName(this).run { intentTo(this@loadIntentOrReturnNull) } 11 | } catch (e: ClassNotFoundException) { 12 | null 13 | } -------------------------------------------------------------------------------- /base/core_presentation/src/main/java/com/raqun/movies/core/presentation/recyclerview/DefaultDisplayItemComperator.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.presentation.recyclerview 2 | 3 | class DefaultDisplayItemComperator : DisplayItemComperator { 4 | 5 | override fun areItemsSame(oldItem: DisplayItem, newItem: DisplayItem): Boolean { 6 | return oldItem == newItem 7 | } 8 | 9 | override fun areContentsSame(oldItem: DisplayItem, newItem: DisplayItem): Boolean { 10 | return oldItem == newItem 11 | } 12 | 13 | } -------------------------------------------------------------------------------- /shows/shows_details_presentation/src/main/java/com/raqun/movies/shows/details/presentation/TvShowsDetailsFragmentModule.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.shows.details.presentation 2 | 3 | import com.raqun.movies.core.injection.scope.FragmentScope 4 | import dagger.Module 5 | import dagger.android.ContributesAndroidInjector 6 | 7 | @Module 8 | abstract class TvShowsDetailsFragmentModule { 9 | @FragmentScope 10 | @ContributesAndroidInjector 11 | abstract fun contributeTvShowDetailsFragmentInjector(): TvShowDetailsFragment 12 | } -------------------------------------------------------------------------------- /base/core_navigation/src/test/java/com/raqun/movies/core/navigation/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.navigation; 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 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/movies/injection/modules/ApplicationModule.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.injection.modules 2 | 3 | import android.content.Context 4 | import com.raqun.movies.MoviesApp 5 | import com.raqun.movies.core.injection.modules.CoreModule 6 | import dagger.Module 7 | import dagger.Provides 8 | 9 | @Module( 10 | includes = [CoreModule::class] 11 | ) 12 | class ApplicationModule { 13 | 14 | @Provides 15 | fun provideApplicationContext(app: MoviesApp): Context { 16 | return app.applicationContext 17 | } 18 | } -------------------------------------------------------------------------------- /base/core_presentation/src/test/java/com/raqun/movies/core/presentation/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.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 | } -------------------------------------------------------------------------------- /shows/shows_domain/src/main/java/com/raqun/movies/shows/domain/TvShowDetail.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.shows.domain 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class TvShowDetail( 6 | @SerializedName("id") val id: Int?, 7 | @SerializedName("name") val name: String?, 8 | @SerializedName("vote_average") val votesAverage: Number?, 9 | @SerializedName("poster_path") val posterPath: String?, 10 | @SerializedName("overview") val overview: String, 11 | @SerializedName("vote_count") val voteCount: Int? 12 | ) -------------------------------------------------------------------------------- /shows/shows_domain/src/test/java/com/raqun/movies/shows/domain/util/DataProvider.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.shows.domain.util 2 | 3 | import com.raqun.movies.shows.domain.TvShow 4 | 5 | class DataProvider { 6 | companion object { 7 | fun createDummyTvShows(count: Int): List { 8 | val dummyTvSHows = ArrayList() 9 | for (i in 0..count) { 10 | dummyTvSHows.add(TvShow(i, "Tv Show $i", i, null)) 11 | } 12 | 13 | return dummyTvSHows 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /base/core_navigation/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "$rootDir/common-android-library.gradle" 2 | 3 | android { 4 | flavorDimensions Dimensions.default 5 | productFlavors { 6 | prod { 7 | buildConfigField("String", Fields.pName, "\"$Prod.packageName\"") 8 | } 9 | 10 | dev { 11 | buildConfigField("String", Fields.pName, "\"$Dev.packageName\"") 12 | } 13 | } 14 | } 15 | 16 | 17 | dependencies { 18 | implementation SupportLibraries.appCompat 19 | implementation project(Modules.core) 20 | } -------------------------------------------------------------------------------- /shows/shows_presentation/src/main/java/com/raqun/movie/shows/presentation/PopularTvShowsFragmentModule.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movie.shows.presentation 2 | 3 | import com.raqun.movies.core.injection.scope.FragmentScope 4 | import dagger.Module 5 | import dagger.android.ContributesAndroidInjector 6 | 7 | @Module 8 | abstract class PopularTvShowsFragmentModule { 9 | @FragmentScope 10 | @ContributesAndroidInjector(modules = [PopularTvShowsPresentationModule::class]) 11 | abstract fun contributeShowsFragmentInjector(): PopularTvShowsFragment 12 | } -------------------------------------------------------------------------------- /shows/shows_presentation/src/test/java/com/raqun/movie/shows/presentation/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.raqun.movie.shows.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 | } -------------------------------------------------------------------------------- /base/core_domain/src/main/java/com/raqun/movies/core/domain/Interactor.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.domain 2 | 3 | import io.reactivex.Flowable 4 | import io.reactivex.Observable 5 | 6 | interface Interactor { 7 | 8 | interface ReactiveRetrieveInteractor

: Interactor { 9 | fun execute(params: P): Observable 10 | } 11 | 12 | interface FlowableRetrieveInteractor

: Interactor { 13 | fun execute(params: P): Flowable 14 | } 15 | // marker class 16 | abstract class Params 17 | } -------------------------------------------------------------------------------- /shows/shows_details_presentation/src/main/java/com/raqun/movies/shows/details/presentation/TvShowDetailsActivityModule.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.shows.details.presentation 2 | 3 | import com.raqun.movies.core.injection.scope.ActivityScope 4 | import dagger.Module 5 | import dagger.android.ContributesAndroidInjector 6 | 7 | @Module 8 | abstract class TvShowDetailsActivityModule { 9 | @ActivityScope 10 | @ContributesAndroidInjector(modules = [TvShowsDetailsFragmentModule::class]) 11 | abstract fun provideTvShowsDetailsActivity(): TvShowDetailsActivity 12 | } -------------------------------------------------------------------------------- /shows/shows_details_presentation/src/test/java/com/raqun/movies/shows/details/presentation/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.shows.details.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 | } -------------------------------------------------------------------------------- /shows/shows_presentation/src/main/java/com/raqun/movie/shows/presentation/PopularTvShowViewEntity.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movie.shows.presentation 2 | 3 | import com.raqun.movies.core.presentation.entity.ViewEntity 4 | import com.raqun.movies.core.presentation.recyclerview.DisplayItem 5 | import java.lang.NumberFormatException 6 | 7 | class PopularTvShowViewEntity( 8 | val id: Int?, 9 | val name: String?, 10 | val rating: Number?, 11 | val picture: String? 12 | ) : ViewEntity, DisplayItem { 13 | 14 | override fun type() = PopularTvShowsPresentationConstants.TYPES.SHOW 15 | } -------------------------------------------------------------------------------- /base/core_navigation/src/main/java/com/raqun/movies/core/navigation/Loader.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.navigation 2 | 3 | const val PACKAGE_NAME = "com.raqun.movies" 4 | 5 | private val classMap = mutableMapOf>() 6 | 7 | private inline fun Any.castOrReturnNull() = this as? T 8 | 9 | internal fun String.loadClassOrReturnNull(): Class? = 10 | classMap.getOrPut(this) { 11 | try { 12 | Class.forName(this) 13 | } catch (e: ClassNotFoundException) { 14 | return null 15 | } 16 | }.castOrReturnNull() -------------------------------------------------------------------------------- /base/core_presentation/src/main/java/com/raqun/movies/core/presentation/recyclerview/ViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.presentation.recyclerview 2 | 3 | import android.view.View 4 | import androidx.recyclerview.widget.RecyclerView 5 | import com.raqun.movies.core.presentation.entity.ViewEntity 6 | 7 | abstract class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 8 | 9 | var itemClickListener: ((item: DisplayItem) -> Unit)? = null 10 | var itemLongClickListener: ((item: DisplayItem) -> Boolean)? = null 11 | 12 | abstract fun bind(item: T) 13 | } -------------------------------------------------------------------------------- /shows/shows_data/src/main/java/com/raqun/movies/shows/data/TvShowMapper.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.shows.data 2 | 3 | import com.raqun.movies.core.data.db.entity.TvShowEntity 4 | import com.raqun.movies.shows.domain.TvShow 5 | import io.reactivex.functions.Function 6 | 7 | class TvShowMapper : Function { 8 | 9 | override fun apply(t: TvShowEntity): TvShow { 10 | return TvShow( 11 | t.tvShowId.toInt(), 12 | t.name, 13 | t.voteAvarage, 14 | t.posterPath, 15 | t.popularity 16 | ) 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /base/core_data/src/main/java/com/raqun/movies/core/data/injection/modules/DbModule.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.data.injection.modules 2 | 3 | import android.content.Context 4 | import androidx.room.Room 5 | import com.raqun.movies.core.data.db.Db 6 | import com.raqun.movies.core.data.db.MoviesDb 7 | import dagger.Module 8 | import dagger.Provides 9 | import javax.inject.Singleton 10 | 11 | 12 | @Module 13 | class DbModule { 14 | @Singleton 15 | @Provides 16 | fun provideDb(context: Context): MoviesDb = Room.databaseBuilder( 17 | context, 18 | MoviesDb::class.java, Db.Config.DB_NAME 19 | ).build() 20 | } -------------------------------------------------------------------------------- /shows/shows_data/src/main/java/com/raqun/movies/shows/data/TvShowEntityMapper.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.shows.data 2 | 3 | import com.raqun.movies.core.data.db.entity.TvShowEntity 4 | import com.raqun.movies.shows.domain.TvShow 5 | import io.reactivex.functions.Function 6 | 7 | class TvShowEntityMapper : Function { 8 | 9 | override fun apply(p0: TvShow): TvShowEntity { 10 | return TvShowEntity( 11 | p0.id!!.toLong(), 12 | p0.name!!, 13 | p0.posterPath!!, 14 | p0.votesAverage!!.toDouble(), 15 | p0.popularity.toInt() 16 | ) 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /base/core_presentation/src/main/java/com/raqun/movies/core/presentation/viewmodel/ReactiveViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.presentation.viewmodel 2 | 3 | import androidx.lifecycle.ViewModel 4 | import io.reactivex.disposables.CompositeDisposable 5 | import io.reactivex.disposables.Disposable 6 | 7 | abstract class ReactiveViewModel() : ViewModel() { 8 | 9 | private val compositeDisposable = CompositeDisposable() 10 | 11 | protected fun action(disposable: Disposable) { 12 | compositeDisposable.add(disposable) 13 | } 14 | 15 | override fun onCleared() { 16 | compositeDisposable.clear() 17 | super.onCleared() 18 | } 19 | } -------------------------------------------------------------------------------- /shows/shows_data/src/main/java/com/raqun/movies/shows/data/TvShowDetailMapper.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.shows.data 2 | 3 | import com.raqun.movies.core.data.db.entity.TvShowDetailEntity 4 | import com.raqun.movies.shows.domain.TvShowDetail 5 | import io.reactivex.functions.Function 6 | 7 | class TvShowDetailMapper : Function { 8 | 9 | override fun apply(t: TvShowDetailEntity): TvShowDetail { 10 | return TvShowDetail( 11 | t.tvShowId.toInt(), 12 | t.name, 13 | t.voteAvarage, 14 | t.posterPath, 15 | t.overview, 16 | t.voteCount 17 | ) 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /shows/shows_data/src/main/java/com/raqun/movies/shows/data/TvShowsServices.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.shows.data 2 | 3 | import com.raqun.movies.core.data.api.PagedApiResponse 4 | import com.raqun.movies.shows.domain.TvShow 5 | import com.raqun.movies.shows.domain.TvShowDetail 6 | import io.reactivex.Single 7 | import retrofit2.http.GET 8 | import retrofit2.http.Path 9 | import retrofit2.http.Query 10 | 11 | interface TvShowsServices { 12 | @GET("/3/tv/popular") 13 | fun getPopularTvShows(@Query("page") page: Int = 1): Single>> 14 | 15 | @GET("/3/tv/{tv_id}") 16 | fun getTvShowDetail(@Path("tv_id") id: Int): Single 17 | } -------------------------------------------------------------------------------- /base/core_data/src/main/java/com/raqun/movies/core/data/db/dao/TvShowDetailsDao.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.data.db.dao 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.OnConflictStrategy 6 | import androidx.room.Query 7 | import com.raqun.movies.core.data.db.entity.TvShowDetailEntity 8 | import io.reactivex.Flowable 9 | 10 | @Dao 11 | interface TvShowDetailsDao { 12 | @Query("SELECT * FROM tv_show_details WHERE id = :tvShowId") 13 | fun getTvShowDetail(tvShowId: Long): Flowable 14 | 15 | @Insert(onConflict = OnConflictStrategy.REPLACE) 16 | fun addTvShowDetail(tvShowDetailEntity: TvShowDetailEntity): Long 17 | } -------------------------------------------------------------------------------- /base/core_presentation/src/main/java/com/raqun/movies/core/presentation/recyclerview/DiffAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.presentation.recyclerview 2 | 3 | import androidx.recyclerview.widget.DiffUtil 4 | 5 | interface DiffAdapter { 6 | fun addItems(newItems: List) 7 | 8 | fun update(newItems: List) 9 | 10 | fun updateAllItems(newItems: List) 11 | 12 | fun updateOnlyChangedItems(newItems: List) 13 | 14 | fun updateItems(newItems: List) 15 | 16 | fun calculateDiff(newItems: List): DiffUtil.DiffResult 17 | 18 | fun updateWithOnlyDiffResult(result: DiffUtil.DiffResult) 19 | } -------------------------------------------------------------------------------- /shows/shows_details_presentation/src/main/java/com/raqun/movies/shows/details/presentation/TvShowDetailsViewEntity.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.shows.details.presentation 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.raqun.movies.core.presentation.entity.ViewEntity 5 | 6 | data class TvShowDetailsViewEntity( 7 | @SerializedName("id") val id: Int?, 8 | @SerializedName("name") val name: String?, 9 | @SerializedName("vote_average") val votesAverage: Number?, 10 | @SerializedName("poster_path") val posterPath: String?, 11 | @SerializedName("overview") val overview: String, 12 | @SerializedName("vote_count") val VoteCount: Int? 13 | ) : ViewEntity -------------------------------------------------------------------------------- /shows/shows_details_presentation/src/main/java/com/raqun/movies/shows/details/presentation/TvShowDetailsViewEntityMapper.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.shows.details.presentation 2 | 3 | import com.raqun.movies.shows.domain.TvShowDetail 4 | import io.reactivex.functions.Function 5 | 6 | class TvShowDetailsViewEntityMapper : Function { 7 | 8 | override fun apply(t: TvShowDetail): TvShowDetailsViewEntity { 9 | return TvShowDetailsViewEntity( 10 | t.id, 11 | t.name, 12 | t.votesAverage, 13 | t.posterPath, 14 | t.overview, 15 | t.voteCount 16 | ) 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /base/core_navigation/src/main/java/com/raqun/movies/core/navigation/features/TvShowDetails.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.navigation.features 2 | 3 | import android.content.Intent 4 | import com.raqun.movies.core.navigation.PACKAGE_NAME 5 | import com.raqun.movies.core.navigation.loadIntentOrReturnNull 6 | 7 | object TvShowDetails : Feature { 8 | 9 | private const val SHOW_DETAIL = "$PACKAGE_NAME.shows.details.presentation.TvShowDetailsActivity" 10 | 11 | const val BUNDLE_ID = "id" 12 | 13 | override val dynamicStart: Intent? 14 | get() = SHOW_DETAIL.loadIntentOrReturnNull() 15 | 16 | fun dynamicStart(id: Int) = dynamicStart?.putExtra(BUNDLE_ID, id) 17 | } 18 | -------------------------------------------------------------------------------- /shows/shows_data/src/main/java/com/raqun/movies/shows/data/TvShowDetailEntityMapper.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.shows.data 2 | 3 | import com.raqun.movies.core.data.db.entity.TvShowDetailEntity 4 | import com.raqun.movies.shows.domain.TvShowDetail 5 | import io.reactivex.functions.Function 6 | 7 | class TvShowDetailEntityMapper : Function { 8 | 9 | override fun apply(t: TvShowDetail): TvShowDetailEntity { 10 | return TvShowDetailEntity( 11 | t.id!!.toLong(), 12 | t.name!!, 13 | t.posterPath!!, 14 | t.votesAverage!!.toDouble(), 15 | t.overview, 16 | t.voteCount!! 17 | ) 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/movies/injection/modules/ViewModelModule.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.injection.modules 2 | 3 | import androidx.lifecycle.ViewModelProvider 4 | import com.raqun.movie.shows.presentation.PopularTvShowsViewModelModule 5 | import com.raqun.movies.core.presentation.viewmodel.VmFactory 6 | import com.raqun.movies.shows.details.presentation.TvShowDetailsViewModelModule 7 | import dagger.Binds 8 | import dagger.Module 9 | 10 | @Module( 11 | includes = [PopularTvShowsViewModelModule::class, 12 | TvShowDetailsViewModelModule::class] 13 | ) 14 | internal abstract class ViewModelModule { 15 | @Binds 16 | abstract fun bindViewModelFactory(vmFactory: VmFactory): ViewModelProvider.Factory 17 | } -------------------------------------------------------------------------------- /shows/shows_presentation/src/main/java/com/raqun/movie/shows/presentation/PopularTvShowsViewEntityMapper.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movie.shows.presentation 2 | 3 | import com.raqun.movies.core.presentation.Constants.Companion.IMAGE_BASE_URL 4 | import com.raqun.movies.core.presentation.recyclerview.DisplayItem 5 | import com.raqun.movies.shows.domain.TvShow 6 | import io.reactivex.functions.Function 7 | 8 | class PopularTvShowsViewEntityMapper : Function { 9 | 10 | override fun apply(t: TvShow): DisplayItem { 11 | return PopularTvShowViewEntity( 12 | t.id, 13 | t.name, 14 | t.votesAverage, 15 | IMAGE_BASE_URL + t.posterPath 16 | ) 17 | } 18 | } -------------------------------------------------------------------------------- /shows/shows_data/src/main/java/com/raqun/movies/shows/data/GetTvShowDetailRemoteDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.shows.data 2 | 3 | import android.annotation.SuppressLint 4 | import com.raqun.movies.core.data.source.DataSource 5 | import com.raqun.movies.shows.domain.PagedTvShows 6 | import com.raqun.movies.shows.domain.TvShowDetail 7 | import io.reactivex.Single 8 | import javax.inject.Inject 9 | 10 | class GetTvShowDetailRemoteDataSource @Inject constructor(private val tvShowsServices: TvShowsServices) : 11 | DataSource.RetrieveRemoteDataSource { 12 | 13 | @SuppressLint("CheckResult") 14 | override fun getResult(request: Int): Single = tvShowsServices.getTvShowDetail(request) 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/movies/injection/modules/ActivityModule.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.injection.modules 2 | 3 | import com.raqun.movie.shows.presentation.PopularTvShowsFragmentModule 4 | import com.raqun.movies.core.injection.scope.ActivityScope 5 | import com.raqun.movies.shows.details.presentation.TvShowDetailsActivityModule 6 | import com.raqun.movies.ui.MainActivity 7 | import dagger.Module 8 | import dagger.android.ContributesAndroidInjector 9 | 10 | @Module(includes = [TvShowDetailsActivityModule::class]) 11 | internal abstract class ActivityModule { 12 | @ContributesAndroidInjector(modules = [PopularTvShowsFragmentModule::class]) 13 | @ActivityScope 14 | abstract fun contributeMainActivityInjector(): MainActivity 15 | } -------------------------------------------------------------------------------- /base/core_data/src/main/java/com/raqun/movies/core/data/db/dao/TvShowsDao.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.data.db.dao 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.OnConflictStrategy 6 | import androidx.room.Query 7 | import com.raqun.movies.core.data.db.entity.TvShowEntity 8 | import io.reactivex.Flowable 9 | 10 | @Dao 11 | interface TvShowsDao { 12 | 13 | @Query("SELECT * FROM tv_shows ORDER BY popularity") 14 | fun getTvShows(): Flowable> 15 | 16 | @Insert(onConflict = OnConflictStrategy.REPLACE) 17 | fun addTvShow(tvShowEntity: TvShowEntity): Long 18 | 19 | @Insert(onConflict = OnConflictStrategy.REPLACE) 20 | fun addTvShows(shows: List): List 21 | } -------------------------------------------------------------------------------- /base/core_presentation/src/main/java/com/raqun/movies/core/presentation/base/BaseInjectionFragment.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.presentation.base 2 | 3 | import android.content.Context 4 | import androidx.annotation.CallSuper 5 | import dagger.android.support.AndroidSupportInjection 6 | import dagger.android.support.HasSupportFragmentInjector 7 | 8 | abstract class BaseInjectionFragment : BaseFragment() { 9 | 10 | @CallSuper 11 | override fun onAttach(context: Context?) { 12 | if (activity is HasSupportFragmentInjector) { 13 | AndroidSupportInjection.inject(this) 14 | onInject() 15 | } 16 | super.onAttach(context) 17 | } 18 | 19 | open fun onInject() { 20 | // empty for override 21 | } 22 | } -------------------------------------------------------------------------------- /shows/shows_domain/src/main/java/com/raqun/movies/shows/domain/GetPopularTvShowsInteractor.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.shows.domain 2 | 3 | import com.raqun.movies.core.domain.Interactor 4 | import io.reactivex.Flowable 5 | import javax.inject.Inject 6 | 7 | class GetPopularTvShowsInteractor @Inject constructor(private val tvShowsRepository: TvShowsRepository) : 8 | Interactor.FlowableRetrieveInteractor> { 9 | 10 | override fun execute(params: Params): Flowable> { 11 | require(params.page > 0) { "Invalid current page number" } 12 | return tvShowsRepository.getPopularTShows(params.page) 13 | } 14 | 15 | class Params(val page: Int, val totalPage: Int) : Interactor.Params() 16 | } -------------------------------------------------------------------------------- /shows/shows_domain/src/main/java/com/raqun/movies/shows/domain/ShowsDomainModule.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.shows.domain 2 | 3 | import com.raqun.movies.core.domain.Interactor 4 | import dagger.Module 5 | import dagger.Provides 6 | 7 | @Module 8 | class ShowsDomainModule { 9 | 10 | @Provides 11 | fun providePopularTvShowsInteractor(tvShowsRepository: TvShowsRepository): 12 | Interactor.FlowableRetrieveInteractor> = 13 | GetPopularTvShowsInteractor(tvShowsRepository) 14 | 15 | @Provides 16 | fun provideGetTvShowDetail(tvShowsRepository: TvShowsRepository): Interactor.ReactiveRetrieveInteractor = 17 | GetTvShowDetailInteractor(tvShowsRepository) 18 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/raqun/movies/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies 2 | 3 | import android.support.test.InstrumentationRegistry 4 | import android.support.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.raqun.movies", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /base/core_data/src/main/java/com/raqun/movies/core/data/db/MoviesDb.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.data.db 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | import com.raqun.movies.core.data.db.dao.TvShowDetailsDao 6 | import com.raqun.movies.core.data.db.dao.TvShowsDao 7 | import com.raqun.movies.core.data.db.entity.TvShowDetailEntity 8 | import com.raqun.movies.core.data.db.entity.TvShowEntity 9 | import javax.inject.Singleton 10 | 11 | @Singleton 12 | @Database( 13 | entities = [TvShowEntity::class, TvShowDetailEntity::class], 14 | version = Db.Config.DB_VERSION, 15 | exportSchema = true 16 | ) 17 | abstract class MoviesDb : RoomDatabase() { 18 | abstract fun tvShowsDao(): TvShowsDao 19 | 20 | abstract fun tvShowDetailsDao(): TvShowDetailsDao 21 | } -------------------------------------------------------------------------------- /base/core_data/src/main/java/com/raqun/movies/core/data/source/DataSource.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.data.source 2 | 3 | import io.reactivex.Flowable 4 | import io.reactivex.Single 5 | 6 | interface DataSource { 7 | 8 | interface RetrieveRemoteDataSource : DataSource { 9 | fun getResult(request: Req): Single 10 | } 11 | 12 | interface FlowableLocal : DataSource { 13 | 14 | fun get(key: K?): Flowable 15 | 16 | fun put(key: K?, data: V): Boolean 17 | 18 | fun remove(value: V): Boolean 19 | 20 | fun clear() 21 | } 22 | 23 | interface Cache : DataSource { 24 | fun get(key: KEY): VALUE? 25 | 26 | fun put(key: KEY, value: VALUE): Boolean 27 | 28 | fun drop() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /shows/shows_domain/src/main/java/com/raqun/movies/shows/domain/GetTvShowDetailInteractor.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.shows.domain 2 | 3 | import com.raqun.movies.core.domain.Interactor 4 | import io.reactivex.Observable 5 | import java.lang.IllegalArgumentException 6 | import javax.inject.Inject 7 | 8 | class GetTvShowDetailInteractor @Inject constructor(private val tvShowsRepository: TvShowsRepository) : 9 | Interactor.ReactiveRetrieveInteractor { 10 | 11 | override fun execute(params: Params): Observable { 12 | require(!(params.id == null || params.id <= 0)) { "Id cannot be null or negative!" } 13 | return tvShowsRepository.getShowDetail(params.id).toObservable() 14 | } 15 | 16 | class Params(val id: Int?) : Interactor.Params() 17 | } -------------------------------------------------------------------------------- /base/core_data/src/main/java/com/raqun/movies/core/data/db/entity/TvShowEntity.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.data.db.entity 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | import com.raqun.movies.core.data.db.Db 7 | 8 | @Entity(tableName = Db.TABLES.TVSHOWS.NAME) 9 | class TvShowEntity constructor( 10 | @PrimaryKey(autoGenerate = true) @ColumnInfo(name = Db.TABLES.TVSHOWS.COLUMNS.ID) val tvShowId: Long, 11 | @ColumnInfo(name = Db.TABLES.TVSHOWS.COLUMNS.SHOW_NAME) val name: String, 12 | @ColumnInfo(name = Db.TABLES.TVSHOWS.COLUMNS.POSTER_PATH) val posterPath: String, 13 | @ColumnInfo(name = Db.TABLES.TVSHOWS.COLUMNS.VOTE_AVARAGE) val voteAvarage: Double, 14 | @ColumnInfo(name = Db.TABLES.TVSHOWS.COLUMNS.POPULARITY) val popularity: Int 15 | ) : DbEntity -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/movies/MoviesApp.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies 2 | 3 | import android.app.Activity 4 | import android.app.Application 5 | import com.raqun.movies.injection.components.DaggerAppComponent 6 | import dagger.android.AndroidInjector 7 | import dagger.android.DispatchingAndroidInjector 8 | import dagger.android.HasActivityInjector 9 | import javax.inject.Inject 10 | 11 | class MoviesApp : Application(), HasActivityInjector { 12 | 13 | @Inject 14 | lateinit var dispachingAndroidInjector: DispatchingAndroidInjector 15 | 16 | override fun onCreate() { 17 | super.onCreate() 18 | DaggerAppComponent.builder() 19 | .application(this) 20 | .build() 21 | .inject(this) 22 | } 23 | 24 | override fun activityInjector() = dispachingAndroidInjector 25 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /base/core/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 | -------------------------------------------------------------------------------- /base/core_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 | -------------------------------------------------------------------------------- /base/core_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 | -------------------------------------------------------------------------------- /shows/shows_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 | -------------------------------------------------------------------------------- /base/core_navigation/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 | -------------------------------------------------------------------------------- /base/core_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 22 | -------------------------------------------------------------------------------- /base/core_presentation/src/main/res/layout/toolbar_default.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /shows/shows_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 | -------------------------------------------------------------------------------- /shows/shows_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 22 | -------------------------------------------------------------------------------- /shows/shows_details_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 22 | -------------------------------------------------------------------------------- /base/core_data/src/main/java/com/raqun/movies/core/data/api/DefaultRequestInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.data.api 2 | 3 | import okhttp3.Interceptor 4 | import okhttp3.Response 5 | import javax.inject.Inject 6 | 7 | private const val CONTENT_TYPE = "Content-Type" 8 | private const val JSON = "application/json" 9 | private const val KEY_API = "api_key" 10 | 11 | class DefaultRequestInterceptor @Inject constructor() : Interceptor { 12 | 13 | override fun intercept(chain: Interceptor.Chain): Response { 14 | val url = chain.request() 15 | .url() 16 | .newBuilder().addQueryParameter(KEY_API, ApiConstants.API_KEY) 17 | .build() 18 | 19 | return chain.proceed(with(chain.request().newBuilder()) { 20 | url(url) 21 | addHeader(CONTENT_TYPE, JSON) 22 | build() 23 | }) 24 | } 25 | } -------------------------------------------------------------------------------- /base/core_presentation/src/main/java/com/raqun/movies/core/presentation/base/BaseViewModelFragment.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.presentation.base 2 | 3 | import android.os.Bundle 4 | import androidx.annotation.CallSuper 5 | import androidx.lifecycle.ViewModel 6 | import androidx.lifecycle.ViewModelProvider 7 | import androidx.lifecycle.ViewModelProviders 8 | import javax.inject.Inject 9 | 10 | abstract class BaseViewModelFragment : BaseInjectionFragment() { 11 | 12 | @Inject 13 | protected lateinit var vmFactory: ViewModelProvider.Factory 14 | 15 | protected lateinit var viewModel: VM 16 | 17 | abstract fun getModelClass(): Class 18 | 19 | @CallSuper 20 | override fun onCreate(savedInstanceState: Bundle?) { 21 | super.onCreate(savedInstanceState) 22 | viewModel = ViewModelProviders.of(this, vmFactory).get(getModelClass()) 23 | } 24 | } -------------------------------------------------------------------------------- /base/core_presentation/src/main/java/com/raqun/movies/core/presentation/recyclerview/DiffUtilImpl.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.presentation.recyclerview 2 | 3 | import androidx.recyclerview.widget.DiffUtil 4 | 5 | class DiffUtilImpl( 6 | private val oldItems: List, 7 | private val newItems: List, 8 | private val comparator: DisplayItemComperator 9 | ) : DiffUtil.Callback() { 10 | 11 | override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = 12 | comparator.areItemsSame(oldItems[oldItemPosition], newItems[newItemPosition]) 13 | 14 | override fun getOldListSize() = oldItems.size 15 | 16 | override fun getNewListSize() = newItems.size 17 | 18 | override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = 19 | comparator.areContentsSame(oldItems[oldItemPosition], newItems[newItemPosition]) 20 | 21 | } -------------------------------------------------------------------------------- /shows/shows_presentation/src/main/res/layout/fragment_popular_tv_shows.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /base/core_presentation/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #0f0f0f 5 | #0e0e0e 6 | #8BC34A 7 | #212121 8 | #757575 9 | #BDBDBD 10 | #303030 11 | #0f0f0f 12 | #262626 13 | #0e0e0e 14 | #3D3B3C 15 | #FFFFFF 16 | #e5e5e5 17 | #757575 18 | 19 | -------------------------------------------------------------------------------- /base/core/src/androidTest/java/com/raqun/movies/core/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.raqun.movies.core.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/movies/injection/components/AppComponent.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.injection.components 2 | 3 | import com.raqun.movies.MoviesApp 4 | import com.raqun.movies.injection.modules.* 5 | import dagger.BindsInstance 6 | import dagger.Component 7 | import dagger.android.AndroidInjectionModule 8 | import javax.inject.Singleton 9 | 10 | @Singleton 11 | @Component( 12 | modules = [ 13 | AndroidInjectionModule::class, 14 | ApplicationModule::class, 15 | ActivityModule::class, 16 | DomainModule::class, 17 | DataModule::class, 18 | ViewModelModule::class 19 | ] 20 | ) 21 | interface AppComponent { 22 | 23 | @Component.Builder 24 | interface Builder { 25 | @BindsInstance 26 | fun application(application: MoviesApp): Builder 27 | 28 | fun build(): AppComponent 29 | } 30 | 31 | fun inject(application: MoviesApp) 32 | } -------------------------------------------------------------------------------- /base/core_data/src/androidTest/java/com/raqun/movies/core/data/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.data; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.raqun.movies.core.data.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /shows/shows_data/src/androidTest/java/com/raqun/movies/shows/data/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.shows.data; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.raqun.movies.shows.data.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /base/core_domain/src/androidTest/java/com/raqun/movies/core/domain/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.domain; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.raqun.movies.core.domain.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /base/core_navigation/src/androidTest/java/com/raqun/movies/core/navigation/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.navigation; 2 | 3 | import android.content.Context; 4 | import androidx.test.InstrumentationRegistry; 5 | import androidx.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.raqun.movies.core.navigation.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /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 | # Kotlin code style for this project: "official" or "obsolete": 15 | kotlin.code.style=official 16 | android.useAndroidX=true 17 | android.enableJetifier=true 18 | -------------------------------------------------------------------------------- /base/core_presentation/src/main/java/com/raqun/movies/core/presentation/extensions/RecyclerViewExt.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.presentation.extensions 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import androidx.recyclerview.widget.LinearLayoutManager 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.raqun.movies.core.presentation.recyclerview.RecyclerViewAdapter 8 | 9 | /* 10 | * Setups RecyclerView with Default parameters 11 | */ 12 | @SuppressLint("WrongConstant") 13 | fun RecyclerView.setup( 14 | context: Context, 15 | orientation: Int = LinearLayoutManager.VERTICAL, 16 | adapter: RecyclerViewAdapter? 17 | ) { 18 | val layoutManager = LinearLayoutManager(context) 19 | layoutManager.orientation = orientation 20 | this.layoutManager = layoutManager 21 | this.setHasFixedSize(false) 22 | adapter?.let { 23 | this.adapter = adapter 24 | } 25 | } -------------------------------------------------------------------------------- /shows/shows_domain/src/androidTest/java/com/raqun/movies/shows/domain/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.shows.domain; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.raqun.movies.shows.domain.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /base/core_presentation/src/androidTest/java/com/raqun/movies/core/presentation/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.presentation; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.raqun.movies.core.presentation.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /shows/shows_presentation/src/androidTest/java/com/raqun/movie/shows/presentation/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.raqun.movie.shows.presentation; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.raqun.movie.shows.presentation.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /base/core_data/src/main/java/com/raqun/movies/core/data/db/entity/TvShowDetailEntity.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.data.db.entity 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | import com.raqun.movies.core.data.db.Db 7 | 8 | @Entity(tableName = Db.TABLES.TVSHOW_DETAILS.NAME) 9 | class TvShowDetailEntity constructor( 10 | @PrimaryKey(autoGenerate = true) @ColumnInfo(name = Db.TABLES.TVSHOW_DETAILS.COLUMNS.ID) val tvShowId: Long, 11 | @ColumnInfo(name = Db.TABLES.TVSHOW_DETAILS.COLUMNS.SHOW_NAME) val name: String, 12 | @ColumnInfo(name = Db.TABLES.TVSHOW_DETAILS.COLUMNS.POSTER_PATH) val posterPath: String, 13 | @ColumnInfo(name = Db.TABLES.TVSHOW_DETAILS.COLUMNS.VOTE_AVARAGE) val voteAvarage: Double, 14 | @ColumnInfo(name = Db.TABLES.TVSHOW_DETAILS.COLUMNS.OVERVIEW) val overview: String, 15 | @ColumnInfo(name = Db.TABLES.TVSHOW_DETAILS.COLUMNS.VOTE_COOUNT) val voteCount: Int 16 | ) : DbEntity -------------------------------------------------------------------------------- /shows/shows_details_presentation/src/androidTest/java/com/raqun/movies/shows/details/presentation/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.shows.details.presentation; 2 | 3 | import android.content.Context; 4 | import androidx.test.InstrumentationRegistry; 5 | import androidx.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.raqun.movies.shows.details.presentation.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/movies/ui/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.ui 2 | 3 | import android.os.Bundle 4 | import com.raqun.movie.shows.presentation.PopularTvShowsFragment 5 | import com.raqun.movies.R 6 | import com.raqun.movies.core.presentation.base.BaseInjectionActivity 7 | import com.raqun.movies.core.presentation.extensions.transact 8 | import com.raqun.movies.core.presentation.navigation.UiNavigation 9 | 10 | class MainActivity : BaseInjectionActivity() { 11 | 12 | override val uiNavigation = UiNavigation.ROOT 13 | 14 | override val toolbarRes: Int = R.id.toolbarDefault 15 | 16 | override fun getLayoutRes() = R.layout.activity_main 17 | 18 | override fun onCreate(savedInstanceState: Bundle?) { 19 | super.onCreate(savedInstanceState) 20 | if (savedInstanceState == null) { 21 | supportFragmentManager.transact { 22 | replace(R.id.framelayout_main, PopularTvShowsFragment.newInstance()) 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /shows/shows_details_presentation/src/main/java/com/raqun/movies/shows/details/presentation/TvShowDetailsViewModelModule.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.shows.details.presentation 2 | 3 | import androidx.lifecycle.ViewModel 4 | import com.raqun.movies.core.presentation.viewmodel.ViewModelKey 5 | import com.raqun.movies.shows.domain.TvShowDetail 6 | import dagger.Binds 7 | import dagger.Module 8 | import dagger.Provides 9 | import dagger.multibindings.IntoMap 10 | import io.reactivex.functions.Function 11 | 12 | @Module 13 | abstract class TvShowDetailsViewModelModule { 14 | @Binds 15 | @IntoMap 16 | @ViewModelKey(TvShowDetailsViewModel::class) 17 | abstract fun bindTvShowDetailsViewModel(tvShowDetailsViewModel: TvShowDetailsViewModel): ViewModel 18 | 19 | @Module 20 | companion object { 21 | @JvmStatic 22 | @Provides 23 | fun provieTvShowDetailsViewEntityMapper(): Function = 24 | TvShowDetailsViewEntityMapper() 25 | } 26 | } -------------------------------------------------------------------------------- /shows/shows_presentation/src/main/java/com/raqun/movie/shows/presentation/PopularTvShowsViewModelModule.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movie.shows.presentation 2 | 3 | import androidx.lifecycle.ViewModel 4 | import com.raqun.movies.core.presentation.recyclerview.DisplayItem 5 | import com.raqun.movies.core.presentation.viewmodel.ViewModelKey 6 | import com.raqun.movies.shows.domain.TvShow 7 | import dagger.Binds 8 | import dagger.Module 9 | import dagger.Provides 10 | import dagger.multibindings.IntoMap 11 | import io.reactivex.functions.Function 12 | 13 | @Module 14 | abstract class PopularTvShowsViewModelModule { 15 | @Binds 16 | @IntoMap 17 | @ViewModelKey(PopularTvShowsViewModel::class) 18 | abstract fun bindShowsViewModel(popularTvShowsViewModel: PopularTvShowsViewModel): ViewModel 19 | 20 | @Module 21 | companion object { 22 | @JvmStatic 23 | @Provides 24 | fun proviePopularTvShowsViewEntityMapper(): Function = PopularTvShowsViewEntityMapper() 25 | } 26 | } -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: Plugins.androidApplication 2 | apply from: "$rootDir/common.gradle" 3 | 4 | android { 5 | flavorDimensions Dimensions.default 6 | productFlavors { 7 | prod { 8 | // empty 9 | } 10 | 11 | dev { 12 | applicationIdSuffix Dev.applicationIdSuffix 13 | versionNameSuffix Dev.versionNameSuffix 14 | } 15 | } 16 | } 17 | 18 | dependencies { 19 | 20 | // Core 21 | implementation project(Modules.core) 22 | implementation project(Modules.coreData) 23 | implementation project(Modules.coreDomain) 24 | implementation project(Modules.corePresentation) 25 | 26 | // Tv Shows 27 | implementation project(Modules.showsPresentation) 28 | implementation project(Modules.showsDomain) 29 | implementation project(Modules.showsData) 30 | implementation project(Modules.showsDetailsPresentation) 31 | 32 | // Support Libraries 33 | implementation SupportLibraries.appCompat 34 | implementation SupportLibraries.design 35 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /base/core_presentation/src/main/java/com/raqun/movies/core/presentation/base/BaseInjectionActivity.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.presentation.base 2 | 3 | import android.os.Bundle 4 | import androidx.fragment.app.Fragment 5 | import com.raqun.movies.core.presentation.base.BaseActivity 6 | import dagger.android.AndroidInjection 7 | import dagger.android.AndroidInjector 8 | import dagger.android.DispatchingAndroidInjector 9 | import dagger.android.support.HasSupportFragmentInjector 10 | import javax.inject.Inject 11 | 12 | abstract class BaseInjectionActivity : BaseActivity(), HasSupportFragmentInjector { 13 | 14 | @Inject 15 | lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector 16 | 17 | override fun supportFragmentInjector(): AndroidInjector = dispatchingAndroidInjector 18 | 19 | override fun onCreate(savedInstanceState: Bundle?) { 20 | AndroidInjection.inject(this) 21 | super.onCreate(savedInstanceState) 22 | } 23 | 24 | open fun onInject() { 25 | // empty for override 26 | } 27 | } -------------------------------------------------------------------------------- /shows/shows_data/src/main/java/com/raqun/movies/shows/data/GetPopularTvShowsRemoteDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.shows.data 2 | 3 | import android.annotation.SuppressLint 4 | import com.raqun.movies.core.data.source.DataSource 5 | import com.raqun.movies.shows.domain.PagedTvShows 6 | import io.reactivex.Single 7 | import javax.inject.Inject 8 | 9 | class GetPopularTvShowsRemoteDataSource @Inject constructor(private val tvShowsServices: TvShowsServices) : 10 | DataSource.RetrieveRemoteDataSource { 11 | 12 | @SuppressLint("CheckResult") 13 | override fun getResult(request: Int): Single = 14 | tvShowsServices.getPopularTvShows(request) 15 | .map { 16 | if (it.page == null 17 | || it.totalResults == null 18 | || it.totalPages == null 19 | || it.results == null 20 | ) { 21 | throw Exception("Unexpected response!") 22 | } 23 | return@map PagedTvShows(it.page!!, it.totalResults!!, it.totalPages!!, it.results!!) 24 | } 25 | } -------------------------------------------------------------------------------- /shows/shows_details_presentation/src/main/java/com/raqun/movies/shows/details/presentation/TvShowDetailsActivity.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.shows.details.presentation 2 | 3 | import android.os.Bundle 4 | import com.raqun.movies.core.navigation.features.TvShowDetails 5 | import com.raqun.movies.core.presentation.base.BaseInjectionActivity 6 | import com.raqun.movies.core.presentation.extensions.transact 7 | 8 | class TvShowDetailsActivity : BaseInjectionActivity() { 9 | 10 | override fun getLayoutRes() = R.layout.activity_show_details 11 | 12 | override fun onCreate(savedInstanceState: Bundle?) { 13 | super.onCreate(savedInstanceState) 14 | if (savedInstanceState == null) { 15 | supportFragmentManager.transact { 16 | replace( 17 | R.id.framelayout_main, 18 | TvShowDetailsFragment.newInstance( 19 | intent?.getIntExtra( 20 | TvShowDetails.BUNDLE_ID, 21 | 0 22 | ) 23 | ) 24 | ) 25 | } 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /base/core_presentation/src/main/java/com/raqun/movies/core/presentation/viewmodel/VmFactory.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.presentation.viewmodel 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.ViewModelProvider 5 | import javax.inject.Inject 6 | import javax.inject.Provider 7 | import javax.inject.Singleton 8 | 9 | @Singleton 10 | class VmFactory @Inject constructor(private val creators: Map, @JvmSuppressWildcards Provider>) : 11 | ViewModelProvider.Factory { 12 | 13 | @SuppressWarnings("Unchecked") 14 | override fun create(modelClass: Class): T { 15 | var creator = creators[modelClass] 16 | 17 | if (creator == null) { 18 | for (entry in creators) { 19 | if (modelClass.isAssignableFrom(entry.key)) { 20 | creator = entry.value 21 | break 22 | } 23 | } 24 | } 25 | 26 | if (creator == null) throw IllegalArgumentException("Unknown model class$modelClass") 27 | 28 | try { 29 | return creator.get() as T 30 | } catch (e: Exception) { 31 | throw RuntimeException(e) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 17 | 18 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /base/core_data/src/main/java/com/raqun/movies/core/data/db/Db.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.data.db 2 | 3 | class Db private constructor() { 4 | 5 | object Config { 6 | const val DB_NAME = "movies" 7 | const val DB_VERSION = 1 8 | const val ID = "id" 9 | const val CREATE_DATE = "create_date" 10 | const val UPDATE_DATE = "update_date" 11 | } 12 | 13 | object TABLES { 14 | object TVSHOWS { 15 | const val NAME = "tv_shows" 16 | 17 | object COLUMNS { 18 | const val ID = Config.ID 19 | const val SHOW_NAME = "name" 20 | const val VOTE_AVARAGE = "vote_avarate" 21 | const val POSTER_PATH = "poster_path" 22 | const val POPULARITY = "popularity" 23 | const val CREATE_DATE = Config.CREATE_DATE 24 | const val UPDATE_DATE = Config.UPDATE_DATE 25 | } 26 | } 27 | 28 | object TVSHOW_DETAILS { 29 | const val NAME = "tv_show_details" 30 | 31 | object COLUMNS { 32 | const val ID = Config.ID 33 | const val SHOW_NAME = "name" 34 | const val VOTE_AVARAGE = "vote_avarate" 35 | const val POSTER_PATH = "poster_path" 36 | const val OVERVIEW = "overview" 37 | const val VOTE_COOUNT = "vote_count" 38 | const val CREATE_DATE = Config.CREATE_DATE 39 | const val UPDATE_DATE = Config.UPDATE_DATE 40 | } 41 | } 42 | 43 | } 44 | } -------------------------------------------------------------------------------- /shows/shows_data/src/main/java/com/raqun/movies/shows/data/TvShowDetailsLocalDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.shows.data 2 | 3 | import com.raqun.movies.core.data.db.MoviesDb 4 | import com.raqun.movies.core.data.db.entity.TvShowDetailEntity 5 | import com.raqun.movies.core.data.source.DataSource 6 | import com.raqun.movies.shows.domain.TvShowDetail 7 | import io.reactivex.Flowable 8 | import io.reactivex.functions.Function 9 | import javax.inject.Inject 10 | 11 | class TvShowDetailsLocalDataSource @Inject constructor( 12 | private val db: MoviesDb, 13 | private val tvShowDetailMapper: Function, 14 | private val tvShowDetailEntityMapper: Function 15 | ) : DataSource.FlowableLocal { 16 | 17 | override fun get(key: Int?): Flowable { 18 | return if (key == null) { 19 | Flowable.empty() 20 | } else { 21 | db.tvShowDetailsDao().getTvShowDetail(key.toLong()).map { t: TvShowDetailEntity -> 22 | tvShowDetailMapper.apply(t) 23 | } 24 | } 25 | } 26 | 27 | override fun put(key: Int?, data: TvShowDetail): Boolean { 28 | return try { 29 | db.tvShowDetailsDao().addTvShowDetail(tvShowDetailEntityMapper.apply(data)) 30 | true 31 | } catch (e: Exception) { 32 | false 33 | } 34 | } 35 | 36 | override fun remove(value: TvShowDetail): Boolean { 37 | TODO("not implemented") //To change body of created functions use File | Settings | File Templates. 38 | } 39 | 40 | override fun clear() { 41 | TODO("not implemented") //To change body of created functions use File | Settings | File Templates. 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /shows/shows_data/src/main/java/com/raqun/movies/shows/data/TvShowsLocalDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.shows.data 2 | 3 | import com.raqun.movies.core.data.db.MoviesDb 4 | import com.raqun.movies.core.data.db.entity.TvShowEntity 5 | import com.raqun.movies.core.data.source.DataSource 6 | import com.raqun.movies.shows.domain.TvShow 7 | import io.reactivex.Flowable 8 | import io.reactivex.Observable 9 | import io.reactivex.functions.Function 10 | import javax.inject.Inject 11 | 12 | class TvShowsLocalDataSource @Inject constructor( 13 | private val db: MoviesDb, 14 | private val tvShowMapper: Function, 15 | private val tvShowEntityMapper: Function 16 | ) : DataSource.FlowableLocal> { 17 | 18 | override fun get(key: Int?): Flowable> = 19 | db.tvShowsDao().getTvShows().map { 20 | it.map { tvShowEntity -> 21 | tvShowMapper.apply(tvShowEntity) 22 | } 23 | } 24 | 25 | override fun put(key: Int?, data: List): Boolean { 26 | return try { 27 | val shows = Observable.fromIterable(data) 28 | .map { tvShow -> 29 | tvShowEntityMapper.apply(tvShow) 30 | } 31 | .toList() 32 | .blockingGet() 33 | db.tvShowsDao().addTvShows(shows) 34 | true 35 | } catch (e: Exception) { 36 | false 37 | } 38 | } 39 | 40 | override fun remove(value: List): Boolean { 41 | TODO("not implemented") //To change body of created functions use File | Settings | File Templates. 42 | } 43 | 44 | override fun clear() { 45 | TODO("not implemented") //To change body of created functions use File | Settings | File Templates. 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /shows/shows_presentation/src/main/java/com/raqun/movie/shows/presentation/PopularTvShowsPresentationModule.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movie.shows.presentation 2 | 3 | import com.raqun.movie.shows.presentation.PopularTvShowsPresentationConstants.TYPES.SHOW 4 | import com.raqun.movies.core.presentation.recyclerview.* 5 | import com.raqun.movies.shows.domain.TvShow 6 | import dagger.Binds 7 | import dagger.Module 8 | import dagger.Provides 9 | import dagger.multibindings.IntKey 10 | import dagger.multibindings.IntoMap 11 | import io.reactivex.functions.Function 12 | 13 | @Module 14 | abstract class PopularTvShowsPresentationModule { 15 | 16 | @Binds 17 | @IntoMap 18 | @IntKey(SHOW) 19 | internal abstract fun bindPopularTvShowsViewHolderFactory(viewHolderFactory: PopularTvShowsViewHolder.PopularTvShowsViewHolderFactory): ViewHolderFactory 20 | 21 | @Binds 22 | @IntoMap 23 | @IntKey(SHOW) 24 | internal abstract fun bindPopularTvShowsViewHolderBinder(viewHolderBinder: PopularTvShowsViewHolder.PopularTvShowsViewHolderBinder): ViewHolderBinder 25 | 26 | @Module 27 | companion object { 28 | 29 | @JvmStatic 30 | @Provides 31 | fun provideDisplayItemComperator(): DisplayItemComperator = DefaultDisplayItemComperator() 32 | 33 | @JvmStatic 34 | @Provides 35 | fun provideRecyclerAdapter( 36 | itemComparator: DisplayItemComperator, 37 | factoryMap: Map, 38 | binderMap: Map 39 | ): RecyclerViewAdapter { 40 | return RecyclerViewAdapter( 41 | itemComperator = itemComparator, 42 | viewHolderFactoryMap = factoryMap, 43 | viewBinderFactoryMap = binderMap 44 | ) 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /shows/shows_presentation/src/main/res/layout/item_popular_tv_show.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 13 | 14 | 17 | 18 | 21 | 22 | 26 | 27 | 32 | 33 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /base/core_presentation/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 196dp 5 | 10dp 6 | 7 | 8 | 16sp 9 | 14sp 10 | 11 | 12 | 8dp 13 | 8dp 14 | 15 | 16 | 24dp 17 | 16dp 18 | 8dp 19 | 4dp 20 | 16dp 21 | 22 | 23 | 10dp 24 | 2dp 25 | 4dp 26 | 27 | 28 | 14sp 29 | 16sp 30 | 12sp 31 | 32 | 33 | 48dp 34 | 144dp 35 | 36 | 37 | 2dp 38 | 16dp 39 | 172dp 40 | 144dp 41 | 104dp 42 | 156dp 43 | 44 | 72dp 45 | 46 | -------------------------------------------------------------------------------- /common.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: Plugins.kotlinAndroid 2 | apply plugin: Plugins.kotlinAndroidExtensions 3 | apply plugin: Plugins.kotlinKapt 4 | 5 | android { 6 | compileSdkVersion Config.compileSdkVersion 7 | 8 | defaultConfig { 9 | minSdkVersion Config.minSdkVersion 10 | targetSdkVersion Config.targetSdkVersion 11 | versionCode Config.versionCode 12 | versionName Config.versionName 13 | vectorDrawables.useSupportLibrary = true 14 | } 15 | 16 | flavorDimensions Dimensions.default 17 | productFlavors { 18 | prod { 19 | versionCode Prod.versionCode 20 | versionName Prod.versionName 21 | } 22 | 23 | dev { 24 | versionCode Dev.versionCode 25 | versionName Dev.versionName 26 | } 27 | } 28 | 29 | buildTypes { 30 | release { 31 | minifyEnabled false 32 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 33 | } 34 | 35 | debug { 36 | debuggable true 37 | } 38 | } 39 | } 40 | 41 | dependencies { 42 | implementation CoreLibraries.kotlin 43 | kapt Libraries.dagger2Compiler 44 | kapt Libraries.dagger2AndroidProcessor 45 | testAnnotationProcessor Libraries.dagger2Compiler 46 | compileOnly Libraries.javaxAnnotation 47 | kapt Libraries.roomCompiler 48 | 49 | // Testing 50 | testImplementation TestLibraries.jUnit 51 | androidTestImplementation TestLibraries.runnner 52 | androidTestImplementation TestLibraries.espressoCore 53 | testImplementation TestLibraries.mockitoCore 54 | androidTestImplementation TestLibraries.mockitoAndroid 55 | } 56 | 57 | configurations.all { 58 | resolutionStrategy.eachDependency { DependencyResolveDetails details -> 59 | def requested = details.requested 60 | if (requested.group == 'org.jetbrains.kotlin') { 61 | details.useVersion Versions.kotlinVersion 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /shows/shows_presentation/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 16 | 17 | 22 | 23 | 30 | 31 | 37 | 38 | 44 | 45 | -------------------------------------------------------------------------------- /shows/shows_data/src/main/java/com/raqun/movies/shows/data/TvShowsRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.shows.data 2 | 3 | import android.annotation.SuppressLint 4 | import com.raqun.movies.core.data.source.DataSource 5 | import com.raqun.movies.shows.domain.PagedTvShows 6 | import com.raqun.movies.shows.domain.TvShow 7 | import com.raqun.movies.shows.domain.TvShowDetail 8 | import com.raqun.movies.shows.domain.TvShowsRepository 9 | import io.reactivex.Flowable 10 | import io.reactivex.rxkotlin.subscribeBy 11 | import io.reactivex.schedulers.Schedulers 12 | import javax.inject.Inject 13 | 14 | class TvShowsRepositoryImpl @Inject constructor( 15 | private val showsRemoteDataSource: DataSource.RetrieveRemoteDataSource, 16 | private val showDetailsRemoteDataSource: DataSource.RetrieveRemoteDataSource, 17 | private val showsLocalDataSource: DataSource.FlowableLocal>, 18 | private val showDetailsLocalDataSource: DataSource.FlowableLocal 19 | ) : TvShowsRepository { 20 | 21 | @SuppressLint("CheckResult") 22 | override fun getPopularTShows(page: Int): Flowable> { 23 | showsRemoteDataSource.getResult(page) 24 | .subscribeOn(Schedulers.io()) 25 | .observeOn(Schedulers.io()) 26 | .subscribeBy(onSuccess = { 27 | showsLocalDataSource.put(null, it.results) 28 | }, onError = { 29 | // ignored 30 | }) 31 | return showsLocalDataSource.get(null) 32 | } 33 | 34 | @SuppressLint("CheckResult") 35 | override fun getShowDetail(id: Int): Flowable { 36 | showDetailsRemoteDataSource.getResult(id) 37 | .subscribeOn(Schedulers.io()) 38 | .observeOn(Schedulers.io()) 39 | .subscribeBy(onSuccess = { 40 | showDetailsLocalDataSource.put(it.id, it) 41 | }, onError = { 42 | // ignored 43 | }) 44 | 45 | return showDetailsLocalDataSource.get(id) 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /shows/shows_details_presentation/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Lorem ipsum dolor sit amet, an sit fugit officiis deserunt, audiam disputando at sit. Eu mel expetenda voluptatibus. Ius no accusam scripserit.\n\n Sea error iracundia ne, duo ne dicat partem sententiae. Nibh admodum facilisi ius in. Ius legere intellegam ei, in zril deserunt qui.At alii audire scripta mea, errem disputando quo in.\n\n Elit vocent legimus cum ei. Eu nonumes eligendi pericula sit. Ceteros omnesque facilisis id vix, at dico choro perpetua est, et dolore oblique vituperata qui. \n\nLorem ipsum dolor sit amet, an sit fugit officiis deserunt, audiam disputando at sit. Eu mel expetenda voluptatibus. Ius no accusam scripserit.\n\n Sea error iracundia ne, duo ne dicat partem sententiae. Nibh admodum facilisi ius in. Ius legere intellegam ei, in zril deserunt qui.At alii audire scripta mea, errem disputando quo in.\n\n Elit vocent legimus cum ei. Eu nonumes eligendi pericula sit. Ceteros omnesque facilisis id vix, at dico choro perpetua est, et dolore oblique vituperata qui.\n\nLorem ipsum dolor sit amet, an sit fugit officiis deserunt, audiam disputando at sit. Eu mel expetenda voluptatibus. Ius no accusam scripserit.\n\n Sea error iracundia ne, duo ne dicat partem sententiae. Nibh admodum facilisi ius in. Ius legere intellegam ei, in zril deserunt qui.At alii audire scripta mea, errem disputando quo in.\n\n Elit vocent legimus cum ei. Eu nonumes eligendi pericula sit. Ceteros omnesque facilisis id vix, at dico choro perpetua est, et dolore oblique vituperata qui. \n\nLorem ipsum dolor sit amet, an sit fugit officiis deserunt, audiam disputando at sit. Eu mel expetenda voluptatibus. Ius no accusam scripserit.\n\n Sea error iracundia ne, duo ne dicat partem sententiae. Nibh admodum facilisi ius in. Ius legere intellegam ei, in zril deserunt qui.At alii audire scripta mea, errem disputando quo in.\n\n Elit vocent legimus cum ei. Eu nonumes eligendi pericula sit. Ceteros omnesque facilisis id vix, at dico choro perpetua est, et dolore oblique vituperata qui. 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # movies 2 | An example approach for Android Application modularization and Reactive Clean architecture. 3 | 4 | ## Modularization: 5 | This repository is created to demonstrate how to implement __modular android application__ and __reactive clean architecture.__ 6 | In __Base__ directory, there are four modules: 7 | 8 | ``` 9 | - core 10 | - core_presentation 11 | - core_domain 12 | - core_data 13 | ``` 14 | 15 | __Core module__ contains classes which can be used in every layer such as injection annotations, injection scopes, error factories, 16 | data holder models. _Core presentation,_ includes core module and classes which can be used in other features presentation modules 17 | such as base ui classes, generic RecyclerView Adapter, ViewModel factories etc. __Core data__ inclues core module and domain spesific 18 | interfaces such as Interactors. Core Data also includes core module, data source interfaces, default request interceptors and 19 | api module. 20 | 21 | All features is implemented as 3 modules which are seperated by their scope. 22 | ``` 23 | - feature_presentation 24 | - feature_domain 25 | - feature_data 26 | ``` 27 | 28 | __Presentation layer,__ contains, ui classes, injection modules for ui and view entities. Presentation layers includes core_presentation 29 | module. __Domain layer__ containes feature spesific domain objects, interactor implementations and 30 | repository interace to provide a contract between data and domain layer of feature. __Data layer__ contains core_data module and 31 | other data related classes such as repository implementaions, remote local data sources etc.. 32 | 33 | ## Tech Stack: 34 | ``` 35 | - Kotlin 36 | - MVVM 37 | - Clean Architecture 38 | - Repository Pattern 39 | - RxJava 40 | - Dagger2 41 | - Retrofit 42 | - Room 43 | - Architecture Components 44 | - Lifecycle Aware Components 45 | - Modularization 46 | - Unit Testing 47 | - Mockito 48 | - Kotlin DSL 49 | - SSOT with RxJava and Room 50 | ``` 51 | 52 | ## Screenshots: 53 | 55 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /shows/shows_details_presentation/src/main/java/com/raqun/movies/shows/details/presentation/TvShowDetailsViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.shows.details.presentation 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MediatorLiveData 5 | import com.raqun.movies.core.domain.Interactor 6 | import com.raqun.movies.core.error.ErrorFactory 7 | import com.raqun.movies.core.model.DataHolder 8 | import com.raqun.movies.core.presentation.viewmodel.ReactiveViewModel 9 | import com.raqun.movies.shows.domain.GetTvShowDetailInteractor 10 | import com.raqun.movies.shows.domain.TvShowDetail 11 | import io.reactivex.functions.Function 12 | import io.reactivex.schedulers.Schedulers 13 | import javax.inject.Inject 14 | 15 | class TvShowDetailsViewModel @Inject constructor( 16 | private val getTvShowDetailsViewInteractor: Interactor.ReactiveRetrieveInteractor, 17 | private val tvShowDetailsViewentityMapper: Function, 18 | private val errorFactory: ErrorFactory 19 | ) : ReactiveViewModel() { 20 | 21 | private val _tvShowDetails = MediatorLiveData>() 22 | val tvShowDetails: LiveData> 23 | get() = _tvShowDetails 24 | 25 | fun getTvShowDetails(id: Int) { 26 | _tvShowDetails.value = DataHolder.Loading() 27 | val getTvShowDetailsParams = GetTvShowDetailInteractor.Params(id) 28 | val getTvShowsDisposable = getTvShowDetailsViewInteractor.execute(getTvShowDetailsParams) 29 | .observeOn(Schedulers.computation()) 30 | .subscribeOn(Schedulers.io()) 31 | .subscribe({ 32 | _tvShowDetails.postValue(DataHolder.Success(tvShowDetailsViewentityMapper.apply(it))) 33 | }, { 34 | _tvShowDetails.postValue( 35 | DataHolder.Fail( 36 | errorFactory.createErrorFromThrowable( 37 | it 38 | ) 39 | ) 40 | ) 41 | }) 42 | action(getTvShowsDisposable) 43 | } 44 | } -------------------------------------------------------------------------------- /base/core_data/src/main/java/com/raqun/movies/core/data/injection/modules/ApiModule.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.data.injection.modules 2 | 3 | import com.raqun.movies.core.data.BuildConfig 4 | import com.raqun.movies.core.data.api.ApiConstants 5 | import com.raqun.movies.core.data.api.DefaultRequestInterceptor 6 | import dagger.Module 7 | import dagger.Provides 8 | import okhttp3.Interceptor 9 | import okhttp3.OkHttpClient 10 | import okhttp3.logging.HttpLoggingInterceptor 11 | import retrofit2.Retrofit 12 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory 13 | import retrofit2.converter.gson.GsonConverterFactory 14 | import java.util.concurrent.TimeUnit 15 | import javax.inject.Named 16 | import javax.inject.Singleton 17 | 18 | @Module 19 | class ApiModule { 20 | 21 | @Provides 22 | @Singleton 23 | @Named(NAME_URL) 24 | fun provideBaseUrl(): String = "https://api.themoviedb.org/" 25 | 26 | @Provides 27 | @Singleton 28 | fun provideRequestInterceptor(): Interceptor = DefaultRequestInterceptor() 29 | 30 | @Provides 31 | @Singleton 32 | fun provideLoggingInterceptor(): HttpLoggingInterceptor = 33 | HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY } 34 | 35 | @Provides 36 | @Singleton 37 | fun provideOkHttpClient( 38 | requestInterceptor: DefaultRequestInterceptor, 39 | loggingInterceptor: HttpLoggingInterceptor 40 | ): OkHttpClient = 41 | with(OkHttpClient.Builder()) { 42 | addInterceptor(requestInterceptor) 43 | if (BuildConfig.DEBUG) addInterceptor(loggingInterceptor) 44 | connectTimeout(ApiConstants.TIMEOUT_INMILIS, TimeUnit.MILLISECONDS) 45 | build() 46 | } 47 | 48 | @Provides 49 | @Singleton 50 | fun provideRetrofit(@Named(NAME_URL) baseUrl: String, client: OkHttpClient): Retrofit = 51 | with(Retrofit.Builder()) { 52 | baseUrl(baseUrl) 53 | client(client) 54 | addConverterFactory(GsonConverterFactory.create()) 55 | addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 56 | build() 57 | } 58 | 59 | companion object { 60 | private const val NAME_URL = "url" 61 | } 62 | } -------------------------------------------------------------------------------- /shows/shows_presentation/src/main/java/com/raqun/movie/shows/presentation/PopularTvShowsViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movie.shows.presentation 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import android.widget.ImageView 7 | import android.widget.TextView 8 | import androidx.recyclerview.widget.RecyclerView 9 | import com.raqun.movies.core.presentation.extensions.loadImage 10 | import com.raqun.movies.core.presentation.recyclerview.DisplayItem 11 | import com.raqun.movies.core.presentation.recyclerview.ViewHolder 12 | import com.raqun.movies.core.presentation.recyclerview.ViewHolderBinder 13 | import com.raqun.movies.core.presentation.recyclerview.ViewHolderFactory 14 | import javax.inject.Inject 15 | 16 | class PopularTvShowsViewHolder private constructor(itemView: View) : 17 | ViewHolder(itemView) { 18 | 19 | private val textViewName: TextView = itemView.findViewById(R.id.textview_name) 20 | private val textViewRating: TextView = itemView.findViewById(R.id.textview_rating) 21 | private val imageviewPoster: ImageView = itemView.findViewById(R.id.imageview_poster) 22 | 23 | override fun bind(item: PopularTvShowViewEntity) { 24 | textViewName.text = item.name 25 | textViewRating.text = itemView.context.getString( 26 | R.string.avarage_rating, item.rating.toString()) 27 | item.picture?.let { 28 | imageviewPoster.loadImage(it) 29 | } 30 | itemView.setOnClickListener { 31 | itemClickListener?.invoke(item) 32 | } 33 | } 34 | 35 | internal class PopularTvShowsViewHolderFactory @Inject constructor() : ViewHolderFactory { 36 | 37 | override fun createViewHolder(parent: ViewGroup): RecyclerView.ViewHolder = 38 | PopularTvShowsViewHolder( 39 | LayoutInflater.from(parent.context).inflate( 40 | R.layout.item_popular_tv_show, 41 | parent, 42 | false 43 | ) 44 | ) 45 | } 46 | 47 | internal class PopularTvShowsViewHolderBinder @Inject constructor() : ViewHolderBinder { 48 | 49 | override fun bind(holder: RecyclerView.ViewHolder, item: DisplayItem) { 50 | (holder as PopularTvShowsViewHolder).bind(item as PopularTvShowViewEntity) 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /shows/shows_details_presentation/src/main/java/com/raqun/movies/shows/details/presentation/TvShowDetailsFragment.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.shows.details.presentation 2 | 3 | import android.os.Bundle 4 | import androidx.lifecycle.Observer 5 | import com.raqun.movies.core.model.DataHolder 6 | import com.raqun.movies.core.navigation.features.TvShowDetails 7 | import com.raqun.movies.core.presentation.Constants.Companion.IMAGE_BASE_URL 8 | import com.raqun.movies.core.presentation.base.BaseViewModelFragment 9 | import com.raqun.movies.core.presentation.extensions.loadImage 10 | import com.raqun.movies.core.presentation.navigation.UiNavigation 11 | import kotlinx.android.synthetic.main.fragment_show_details.* 12 | 13 | class TvShowDetailsFragment : BaseViewModelFragment() { 14 | 15 | private var id: Int? = null 16 | 17 | override fun getModelClass() = TvShowDetailsViewModel::class.java 18 | 19 | override fun getLayoutRes() = R.layout.fragment_show_details 20 | 21 | override val uiNavigation = UiNavigation.BACK 22 | 23 | override fun onCreate(savedInstanceState: Bundle?) { 24 | super.onCreate(savedInstanceState) 25 | id = arguments?.getInt(TvShowDetails.BUNDLE_ID, 0) 26 | } 27 | 28 | override fun onActivityCreated(savedInstanceState: Bundle?) { 29 | super.onActivityCreated(savedInstanceState) 30 | viewModel.tvShowDetails.observe(this, Observer { 31 | when (it) { 32 | is DataHolder.Success -> { 33 | toolbar.title = it.data.name 34 | textViewOverView.text = it.data.overview 35 | if (!it.data.posterPath.isNullOrEmpty()) { 36 | imageViewPoster.loadImage(IMAGE_BASE_URL + it.data.posterPath!!) 37 | } 38 | } 39 | 40 | is DataHolder.Fail -> { 41 | // handle fail 42 | } 43 | 44 | is DataHolder.Loading -> { 45 | // handle loading 46 | } 47 | } 48 | }) 49 | 50 | id?.let { 51 | viewModel.getTvShowDetails(it) 52 | } 53 | } 54 | 55 | companion object { 56 | fun newInstance(id: Int?) = TvShowDetailsFragment().apply { 57 | val args = Bundle() 58 | if (id != null) { 59 | args.putInt(TvShowDetails.BUNDLE_ID, id) 60 | } 61 | arguments = args 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /base/core_presentation/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 13 | 18 | 19 | 24 | 25 | 34 | 35 | 38 | 39 | 43 | 44 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /shows/shows_data/src/main/java/com/raqun/movies/shows/data/ShowsDataModule.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.shows.data 2 | 3 | import com.raqun.movies.core.data.db.MoviesDb 4 | import com.raqun.movies.core.data.db.entity.TvShowDetailEntity 5 | import com.raqun.movies.core.data.db.entity.TvShowEntity 6 | import com.raqun.movies.core.data.source.DataSource 7 | import com.raqun.movies.shows.domain.PagedTvShows 8 | import com.raqun.movies.shows.domain.TvShow 9 | import com.raqun.movies.shows.domain.TvShowDetail 10 | import com.raqun.movies.shows.domain.TvShowsRepository 11 | import dagger.Module 12 | import dagger.Provides 13 | import io.reactivex.functions.Function 14 | import retrofit2.Retrofit 15 | import javax.inject.Singleton 16 | 17 | @Module 18 | class ShowsDataModule { 19 | 20 | @Provides 21 | @Singleton 22 | fun provideShowsServices(retrofit: Retrofit): TvShowsServices = 23 | retrofit.create(TvShowsServices::class.java) 24 | 25 | @Provides 26 | fun provideGetPopularTvShowsRemoteDataSource(tvShowsServices: TvShowsServices): DataSource.RetrieveRemoteDataSource = 27 | GetPopularTvShowsRemoteDataSource(tvShowsServices) 28 | 29 | @Provides 30 | fun provideGetTvShowDetailsRemoteDataSource(tvShowsServices: TvShowsServices): DataSource.RetrieveRemoteDataSource = 31 | GetTvShowDetailRemoteDataSource(tvShowsServices) 32 | 33 | @Provides 34 | fun provideShowsLocalDataSource( 35 | db: MoviesDb, tvShowMapper: Function, 36 | tvShowEntityMapper: Function 37 | ): DataSource.FlowableLocal> = 38 | TvShowsLocalDataSource(db, tvShowMapper, tvShowEntityMapper) 39 | 40 | @Provides 41 | fun provideTvShowEntityMapper(): Function = TvShowEntityMapper() 42 | 43 | @Provides 44 | fun provideTvShowMapper(): Function = TvShowMapper() 45 | 46 | @Provides 47 | fun provideTvShowDetailEntityMapper(): Function = 48 | TvShowDetailEntityMapper() 49 | 50 | @Provides 51 | fun provideTvShowDetailMapper(): Function = 52 | TvShowDetailMapper() 53 | 54 | @Provides 55 | fun provideTvShowDetailsLocalDataSource( 56 | db: MoviesDb, 57 | tvShowDetailMapper: Function, 58 | tvShowEntityMapper: Function 59 | ): DataSource.FlowableLocal = 60 | TvShowDetailsLocalDataSource(db, tvShowDetailMapper, tvShowEntityMapper) 61 | 62 | @Provides 63 | @Singleton 64 | fun provideShowsRepository( 65 | getPopularTvShowsRemoteDataSource: DataSource.RetrieveRemoteDataSource, 66 | getTvShowDetailRemoteDatASource: DataSource.RetrieveRemoteDataSource, 67 | tvShowsLocalDataSource: DataSource.FlowableLocal>, 68 | tvShowDetailsLocalDataSource: DataSource.FlowableLocal 69 | ): TvShowsRepository = 70 | TvShowsRepositoryImpl( 71 | getPopularTvShowsRemoteDataSource, 72 | getTvShowDetailRemoteDatASource, 73 | tvShowsLocalDataSource, 74 | tvShowDetailsLocalDataSource 75 | ) 76 | } -------------------------------------------------------------------------------- /base/core_presentation/src/main/java/com/raqun/movies/core/presentation/recyclerview/RecyclerViewAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.presentation.recyclerview 2 | 3 | import android.annotation.SuppressLint 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.DiffUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import io.reactivex.Single 8 | import io.reactivex.android.schedulers.AndroidSchedulers 9 | import io.reactivex.schedulers.Schedulers 10 | 11 | class RecyclerViewAdapter constructor( 12 | private val items: MutableList = ArrayList(), 13 | private val itemComperator: DisplayItemComperator, 14 | private val viewHolderFactoryMap: Map, 15 | private val viewBinderFactoryMap: Map 16 | ) : RecyclerView.Adapter(), DiffAdapter { 17 | 18 | var itemClickListener: ((item: DisplayItem) -> Unit)? = null 19 | var itemLongClickListener: ((item: DisplayItem) -> Boolean)? = null 20 | 21 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder = 22 | viewHolderFactoryMap[viewType]?.createViewHolder(parent)!! 23 | 24 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { 25 | viewBinderFactoryMap[items[position].type()]?.bind(holder, items[position]) 26 | (holder as ViewHolder<*>).itemClickListener = itemClickListener 27 | holder.itemLongClickListener = itemLongClickListener 28 | } 29 | 30 | override fun getItemCount() = items.size 31 | 32 | override fun getItemViewType(position: Int) = items[position].type() 33 | 34 | override fun update(newItems: List) { 35 | if (items.isEmpty()) { 36 | updateAllItems(newItems) 37 | } else { 38 | updateOnlyChangedItems(newItems) 39 | } 40 | } 41 | 42 | override fun updateAllItems(newItems: List) { 43 | updateItems(newItems) 44 | notifyDataSetChanged() 45 | } 46 | 47 | @SuppressLint("CheckResult") 48 | override fun updateOnlyChangedItems(newItems: List) { 49 | Single.fromCallable { calculateDiffResult(newItems) } 50 | .subscribeOn(Schedulers.computation()) 51 | .observeOn(AndroidSchedulers.mainThread()) 52 | .doOnSuccess { updateItems(newItems) } 53 | .subscribe(this::updateWithOnlyDiffResult) 54 | } 55 | 56 | override fun updateItems(newItems: List) { 57 | items.clear() 58 | items.addAll(newItems) 59 | } 60 | 61 | override fun calculateDiff(newItems: List): DiffUtil.DiffResult = 62 | DiffUtil.calculateDiff( 63 | DiffUtilImpl( 64 | items, 65 | newItems, 66 | itemComperator 67 | ) 68 | ) 69 | 70 | override fun updateWithOnlyDiffResult(result: DiffUtil.DiffResult) { 71 | result.dispatchUpdatesTo(this) 72 | } 73 | 74 | override fun addItems(newItems: List) { 75 | val startRange = items.size 76 | items.addAll(newItems) 77 | notifyItemRangeChanged(startRange, newItems.size) 78 | } 79 | 80 | private fun calculateDiffResult(newItems: List): DiffUtil.DiffResult = 81 | calculateDiff(newItems) 82 | } -------------------------------------------------------------------------------- /base/core_presentation/src/main/java/com/raqun/movies/core/presentation/base/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.presentation.base 2 | 3 | import android.content.res.Resources 4 | import android.os.Bundle 5 | import android.view.Menu 6 | import android.view.MenuItem 7 | import androidx.annotation.* 8 | import androidx.appcompat.app.AppCompatActivity 9 | import androidx.appcompat.widget.Toolbar 10 | import com.raqun.movies.core.presentation.Constants 11 | import com.raqun.movies.core.presentation.R 12 | import com.raqun.movies.core.presentation.navigation.UiNavigation 13 | import dagger.android.support.HasSupportFragmentInjector 14 | 15 | abstract class BaseActivity : AppCompatActivity(), HasSupportFragmentInjector { 16 | 17 | @LayoutRes 18 | abstract fun getLayoutRes(): Int 19 | 20 | @StringRes 21 | open val titleRes = R.string.app_name 22 | 23 | @MenuRes 24 | open val menuRes = Constants.NO_RES 25 | 26 | open val uiNavigation = UiNavigation.BACK 27 | 28 | @IdRes 29 | open val toolbarRes = Constants.NO_RES 30 | 31 | @CallSuper 32 | override fun onCreate(savedInstanceState: Bundle?) { 33 | super.onCreate(savedInstanceState) 34 | setContentView(getLayoutRes()) 35 | if (toolbarRes != Constants.NO_RES) { 36 | setToolbar(findViewById(toolbarRes)) 37 | } 38 | initNavigation(uiNavigation) 39 | setScreenTitle(getString(titleRes)) 40 | } 41 | 42 | override fun onCreateOptionsMenu(menu: Menu?): Boolean { 43 | if (menuRes != Constants.NO_RES) { 44 | menuInflater.inflate(menuRes, menu) 45 | return true 46 | } 47 | 48 | return super.onCreateOptionsMenu(menu) 49 | } 50 | 51 | override fun onOptionsItemSelected(item: MenuItem?): Boolean { 52 | when (item?.itemId) { 53 | android.R.id.home -> onBackPressed() 54 | } 55 | 56 | return super.onOptionsItemSelected(item) 57 | } 58 | 59 | fun setScreenTitle(@StringRes titleRes: Int) { 60 | var title: String? = null 61 | try { 62 | title = getString(titleRes) 63 | } catch (e: Resources.NotFoundException) { 64 | // ignored 65 | } 66 | setScreenTitle(title) 67 | } 68 | 69 | fun setScreenTitle(title: String?) { 70 | supportActionBar?.title = title 71 | } 72 | 73 | fun setToolbar(toolbar: Toolbar?) { 74 | setSupportActionBar(toolbar) 75 | } 76 | 77 | fun setNavigation(uiNavigation: UiNavigation) { 78 | initNavigation(uiNavigation) 79 | } 80 | 81 | fun initNavigation(uiNavigation: UiNavigation) { 82 | when (uiNavigation) { 83 | UiNavigation.BACK -> { 84 | supportActionBar?.apply { 85 | setDisplayShowHomeEnabled(true) 86 | setHomeButtonEnabled(true) 87 | setDisplayHomeAsUpEnabled(true) 88 | } 89 | 90 | } 91 | UiNavigation.ROOT -> { 92 | supportActionBar?.apply { 93 | setDisplayShowHomeEnabled(false) 94 | setHomeButtonEnabled(false) 95 | setDisplayHomeAsUpEnabled(false) 96 | } 97 | } 98 | UiNavigation.NONE -> { 99 | // no-op 100 | } 101 | } 102 | } 103 | } -------------------------------------------------------------------------------- /shows/shows_presentation/src/main/java/com/raqun/movie/shows/presentation/PopularTvShowsFragment.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movie.shows.presentation 2 | 3 | import android.os.Bundle 4 | import android.util.Log 5 | import androidx.lifecycle.Observer 6 | import androidx.recyclerview.widget.LinearLayoutManager 7 | import androidx.recyclerview.widget.RecyclerView 8 | import com.raqun.movies.core.model.DataHolder 9 | import com.raqun.movies.core.navigation.features.TvShowDetails 10 | import com.raqun.movies.core.presentation.base.BaseViewModelFragment 11 | import com.raqun.movies.core.presentation.extensions.setup 12 | import com.raqun.movies.core.presentation.recyclerview.RecyclerViewAdapter 13 | import kotlinx.android.synthetic.main.fragment_popular_tv_shows.* 14 | import javax.inject.Inject 15 | 16 | 17 | class PopularTvShowsFragment : BaseViewModelFragment() { 18 | 19 | @Inject 20 | protected lateinit var popularTvShowsAdapter: RecyclerViewAdapter 21 | 22 | override fun getLayoutRes() = R.layout.fragment_popular_tv_shows 23 | 24 | override fun getModelClass() = PopularTvShowsViewModel::class.java 25 | 26 | private val recyclerViewOnScrollListener = object : RecyclerView.OnScrollListener() { 27 | 28 | override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { 29 | super.onScrolled(recyclerView, dx, dy) 30 | val visibleItemCount = recyclerViewPopularTvShows.childCount 31 | val totalItemCount = recyclerViewPopularTvShows.layoutManager?.itemCount ?: 0 32 | val firstVisibleItemPosition = 33 | (recyclerViewPopularTvShows.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition() 34 | 35 | if (viewModel.popularTvShowsLiveData.value !is DataHolder.Loading) { 36 | if (visibleItemCount + firstVisibleItemPosition >= totalItemCount - 5 37 | && firstVisibleItemPosition >= 0 38 | ) { 39 | viewModel.getPopularTvShowsByPagination() 40 | } 41 | } 42 | } 43 | } 44 | 45 | override fun initView() { 46 | recyclerViewPopularTvShows.apply { 47 | setup(context = activity!!, adapter = popularTvShowsAdapter) 48 | addOnScrollListener(recyclerViewOnScrollListener) 49 | } 50 | swipteToRefreshLayoutShows.setOnRefreshListener { 51 | viewModel.refreshPopularTvShows() 52 | } 53 | popularTvShowsAdapter.itemClickListener = { 54 | val showId = (it as? PopularTvShowViewEntity)?.id 55 | showId?.let { 56 | val intent = TvShowDetails.dynamicStart(showId) 57 | intent?.let { 58 | startActivity(intent) 59 | } 60 | } 61 | } 62 | } 63 | 64 | override fun onActivityCreated(savedInstanceState: Bundle?) { 65 | super.onActivityCreated(savedInstanceState) 66 | viewModel.popularTvShowsLiveData.observe(viewLifecycleOwner, Observer { 67 | swipteToRefreshLayoutShows.isRefreshing = it is DataHolder.Loading 68 | when (it) { 69 | is DataHolder.Success -> popularTvShowsAdapter.addItems(it.data) 70 | is DataHolder.Fail -> onError(it.e) 71 | is DataHolder.Loading -> Log.e("loading", "tv shows") 72 | } 73 | }) 74 | 75 | viewModel.getPopularTvShowsByPagination() 76 | } 77 | 78 | companion object { 79 | fun newInstance() = PopularTvShowsFragment() 80 | } 81 | } -------------------------------------------------------------------------------- /shows/shows_presentation/src/main/java/com/raqun/movie/shows/presentation/PopularTvShowsViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movie.shows.presentation 2 | 3 | import android.annotation.SuppressLint 4 | import androidx.lifecycle.LiveData 5 | import androidx.lifecycle.MediatorLiveData 6 | import androidx.lifecycle.MutableLiveData 7 | import com.raqun.movies.core.domain.Interactor 8 | import com.raqun.movies.core.error.ErrorFactory 9 | import com.raqun.movies.core.model.DataHolder 10 | import com.raqun.movies.core.presentation.recyclerview.DisplayItem 11 | import com.raqun.movies.core.presentation.viewmodel.ReactiveViewModel 12 | import com.raqun.movies.shows.domain.GetPopularTvShowsInteractor 13 | import com.raqun.movies.shows.domain.TvShow 14 | import io.reactivex.Observable 15 | import io.reactivex.functions.Function 16 | import io.reactivex.schedulers.Schedulers 17 | import java.util.* 18 | import javax.inject.Inject 19 | 20 | class PopularTvShowsViewModel @Inject constructor( 21 | private val getPopularTvShowsInteractor: Interactor.FlowableRetrieveInteractor>, 22 | private val itemMapper: Function, 23 | private val errorFactory: ErrorFactory 24 | ) : ReactiveViewModel() { 25 | 26 | private val _popularTvShowsLiveData = MediatorLiveData>>() 27 | private val _pageLiveData = MutableLiveData() 28 | private val popularTvShows = ArrayList() 29 | private val page = Page() 30 | 31 | val popularTvShowsLiveData: LiveData>> 32 | get() = _popularTvShowsLiveData 33 | 34 | init { 35 | _popularTvShowsLiveData.value = DataHolder.Success(popularTvShows) 36 | _popularTvShowsLiveData.addSource(_pageLiveData) { 37 | fetchPopularTvShows(it) 38 | } 39 | } 40 | 41 | fun getPopularTvShowsByPagination() { 42 | if (_pageLiveData.value == null) { 43 | _pageLiveData.value = page.currentPage 44 | } else { 45 | val nextPage = page.currentPage + 1 46 | if (nextPage > page.totalPages) { 47 | return 48 | } 49 | _pageLiveData.value = nextPage 50 | } 51 | } 52 | 53 | fun refreshPopularTvShows() { 54 | page.currentPage = 0 55 | getPopularTvShowsByPagination() 56 | } 57 | 58 | @SuppressLint("CheckResult") 59 | private fun fetchPopularTvShows(currentPage: Int) { 60 | _popularTvShowsLiveData.value = DataHolder.Loading() 61 | val pagedParams = GetPopularTvShowsInteractor.Params(currentPage, page.totalPages) 62 | val popularTvShowsFetchDisposible = getPopularTvShowsInteractor.execute(pagedParams) 63 | .observeOn(Schedulers.computation()) 64 | .subscribeOn(Schedulers.io()) 65 | .subscribe({ 66 | 67 | Observable.fromIterable(it) 68 | .map { item -> itemMapper.apply(item) } 69 | .toList() 70 | .blockingGet() 71 | .run { 72 | _popularTvShowsLiveData.postValue(DataHolder.Success(this)) 73 | popularTvShows.addAll(this) 74 | } 75 | }, { 76 | _popularTvShowsLiveData.postValue( 77 | DataHolder.Fail( 78 | errorFactory.createErrorFromThrowable( 79 | it 80 | ) 81 | ) 82 | ) 83 | }) 84 | action(popularTvShowsFetchDisposible!!) 85 | } 86 | 87 | data class Page(var currentPage: Int = 1, var totalPages: Int = 1) 88 | } -------------------------------------------------------------------------------- /shows/shows_domain/src/test/java/com/raqun/movies/shows/domain/GetPopularTvShowsInteractorTest.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.shows.domain 2 | 3 | import com.raqun.movies.shows.domain.util.DataProvider 4 | import io.reactivex.Single 5 | import org.junit.Test 6 | import org.mockito.ArgumentMatchers.anyInt 7 | import org.mockito.Mockito 8 | import java.io.IOException 9 | 10 | class GetPopularTvShowsInteractorTest { 11 | 12 | private var tvShowsRepository = Mockito.mock(TvShowsRepository::class.java) 13 | 14 | private val popularTvShowsInteractor by lazy { 15 | GetPopularTvShowsInteractor(tvShowsRepository) 16 | } 17 | 18 | /* 19 | * Tests successful response 20 | */ 21 | @Test 22 | fun testPopularTvShowsInteractor_getPopularTvShows_Success() { 23 | val currentPage = 1 24 | val totalPage = 2 25 | val pageResultCount = 10 26 | val totalResults = totalPage * pageResultCount 27 | val params = GetPopularTvShowsInteractor.PopularTvShowsParams(currentPage, totalPage) 28 | 29 | val successResponse = Single.just( 30 | PagedTvShows( 31 | currentPage, 32 | totalResults, 33 | totalPage, 34 | DataProvider.createDummyTvShows(pageResultCount) 35 | ) 36 | ) 37 | 38 | Mockito.`when`(tvShowsRepository.getPopularTShows(anyInt())) 39 | .thenReturn(successResponse) 40 | 41 | popularTvShowsInteractor.execute(params) 42 | .test() 43 | .assertSubscribed() 44 | .assertComplete() 45 | .assertNoErrors() 46 | .dispose() 47 | } 48 | 49 | /* 50 | * Test failed response 51 | */ 52 | @Test 53 | fun testPopularTvShowsInteractor_getPopularTvShows_Fail() { 54 | val errorResponse = IOException("Failed to connect to network!") 55 | val currentPage = 1 56 | val totalPage = 2 57 | val params = GetPopularTvShowsInteractor.PopularTvShowsParams(currentPage, totalPage) 58 | 59 | Mockito.`when`(tvShowsRepository.getPopularTShows(anyInt())) 60 | .thenReturn(Single.error(errorResponse)) 61 | 62 | popularTvShowsInteractor.execute(params) 63 | .test() 64 | .assertSubscribed() 65 | .assertError(errorResponse) 66 | .dispose() 67 | } 68 | 69 | /* 70 | * Test if page number is unexpected or not 71 | */ 72 | @Test 73 | fun testPopularTvShowsInteractor_getPopularTvShows_Fail_UnexpectedCurrentPageNumber() { 74 | val currentPage = 0 75 | val totalPage = 2 76 | val params = GetPopularTvShowsInteractor.PopularTvShowsParams(currentPage, totalPage) 77 | 78 | var result: java.lang.IllegalArgumentException? = null 79 | try { 80 | popularTvShowsInteractor.execute(params) 81 | .test() 82 | } catch (e: java.lang.IllegalArgumentException) { 83 | result = e 84 | } 85 | 86 | assert(result != null) 87 | } 88 | 89 | /* 90 | * Test if trying to reach above total page number 91 | */ 92 | @Test 93 | fun testPopularTvShowsInteractor_getPopularTvShows_Fail_UnexpectedCurrentPageRange() { 94 | val currentPage = 3 95 | val totalPage = 2 96 | val params = GetPopularTvShowsInteractor.PopularTvShowsParams(currentPage, totalPage) 97 | 98 | var result: java.lang.IllegalStateException? = null 99 | try { 100 | popularTvShowsInteractor.execute(params) 101 | .test() 102 | } catch (e: java.lang.IllegalStateException) { 103 | result = e 104 | } 105 | 106 | assert(result != null) 107 | } 108 | } -------------------------------------------------------------------------------- /base/core_presentation/src/main/java/com/raqun/movies/core/presentation/base/BaseFragment.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.movies.core.presentation.base 2 | 3 | import android.content.Context 4 | import android.os.Bundle 5 | import android.view.* 6 | import android.widget.Toast 7 | import androidx.annotation.IdRes 8 | import androidx.annotation.LayoutRes 9 | import androidx.annotation.MenuRes 10 | import androidx.annotation.StringRes 11 | import androidx.appcompat.widget.Toolbar 12 | import androidx.fragment.app.Fragment 13 | import com.raqun.movies.core.error.Error 14 | import com.raqun.movies.core.presentation.Constants 15 | import com.raqun.movies.core.presentation.R 16 | import com.raqun.movies.core.presentation.navigation.UiNavigation 17 | import dagger.android.support.AndroidSupportInjection 18 | import dagger.android.support.HasSupportFragmentInjector 19 | 20 | abstract class BaseFragment : Fragment(), BaseView { 21 | 22 | @LayoutRes 23 | protected abstract fun getLayoutRes(): Int 24 | 25 | @MenuRes 26 | open val menuRes = Constants.NO_RES 27 | 28 | @IdRes 29 | open val toolbarRes = Constants.NO_RES 30 | 31 | @StringRes 32 | open val titleRes = Constants.NO_RES 33 | 34 | open val uiNavigation = UiNavigation.NONE 35 | 36 | override fun onAttach(context: Context?) { 37 | if (activity is HasSupportFragmentInjector) { 38 | AndroidSupportInjection.inject(this) 39 | onInjected() 40 | } 41 | super.onAttach(context) 42 | } 43 | 44 | override fun onCreate(savedInstanceState: Bundle?) { 45 | super.onCreate(savedInstanceState) 46 | setHasOptionsMenu(menuRes != Constants.NO_RES) 47 | } 48 | 49 | override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) { 50 | super.onCreateOptionsMenu(menu, inflater) 51 | if (menuRes != Constants.NO_RES) { 52 | inflater?.inflate(menuRes, menu) 53 | } 54 | } 55 | 56 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 57 | return inflater.inflate(getLayoutRes(), null, false) 58 | } 59 | 60 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 61 | super.onViewCreated(view, savedInstanceState) 62 | if (toolbarRes != Constants.NO_RES) { 63 | val toolbar: Toolbar = view.findViewById(toolbarRes) 64 | (activity as? BaseActivity)?.setToolbar(toolbar) 65 | } 66 | if (uiNavigation != UiNavigation.NONE) { 67 | (activity as? BaseActivity)?.setNavigation(uiNavigation) 68 | } 69 | initView() 70 | } 71 | 72 | override fun onActivityCreated(savedInstanceState: Bundle?) { 73 | super.onActivityCreated(savedInstanceState) 74 | if (titleRes != Constants.NO_RES) { 75 | setActivityTitle(getString(titleRes)) 76 | } 77 | } 78 | 79 | override fun onError(e: Error) { 80 | val message = when (e) { 81 | is Error.UnknownError -> getString(R.string.error_message_unknowon_error) 82 | is Error.BusinessError -> e.message 83 | is Error.ApiError -> e.message 84 | } 85 | Toast.makeText(context, message, Toast.LENGTH_LONG).show() 86 | } 87 | 88 | protected fun setActivityTitle(title: String) { 89 | if (activity is BaseActivity) { 90 | (activity as BaseActivity).setScreenTitle(title) 91 | } 92 | } 93 | 94 | fun getApplication() = activity?.application 95 | 96 | fun getApplicationContext() = getApplication()?.applicationContext 97 | 98 | open fun initView() { 99 | // can be overridden 100 | } 101 | 102 | open fun onInjected() { 103 | // can be overridden 104 | } 105 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /shows/shows_details_presentation/src/main/res/layout/fragment_show_details.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 19 | 20 | 26 | 27 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 47 | 48 | 52 | 53 | 65 | 66 | 70 | 71 | 75 | 76 | 86 | 87 | 88 | 89 | 90 | 94 | 95 | 96 | 97 | 109 | 110 | 114 | 115 | 119 | 120 | 130 | 131 | 137 | 138 | 139 | 140 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 160 | 161 | 169 | 170 | 175 | 176 | 181 | 182 | 193 | 194 | 195 | 196 | 201 | 202 | 207 | 208 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | --------------------------------------------------------------------------------