├── .gitignore ├── .idea ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── gradle.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── isfaaghyth │ │ └── app │ │ └── jetmovie │ │ ├── ExampleInstrumentedTest.kt │ │ └── MainActivityTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── isfaaghyth │ │ │ └── app │ │ │ └── jetmovie │ │ │ ├── MainActivity.kt │ │ │ └── deeplink │ │ │ ├── AppDeepLinkModule.kt │ │ │ └── DeepLinkRouterActivity.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.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 │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── isfaaghyth │ └── app │ └── jetmovie │ └── ExampleUnitTest.kt ├── build.gradle ├── buildSrc ├── build.gradle.kts ├── build │ ├── classes │ │ └── kotlin │ │ │ └── main │ │ │ ├── Android.class │ │ │ ├── ApplicationId.class │ │ │ ├── Coroutines.class │ │ │ ├── Dagger.class │ │ │ ├── Dependencies.class │ │ │ ├── Glide.class │ │ │ ├── Jetpack.class │ │ │ ├── META-INF │ │ │ └── buildSrc.kotlin_module │ │ │ ├── Misc.class │ │ │ ├── Modules.class │ │ │ ├── Releases.class │ │ │ ├── Retrofit.class │ │ │ ├── Testing.class │ │ │ └── Version.class │ ├── kotlin │ │ ├── buildSrcjar-classes.txt │ │ └── compileKotlin │ │ │ ├── build-history.bin │ │ │ ├── caches-jvm │ │ │ ├── inputs │ │ │ │ ├── source-to-output.tab │ │ │ │ ├── source-to-output.tab.keystream │ │ │ │ ├── source-to-output.tab.keystream.len │ │ │ │ ├── source-to-output.tab.len │ │ │ │ ├── source-to-output.tab.values.at │ │ │ │ ├── source-to-output.tab_i │ │ │ │ └── source-to-output.tab_i.len │ │ │ ├── jvm │ │ │ │ └── kotlin │ │ │ │ │ ├── class-fq-name-to-source.tab │ │ │ │ │ ├── class-fq-name-to-source.tab.keystream │ │ │ │ │ ├── class-fq-name-to-source.tab.keystream.len │ │ │ │ │ ├── class-fq-name-to-source.tab.len │ │ │ │ │ ├── class-fq-name-to-source.tab.values.at │ │ │ │ │ ├── class-fq-name-to-source.tab_i │ │ │ │ │ ├── class-fq-name-to-source.tab_i.len │ │ │ │ │ ├── constants.tab │ │ │ │ │ ├── constants.tab.keystream │ │ │ │ │ ├── constants.tab.keystream.len │ │ │ │ │ ├── constants.tab.len │ │ │ │ │ ├── constants.tab.values.at │ │ │ │ │ ├── constants.tab_i │ │ │ │ │ ├── constants.tab_i.len │ │ │ │ │ ├── internal-name-to-source.tab │ │ │ │ │ ├── internal-name-to-source.tab.keystream │ │ │ │ │ ├── internal-name-to-source.tab.keystream.len │ │ │ │ │ ├── internal-name-to-source.tab.len │ │ │ │ │ ├── internal-name-to-source.tab.values.at │ │ │ │ │ ├── internal-name-to-source.tab_i │ │ │ │ │ ├── internal-name-to-source.tab_i.len │ │ │ │ │ ├── proto.tab │ │ │ │ │ ├── proto.tab.keystream │ │ │ │ │ ├── proto.tab.keystream.len │ │ │ │ │ ├── proto.tab.len │ │ │ │ │ ├── proto.tab.values.at │ │ │ │ │ ├── proto.tab_i │ │ │ │ │ ├── proto.tab_i.len │ │ │ │ │ ├── source-to-classes.tab │ │ │ │ │ ├── source-to-classes.tab.keystream │ │ │ │ │ ├── source-to-classes.tab.keystream.len │ │ │ │ │ ├── source-to-classes.tab.len │ │ │ │ │ ├── source-to-classes.tab.values.at │ │ │ │ │ ├── source-to-classes.tab_i │ │ │ │ │ └── source-to-classes.tab_i.len │ │ │ └── lookups │ │ │ │ ├── counters.tab │ │ │ │ ├── file-to-id.tab │ │ │ │ ├── file-to-id.tab.keystream │ │ │ │ ├── file-to-id.tab.keystream.len │ │ │ │ ├── file-to-id.tab.len │ │ │ │ ├── file-to-id.tab.values.at │ │ │ │ ├── file-to-id.tab_i │ │ │ │ ├── file-to-id.tab_i.len │ │ │ │ ├── id-to-file.tab │ │ │ │ ├── id-to-file.tab.keystream │ │ │ │ ├── id-to-file.tab.keystream.len │ │ │ │ ├── id-to-file.tab.len │ │ │ │ ├── id-to-file.tab.values.at │ │ │ │ ├── id-to-file.tab_i │ │ │ │ ├── id-to-file.tab_i.len │ │ │ │ ├── lookups.tab │ │ │ │ ├── lookups.tab.keystream │ │ │ │ ├── lookups.tab.keystream.len │ │ │ │ ├── lookups.tab.len │ │ │ │ ├── lookups.tab.values.at │ │ │ │ ├── lookups.tab_i │ │ │ │ └── lookups.tab_i.len │ │ │ ├── data-container-format-version.txt │ │ │ ├── format-version.txt │ │ │ ├── gradle-format-version.txt │ │ │ └── last-build.bin │ ├── libs │ │ └── buildSrc.jar │ ├── source-roots │ │ └── buildSrc │ │ │ └── source-roots.txt │ └── tmp │ │ └── jar │ │ └── MANIFEST.MF └── src │ └── main │ └── java │ ├── Dependencies.kt │ └── Modules.kt ├── common.gradle ├── data ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── isfaaghyth │ │ │ └── app │ │ │ └── data │ │ │ ├── di │ │ │ ├── DataModule.kt │ │ │ └── DataScope.kt │ │ │ ├── entity │ │ │ ├── Movie.kt │ │ │ └── TVShow.kt │ │ │ ├── repository │ │ │ ├── movie │ │ │ │ ├── MovieRepository.kt │ │ │ │ └── MovieRepositoryImpl.kt │ │ │ ├── movie_detail │ │ │ │ ├── MovieDetailRepository.kt │ │ │ │ └── MovieDetailRepositoryImpl.kt │ │ │ └── tvshow │ │ │ │ ├── TVShowRepository.kt │ │ │ │ └── TVShowRepositoryImpl.kt │ │ │ └── service │ │ │ └── NetworkServices.kt │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── isfaaghyth │ └── app │ └── data │ └── repository │ ├── MockAPIResponse.kt │ ├── NetworkMovieTest.kt │ ├── movie │ └── MovieRepositoryTest.kt │ ├── movie_detail │ └── MovieDetailRepositoryTest.kt │ └── tvshow │ └── TVShowRepositoryTest.kt ├── features ├── movie_details │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── isfaaghyth │ │ │ └── app │ │ │ └── movie_details │ │ │ └── ExampleInstrumentedTest.java │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── isfaaghyth │ │ │ │ └── app │ │ │ │ └── movie_details │ │ │ │ ├── di │ │ │ │ ├── MovieDetailComponent.kt │ │ │ │ ├── MovieDetailDeepLinkModule.kt │ │ │ │ └── MovieDetailScope.kt │ │ │ │ ├── domain │ │ │ │ └── MovieDetailUseCase.kt │ │ │ │ └── ui │ │ │ │ ├── MovieDetailActivity.kt │ │ │ │ ├── MovieDetailModule.kt │ │ │ │ ├── MovieDetailViewModel.kt │ │ │ │ └── MovieDetailViewModelModule.kt │ │ └── res │ │ │ ├── layout │ │ │ └── activity_movie_detail.xml │ │ │ └── values │ │ │ └── strings.xml │ │ └── test │ │ └── java │ │ └── isfaaghyth │ │ └── app │ │ └── movie_details │ │ ├── ExampleUnitTest.java │ │ ├── domain │ │ └── MovieDetailUseCaseTest.kt │ │ └── ui │ │ └── MovieDetailViewModelTest.kt ├── movies │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── isfaaghyth │ │ │ └── app │ │ │ └── movies │ │ │ └── ExampleInstrumentedTest.java │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── isfaaghyth │ │ │ │ └── app │ │ │ │ └── movies │ │ │ │ ├── di │ │ │ │ ├── MovieComponent.kt │ │ │ │ └── MovieScope.kt │ │ │ │ ├── domain │ │ │ │ └── MovieUseCase.kt │ │ │ │ └── ui │ │ │ │ ├── MovieAdapter.kt │ │ │ │ ├── MovieFragment.kt │ │ │ │ ├── MovieModule.kt │ │ │ │ ├── MovieViewModel.kt │ │ │ │ └── MovieViewModelModule.kt │ │ └── res │ │ │ ├── layout │ │ │ ├── fragment_movie.xml │ │ │ └── item_movie.xml │ │ │ └── values │ │ │ └── strings.xml │ │ └── test │ │ └── java │ │ └── isfaaghyth │ │ └── app │ │ └── movies │ │ ├── ExampleUnitTest.java │ │ ├── domain │ │ └── MovieUseCaseTest.kt │ │ └── ui │ │ └── MovieViewModelTest.kt └── tvshows │ ├── .gitignore │ ├── build.gradle │ ├── build │ └── generated │ │ └── source │ │ └── buildConfig │ │ └── debug │ │ └── isfaaghyth │ │ └── app │ │ └── tvshows │ │ └── BuildConfig.java │ ├── proguard-rules.pro │ └── src │ ├── androidTest │ └── java │ │ └── isfaaghyth │ │ └── app │ │ └── tvshows │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── isfaaghyth │ │ │ └── app │ │ │ └── tvshows │ │ │ ├── di │ │ │ ├── TVShowComponent.kt │ │ │ └── TVShowScope.kt │ │ │ ├── domain │ │ │ └── TVShowUseCase.kt │ │ │ └── ui │ │ │ ├── TVShowAdapter.kt │ │ │ ├── TVShowFragment.kt │ │ │ ├── TVShowModule.kt │ │ │ ├── TVShowViewModel.kt │ │ │ └── TVShowViewModelModule.kt │ └── res │ │ ├── layout │ │ ├── fragment_tvshow.xml │ │ └── item_tvshow.xml │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── isfaaghyth │ └── app │ └── tvshows │ ├── ExampleUnitTest.java │ ├── domain │ └── TVShowUseCaseTest.kt │ └── ui │ └── TVShowViewModelTest.kt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── libraries ├── abstraction │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── isfaaghyth │ │ │ └── app │ │ │ └── abstraction │ │ │ └── ExampleInstrumentedTest.java │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── ic_placeholder-web.png │ │ ├── ic_rating-web.png │ │ ├── ic_voter-web.png │ │ ├── java │ │ │ └── isfaaghyth │ │ │ │ └── app │ │ │ │ └── abstraction │ │ │ │ ├── annotation │ │ │ │ └── Testable.kt │ │ │ │ ├── base │ │ │ │ ├── BaseActivity.kt │ │ │ │ ├── BaseView.kt │ │ │ │ └── BaseViewModel.kt │ │ │ │ ├── helper │ │ │ │ └── EspressoIdlingResource.kt │ │ │ │ ├── ui │ │ │ │ └── ViewPagerAdapter.kt │ │ │ │ └── util │ │ │ │ ├── Consts.kt │ │ │ │ ├── ext │ │ │ │ ├── CoroutinesUtil.kt │ │ │ │ ├── ImageExt.kt │ │ │ │ └── UtilExt.kt │ │ │ │ ├── state │ │ │ │ ├── LoaderState.kt │ │ │ │ └── ResultState.kt │ │ │ │ ├── thread │ │ │ │ ├── ApplicationSchedulerProvider.kt │ │ │ │ ├── SchedulerProvider.kt │ │ │ │ └── TestSchedulerProvider.kt │ │ │ │ ├── view │ │ │ │ └── KeyboardUtils.kt │ │ │ │ └── viewmodel │ │ │ │ ├── ViewModelFactory.kt │ │ │ │ └── ViewModelKey.kt │ │ └── res │ │ │ ├── drawable │ │ │ └── bg_gradient.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_placeholder.png │ │ │ ├── ic_rating.png │ │ │ └── ic_voter.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_placeholder.png │ │ │ ├── ic_rating.png │ │ │ └── ic_voter.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_placeholder.png │ │ │ ├── ic_rating.png │ │ │ └── ic_voter.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_placeholder.png │ │ │ ├── ic_rating.png │ │ │ └── ic_voter.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_placeholder.png │ │ │ ├── ic_rating.png │ │ │ └── ic_voter.png │ │ │ └── values │ │ │ ├── dimens.xml │ │ │ └── strings.xml │ │ └── test │ │ └── java │ │ └── isfaaghyth │ │ └── app │ │ └── abstraction │ │ └── ExampleUnitTest.java └── network │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ ├── androidTest │ └── java │ │ └── isfaaghyth │ │ └── app │ │ └── network │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── isfaaghyth │ │ │ └── app │ │ │ └── network │ │ │ ├── Network.kt │ │ │ └── NetworkInterceptor.kt │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── isfaaghyth │ └── app │ └── network │ └── ExampleUnitTest.java └── settings.gradle /.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/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 10 | 11 | 21 | 22 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 39 | 40 | 41 | 43 | 44 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 26 | 27 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply plugin: 'kotlin-kapt' 5 | 6 | android { 7 | compileSdkVersion Releases.compileSdkVersion 8 | buildToolsVersion Releases.buildToolsVersion 9 | 10 | defaultConfig { 11 | applicationId ApplicationId.id 12 | minSdkVersion Releases.minSdkVersion 13 | targetSdkVersion Releases.targetSdkVersion 14 | versionCode Releases.versionCode 15 | versionName Releases.versionName 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | 26 | compileOptions { 27 | sourceCompatibility JavaVersion.VERSION_1_8 28 | targetCompatibility JavaVersion.VERSION_1_8 29 | } 30 | } 31 | 32 | dependencies { 33 | implementation fileTree(dir: 'libs', include: ['*.jar']) 34 | implementation Dependencies.kotlin 35 | 36 | //android 37 | implementation Android.appCompat 38 | implementation Android.ktx 39 | implementation Android.constraintLayout 40 | implementation Android.design 41 | 42 | //deeplink 43 | implementation Misc.deeplink 44 | kapt Misc.deeplinkProcessor 45 | 46 | //dependency injection 47 | implementation Dagger.dagger 48 | implementation Dagger.android 49 | kapt Dagger.compiler 50 | kapt Dagger.processor 51 | 52 | //testing 53 | testImplementation Testing.jUnit 54 | testImplementation Testing.mockito 55 | testImplementation Testing.mockKtRunner 56 | androidTestImplementation Testing.coreTesting 57 | androidTestImplementation Testing.testRunner 58 | androidTestImplementation Testing.espresso 59 | androidTestImplementation Testing.espressoContrib 60 | androidTestImplementation Testing.runner 61 | androidTestImplementation Testing.rules 62 | androidTestImplementation Testing.core 63 | androidTestImplementation Testing.espressoIdleResources 64 | androidTestImplementation Testing.extJunit 65 | androidTestImplementation Testing.extTruth 66 | 67 | /* Feature Module should add below */ 68 | implementation project(Modules.abstraction) 69 | implementation project(Modules.movieDetail) 70 | implementation project(Modules.tvshows) 71 | implementation project(Modules.movies) 72 | } 73 | 74 | repositories { 75 | google() 76 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/src/androidTest/java/isfaaghyth/app/jetmovie/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.jetmovie 2 | 3 | import androidx.test.InstrumentationRegistry 4 | import androidx.test.runner.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getTargetContext() 22 | assertEquals("isfaaghyth.app.jetmovie", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/androidTest/java/isfaaghyth/app/jetmovie/MainActivityTest.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.jetmovie 2 | 3 | import androidx.recyclerview.widget.RecyclerView 4 | import androidx.test.espresso.Espresso.onView 5 | import androidx.test.espresso.Espresso.pressBack 6 | import androidx.test.espresso.IdlingRegistry 7 | import androidx.test.espresso.action.ViewActions.click 8 | import androidx.test.espresso.action.ViewActions.swipeLeft 9 | import androidx.test.espresso.assertion.ViewAssertions.matches 10 | import androidx.test.espresso.contrib.RecyclerViewActions 11 | import androidx.test.espresso.matcher.ViewMatchers.isDisplayed 12 | import androidx.test.espresso.matcher.ViewMatchers.withId 13 | import androidx.test.ext.junit.runners.AndroidJUnit4 14 | import androidx.test.filters.LargeTest 15 | import androidx.test.rule.ActivityTestRule 16 | import isfaaghyth.app.abstraction.helper.FetchingIdlingResource 17 | import org.junit.After 18 | import org.junit.Before 19 | import org.junit.Rule 20 | import org.junit.Test 21 | import org.junit.runner.RunWith 22 | 23 | @LargeTest 24 | @RunWith(AndroidJUnit4::class) 25 | class MainActivityTest { 26 | 27 | @get:Rule 28 | val activityRule = ActivityTestRule( 29 | MainActivity::class.java, 30 | false, 31 | false) 32 | 33 | private val itemPosition = 5 34 | 35 | @Before fun setUp() { 36 | activityRule.launchActivity(null) 37 | IdlingRegistry.getInstance().register(FetchingIdlingResource.get()) 38 | } 39 | 40 | @Test fun testMovieList() { 41 | //check visibility of listMovies 42 | onView(withId(R.id.lstMovies)).check(matches(isDisplayed())) 43 | 44 | //scroll movie list into 5 45 | onView(withId(R.id.lstMovies)).perform(RecyclerViewActions.scrollToPosition(itemPosition)) 46 | 47 | //goto movieDetailActivity 48 | onView(withId(R.id.lstMovies)).perform(RecyclerViewActions.actionOnItemAtPosition(itemPosition, click())) 49 | 50 | //check visibility of image of banner 51 | onView(withId(R.id.imgBanner)).check(matches(isDisplayed())) 52 | } 53 | 54 | @Test fun testTvShowList() { 55 | onView(withId(R.id.viewpagerMain)).perform(swipeLeft()) 56 | 57 | //check visibility of listMovies 58 | onView(withId(R.id.lstTvShows)).check(matches(isDisplayed())) 59 | 60 | //scroll movie list into 5 61 | onView(withId(R.id.lstTvShows)).perform(RecyclerViewActions.scrollToPosition(itemPosition)) 62 | 63 | //goto movieDetailActivity 64 | onView(withId(R.id.lstTvShows)).perform(RecyclerViewActions.actionOnItemAtPosition(itemPosition, click())) 65 | 66 | //check visibility of image of banner 67 | onView(withId(R.id.imgBanner)).check(matches(isDisplayed())) 68 | } 69 | 70 | @After fun tearDown() { 71 | IdlingRegistry.getInstance().unregister(FetchingIdlingResource.get()) 72 | } 73 | 74 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/java/isfaaghyth/app/jetmovie/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.jetmovie 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.fragment.app.Fragment 6 | import isfaaghyth.app.abstraction.ui.ViewPagerAdapter 7 | import isfaaghyth.app.movies.ui.MovieFragment 8 | import isfaaghyth.app.tvshows.ui.TVShowFragment 9 | import kotlinx.android.synthetic.main.activity_main.* 10 | 11 | class MainActivity : AppCompatActivity() { 12 | 13 | override fun onCreate(savedInstanceState: Bundle?) { 14 | super.onCreate(savedInstanceState) 15 | setContentView(R.layout.activity_main) 16 | 17 | viewPagerSetup() 18 | } 19 | 20 | private fun viewPagerSetup() { 21 | val adapter = ViewPagerAdapter(supportFragmentManager) 22 | adapter.addFragment(MovieFragment(), "Movie") 23 | adapter.addFragment(TVShowFragment(), "TV") 24 | viewpagerMain?.adapter = adapter 25 | tabLayoutMain?.setupWithViewPager(viewpagerMain) 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/isfaaghyth/app/jetmovie/deeplink/AppDeepLinkModule.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.jetmovie.deeplink 2 | 3 | import com.airbnb.deeplinkdispatch.DeepLinkModule 4 | 5 | @DeepLinkModule class AppDeepLinkModule -------------------------------------------------------------------------------- /app/src/main/java/isfaaghyth/app/jetmovie/deeplink/DeepLinkRouterActivity.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.jetmovie.deeplink 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import com.airbnb.deeplinkdispatch.DeepLinkHandler 6 | import isfaaghyth.app.movie_details.di.MovieDetailDeepLinkModule 7 | import isfaaghyth.app.movie_details.di.MovieDetailDeepLinkModuleLoader 8 | 9 | @DeepLinkHandler( 10 | AppDeepLinkModule::class, 11 | MovieDetailDeepLinkModule::class 12 | ) 13 | class DeepLinkRouterActivity: AppCompatActivity() { 14 | override fun onCreate(savedInstanceState: Bundle?) { 15 | super.onCreate(savedInstanceState) 16 | val deepLinkDelegate = DeepLinkDelegate( 17 | AppDeepLinkModuleLoader(), 18 | MovieDetailDeepLinkModuleLoader()) 19 | deepLinkDelegate.dispatchFrom(this) 20 | finish() 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 21 | 22 | 26 | 27 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #30000000 4 | #99000000 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Jet Movie 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/isfaaghyth/app/jetmovie/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.jetmovie 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 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = '1.3.20' 5 | repositories { 6 | google() 7 | jcenter() 8 | 9 | } 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.3.1' 12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 13 | classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version" 14 | // NOTE: Do not place your application dependencies here; they belong 15 | // in the individual module build.gradle files 16 | } 17 | } 18 | 19 | allprojects { 20 | repositories { 21 | google() 22 | jcenter() 23 | mavenCentral() 24 | } 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.gradle.kotlin.dsl.`kotlin-dsl` 2 | 3 | plugins { 4 | `kotlin-dsl` 5 | } 6 | 7 | // Required since Gradle 4.10+. 8 | repositories { 9 | jcenter() 10 | } -------------------------------------------------------------------------------- /buildSrc/build/classes/kotlin/main/Android.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/classes/kotlin/main/Android.class -------------------------------------------------------------------------------- /buildSrc/build/classes/kotlin/main/ApplicationId.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/classes/kotlin/main/ApplicationId.class -------------------------------------------------------------------------------- /buildSrc/build/classes/kotlin/main/Coroutines.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/classes/kotlin/main/Coroutines.class -------------------------------------------------------------------------------- /buildSrc/build/classes/kotlin/main/Dagger.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/classes/kotlin/main/Dagger.class -------------------------------------------------------------------------------- /buildSrc/build/classes/kotlin/main/Dependencies.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/classes/kotlin/main/Dependencies.class -------------------------------------------------------------------------------- /buildSrc/build/classes/kotlin/main/Glide.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/classes/kotlin/main/Glide.class -------------------------------------------------------------------------------- /buildSrc/build/classes/kotlin/main/Jetpack.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/classes/kotlin/main/Jetpack.class -------------------------------------------------------------------------------- /buildSrc/build/classes/kotlin/main/META-INF/buildSrc.kotlin_module: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /buildSrc/build/classes/kotlin/main/Misc.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/classes/kotlin/main/Misc.class -------------------------------------------------------------------------------- /buildSrc/build/classes/kotlin/main/Modules.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/classes/kotlin/main/Modules.class -------------------------------------------------------------------------------- /buildSrc/build/classes/kotlin/main/Releases.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/classes/kotlin/main/Releases.class -------------------------------------------------------------------------------- /buildSrc/build/classes/kotlin/main/Retrofit.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/classes/kotlin/main/Retrofit.class -------------------------------------------------------------------------------- /buildSrc/build/classes/kotlin/main/Testing.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/classes/kotlin/main/Testing.class -------------------------------------------------------------------------------- /buildSrc/build/classes/kotlin/main/Version.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/classes/kotlin/main/Version.class -------------------------------------------------------------------------------- /buildSrc/build/kotlin/buildSrcjar-classes.txt: -------------------------------------------------------------------------------- 1 | /Users/nakama/AndroidStudioProjects/JetMovie/buildSrc/build/classes/kotlin/main/Android.class:/Users/nakama/AndroidStudioProjects/JetMovie/buildSrc/build/classes/kotlin/main/ApplicationId.class:/Users/nakama/AndroidStudioProjects/JetMovie/buildSrc/build/classes/kotlin/main/Coroutines.class:/Users/nakama/AndroidStudioProjects/JetMovie/buildSrc/build/classes/kotlin/main/Dagger.class:/Users/nakama/AndroidStudioProjects/JetMovie/buildSrc/build/classes/kotlin/main/Dependencies.class:/Users/nakama/AndroidStudioProjects/JetMovie/buildSrc/build/classes/kotlin/main/Glide.class:/Users/nakama/AndroidStudioProjects/JetMovie/buildSrc/build/classes/kotlin/main/Jetpack.class:/Users/nakama/AndroidStudioProjects/JetMovie/buildSrc/build/classes/kotlin/main/Misc.class:/Users/nakama/AndroidStudioProjects/JetMovie/buildSrc/build/classes/kotlin/main/Modules.class:/Users/nakama/AndroidStudioProjects/JetMovie/buildSrc/build/classes/kotlin/main/Releases.class:/Users/nakama/AndroidStudioProjects/JetMovie/buildSrc/build/classes/kotlin/main/Retrofit.class:/Users/nakama/AndroidStudioProjects/JetMovie/buildSrc/build/classes/kotlin/main/Testing.class:/Users/nakama/AndroidStudioProjects/JetMovie/buildSrc/build/classes/kotlin/main/Version.class -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/build-history.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/build-history.bin -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab.keystream: -------------------------------------------------------------------------------- 1 | S/Users/nakama/AndroidStudioProjects/JetMovie/buildSrc/src/main/java/Dependencies.kt -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab.keystream.len: -------------------------------------------------------------------------------- 1 | T -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab.len: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab.len -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab.values.at: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab.values.at -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab_i: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab_i -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab_i.len: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab_i.len -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream: -------------------------------------------------------------------------------- 1 | ApplicationIdReleasesVersionAndroidJetpack DependenciesRetrofit 2 | CoroutinesGlideDaggerTestingMisc -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream.len: -------------------------------------------------------------------------------- 1 | j -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len: -------------------------------------------------------------------------------- 1 | � -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i.len: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i.len -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab.keystream: -------------------------------------------------------------------------------- 1 | Version ApplicationIdReleases -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab.keystream.len: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab.len: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab.len -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab.values.at: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab.values.at -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab_i: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab_i -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab_i.len: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/constants.tab_i.len -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream: -------------------------------------------------------------------------------- 1 | ApplicationIdReleasesVersionAndroidJetpack DependenciesRetrofit 2 | CoroutinesGlideDaggerTestingMisc -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len: -------------------------------------------------------------------------------- 1 | j -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len: -------------------------------------------------------------------------------- 1 | � -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i.len: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i.len -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab.keystream: -------------------------------------------------------------------------------- 1 | ApplicationIdReleasesVersionAndroidJetpack DependenciesRetrofit 2 | CoroutinesGlideDaggerTestingMisc.kotlin_module -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab.keystream.len: -------------------------------------------------------------------------------- 1 | y -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab.len: -------------------------------------------------------------------------------- 1 | � -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab.values.at: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab.values.at -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab_i: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab_i -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab_i.len: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab_i.len -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream: -------------------------------------------------------------------------------- 1 | S/Users/nakama/AndroidStudioProjects/JetMovie/buildSrc/src/main/java/Dependencies.kt -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream.len: -------------------------------------------------------------------------------- 1 | T -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.len: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.len -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at: -------------------------------------------------------------------------------- 1 | /Header Record For PersistentHashMapValueStoragey ApplicationIdReleasesVersionAndroidJetpack DependenciesRetrofit 2 | CoroutinesGlideDaggerTestingMisc.kotlin_moduley ApplicationIdReleasesVersionAndroidJetpack DependenciesRetrofit 3 | CoroutinesGlideDaggerTestingMisc.kotlin_moduley ApplicationIdReleasesVersionAndroidJetpack DependenciesRetrofit 4 | CoroutinesGlideDaggerTestingMisc.kotlin_moduley ApplicationIdReleasesVersionAndroidJetpack DependenciesRetrofit 5 | CoroutinesGlideDaggerTestingMisc.kotlin_moduley ApplicationIdReleasesVersionAndroidJetpack DependenciesRetrofit 6 | CoroutinesGlideDaggerTestingMisc.kotlin_module -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab_i: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab_i -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab_i.len: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab_i.len -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/counters.tab: -------------------------------------------------------------------------------- 1 | 5 2 | 4 -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab.keystream: -------------------------------------------------------------------------------- 1 | S/Users/nakama/AndroidStudioProjects/JetMovie/buildSrc/src/main/java/Dependencies.kt -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab.keystream.len: -------------------------------------------------------------------------------- 1 | U -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab.len: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab.len -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab.values.at: -------------------------------------------------------------------------------- 1 | /Header Record For PersistentHashMapValueStorage -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab_i: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab_i -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab_i.len: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab_i.len -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab.keystream: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab.keystream.len: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab.len: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab.len -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab.values.at: -------------------------------------------------------------------------------- 1 | /Header Record For PersistentHashMapValueStorageUS/Users/nakama/AndroidStudioProjects/JetMovie/buildSrc/src/main/java/Dependencies.ktUS/Users/nakama/AndroidStudioProjects/JetMovie/buildSrc/src/main/java/Dependencies.ktUS/Users/nakama/AndroidStudioProjects/JetMovie/buildSrc/src/main/java/Dependencies.ktUS/Users/nakama/AndroidStudioProjects/JetMovie/buildSrc/src/main/java/Dependencies.ktUS/Users/nakama/AndroidStudioProjects/JetMovie/buildSrc/src/main/java/Dependencies.kt -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab_i: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab_i -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab_i.len: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab_i.len -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab.keystream: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab.keystream -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab.keystream.len: -------------------------------------------------------------------------------- 1 | � -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab.len: -------------------------------------------------------------------------------- 1 | X -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab.values.at: -------------------------------------------------------------------------------- 1 | /Header Record For PersistentHashMapValueStorage                                                          -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab_i: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab_i -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab_i.len: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab_i.len -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/data-container-format-version.txt: -------------------------------------------------------------------------------- 1 | 3011001 -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/format-version.txt: -------------------------------------------------------------------------------- 1 | 9011001 -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/gradle-format-version.txt: -------------------------------------------------------------------------------- 1 | 4011001 -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/last-build.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/kotlin/compileKotlin/last-build.bin -------------------------------------------------------------------------------- /buildSrc/build/libs/buildSrc.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/buildSrc/build/libs/buildSrc.jar -------------------------------------------------------------------------------- /buildSrc/build/source-roots/buildSrc/source-roots.txt: -------------------------------------------------------------------------------- 1 | src/main/resources 2 | src/main/java 3 | src/main/groovy 4 | src/main/kotlin 5 | src/test/resources 6 | src/test/java 7 | src/test/groovy 8 | src/test/kotlin 9 | -------------------------------------------------------------------------------- /buildSrc/build/tmp/jar/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | 3 | -------------------------------------------------------------------------------- /buildSrc/src/main/java/Modules.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by isfaaghyth on 31/07/19. 3 | * github: @isfaaghyth 4 | */ 5 | 6 | object Modules { 7 | //app level 8 | val app = ":app" 9 | 10 | //feature level 11 | val movies = ":features:movies" 12 | val tvshows = ":features:tvshows" 13 | val movieDetail = ":features:movie_details" 14 | 15 | //libraries level 16 | val network = ":libraries:network" 17 | val abstraction = ":libraries:abstraction" 18 | 19 | //data level 20 | val data = ":data" 21 | } -------------------------------------------------------------------------------- /common.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply plugin: 'kotlin-kapt' 5 | apply plugin: 'kotlin-allopen' 6 | 7 | allOpen { 8 | def openClass = ".annotation.OpenClass" 9 | annotation ApplicationId.abstraction + openClass 10 | } 11 | 12 | android { 13 | compileSdkVersion Releases.compileSdkVersion 14 | 15 | defaultConfig { 16 | minSdkVersion Releases.minSdkVersion 17 | targetSdkVersion Releases.targetSdkVersion 18 | versionCode Releases.versionCode 19 | versionName Releases.versionName 20 | 21 | buildConfigField("String", "API_KEY", movieApiKey) 22 | buildConfigField("String", "MOVIE_URL", movieApiUrl) 23 | buildConfigField("String", "IMAGE_URL", movieImageUrl) 24 | } 25 | 26 | buildTypes { 27 | debug { 28 | 29 | } 30 | release { 31 | minifyEnabled false 32 | } 33 | } 34 | 35 | compileOptions { 36 | sourceCompatibility JavaVersion.VERSION_1_8 37 | targetCompatibility JavaVersion.VERSION_1_8 38 | } 39 | } 40 | 41 | dependencies { 42 | implementation Dependencies.kotlin 43 | 44 | //di 45 | implementation Dagger.dagger 46 | implementation Dagger.android 47 | kapt Dagger.compiler 48 | kapt Dagger.processor 49 | 50 | //deeplink 51 | implementation Misc.deeplink 52 | kapt Misc.deeplinkProcessor 53 | 54 | //testing 55 | implementation Testing.espressoIdleResources 56 | 57 | testImplementation Testing.jUnit 58 | testImplementation Testing.mockito 59 | testImplementation Testing.mockKtRunner 60 | testImplementation Testing.androidX 61 | testImplementation Coroutines.core 62 | testImplementation Coroutines.android 63 | testImplementation Coroutines.test 64 | testImplementation Retrofit.mockWebServer 65 | androidTestImplementation Testing.testRunner 66 | androidTestImplementation Testing.espresso 67 | androidTestImplementation Testing.espressoContrib 68 | androidTestImplementation Testing.runner 69 | androidTestImplementation Testing.rules 70 | androidTestImplementation Testing.core 71 | androidTestImplementation Testing.espressoIdleResources 72 | androidTestImplementation Testing.extJunit 73 | androidTestImplementation Testing.extTruth 74 | } -------------------------------------------------------------------------------- /data/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /data/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "$rootDir/common.gradle" 2 | 3 | dependencies { 4 | //utilities 5 | implementation Android.gson 6 | 7 | //network 8 | implementation Retrofit.retrofit 9 | 10 | //coroutines 11 | implementation Coroutines.core 12 | 13 | //modules 14 | implementation project(Modules.network) 15 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /data/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /data/src/main/java/isfaaghyth/app/data/di/DataModule.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.data.di 2 | 3 | import dagger.Module 4 | import dagger.Provides 5 | import isfaaghyth.app.data.service.NetworkServices 6 | import isfaaghyth.app.network.Network.retrofitClient 7 | 8 | @Module class DataModule { 9 | 10 | @Provides @DataScope 11 | fun provideServices(): NetworkServices { 12 | return retrofitClient().create(NetworkServices::class.java) 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /data/src/main/java/isfaaghyth/app/data/di/DataScope.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.data.di 2 | 3 | import javax.inject.Qualifier 4 | 5 | @Qualifier annotation class DataScope -------------------------------------------------------------------------------- /data/src/main/java/isfaaghyth/app/data/entity/Movie.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.data.entity 2 | 3 | import android.os.Parcel 4 | import android.os.Parcelable 5 | import com.google.gson.annotations.Expose 6 | import com.google.gson.annotations.SerializedName 7 | import isfaaghyth.app.data.BuildConfig 8 | 9 | data class Movies( 10 | @Expose @SerializedName("results") val resultsIntent: List 11 | ) 12 | 13 | data class Movie( 14 | @Expose @SerializedName("id") val id: String, 15 | @Expose @SerializedName("movie_id") val movieId: String, 16 | @Expose @SerializedName("original_title") val title: String, 17 | @Expose @SerializedName("poster_path") val posterPath: String, 18 | @Expose @SerializedName("overview") val overview: String, 19 | @Expose @SerializedName("backdrop_path") val backdropPath: String, 20 | @Expose @SerializedName("vote_count") val voteCount: Int, 21 | @Expose @SerializedName("vote_average") val voteAverage: Float, 22 | @Expose @SerializedName("release_date") val releaseDate: String 23 | ): Parcelable { 24 | 25 | fun bannerUrl() = "${BuildConfig.IMAGE_URL}$backdropPath" 26 | 27 | fun posterUrl() = "${BuildConfig.IMAGE_URL}$posterPath" 28 | 29 | constructor(parcel: Parcel) : this( 30 | parcel.readString()?: "", 31 | parcel.readString()?: "", 32 | parcel.readString()?: "", 33 | parcel.readString()?: "", 34 | parcel.readString()?: "", 35 | parcel.readString()?: "", 36 | parcel.readInt(), 37 | parcel.readFloat(), 38 | parcel.readString()?: "" 39 | ) 40 | 41 | override fun writeToParcel(parcel: Parcel, flags: Int) { 42 | parcel.writeString(id) 43 | parcel.writeString(movieId) 44 | parcel.writeString(title) 45 | parcel.writeString(posterPath) 46 | parcel.writeString(overview) 47 | parcel.writeString(backdropPath) 48 | parcel.writeInt(voteCount) 49 | parcel.writeFloat(voteAverage) 50 | parcel.writeString(releaseDate) 51 | } 52 | 53 | override fun describeContents(): Int { 54 | return 0 55 | } 56 | 57 | companion object CREATOR : Parcelable.Creator { 58 | override fun createFromParcel(parcel: Parcel): Movie { 59 | return Movie(parcel) 60 | } 61 | 62 | override fun newArray(size: Int): Array { 63 | return arrayOfNulls(size) 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /data/src/main/java/isfaaghyth/app/data/entity/TVShow.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.data.entity 2 | 3 | import android.os.Parcel 4 | import android.os.Parcelable 5 | import com.google.gson.annotations.Expose 6 | import com.google.gson.annotations.SerializedName 7 | import isfaaghyth.app.data.BuildConfig 8 | 9 | data class TVShows( 10 | @Expose @SerializedName("results") val resultsIntent: List 11 | ) 12 | 13 | data class TVShow( 14 | @Expose @SerializedName("id") val id: String, 15 | @Expose @SerializedName("movie_id") val movieId: String, 16 | @Expose @SerializedName("original_name") val title: String, 17 | @Expose @SerializedName("poster_path") val posterPath: String, 18 | @Expose @SerializedName("overview") val overview: String, 19 | @Expose @SerializedName("backdrop_path") val backdropPath: String, 20 | @Expose @SerializedName("vote_count") val voteCount: Int, 21 | @Expose @SerializedName("vote_average") val voteAverage: Float, 22 | @Expose @SerializedName("first_air_date") val releaseDate: String 23 | ): Parcelable { 24 | 25 | fun bannerUrl() = "${BuildConfig.IMAGE_URL}$backdropPath" 26 | 27 | fun posterUrl() = "${BuildConfig.IMAGE_URL}$posterPath" 28 | 29 | constructor(parcel: Parcel) : this( 30 | parcel.readString()?: "", 31 | parcel.readString()?: "", 32 | parcel.readString()?: "", 33 | parcel.readString()?: "", 34 | parcel.readString()?: "", 35 | parcel.readString()?: "", 36 | parcel.readInt(), 37 | parcel.readFloat(), 38 | parcel.readString()?: "" 39 | ) 40 | 41 | override fun writeToParcel(parcel: Parcel, flags: Int) { 42 | parcel.writeString(id) 43 | parcel.writeString(movieId) 44 | parcel.writeString(title) 45 | parcel.writeString(posterPath) 46 | parcel.writeString(overview) 47 | parcel.writeString(backdropPath) 48 | parcel.writeInt(voteCount) 49 | parcel.writeFloat(voteAverage) 50 | parcel.writeString(releaseDate) 51 | } 52 | 53 | override fun describeContents(): Int { 54 | return 0 55 | } 56 | 57 | companion object CREATOR : Parcelable.Creator { 58 | override fun createFromParcel(parcel: Parcel): TVShow { 59 | return TVShow(parcel) 60 | } 61 | 62 | override fun newArray(size: Int): Array { 63 | return arrayOfNulls(size) 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /data/src/main/java/isfaaghyth/app/data/repository/movie/MovieRepository.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.data.repository.movie 2 | 3 | import isfaaghyth.app.data.entity.Movies 4 | import kotlinx.coroutines.Deferred 5 | import retrofit2.Response 6 | 7 | interface MovieRepository { 8 | suspend fun getPopularMovie(): Response 9 | } -------------------------------------------------------------------------------- /data/src/main/java/isfaaghyth/app/data/repository/movie/MovieRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.data.repository.movie 2 | 3 | import isfaaghyth.app.data.entity.Movies 4 | import isfaaghyth.app.data.service.NetworkServices 5 | import retrofit2.Response 6 | import javax.inject.Inject 7 | 8 | class MovieRepositoryImpl @Inject constructor( 9 | private val service: NetworkServices 10 | ): MovieRepository { 11 | 12 | override suspend fun getPopularMovie(): Response { 13 | return service.getPopularMovie() 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /data/src/main/java/isfaaghyth/app/data/repository/movie_detail/MovieDetailRepository.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.data.repository.movie_detail 2 | 3 | import isfaaghyth.app.data.entity.Movie 4 | import isfaaghyth.app.data.entity.TVShow 5 | import kotlinx.coroutines.Deferred 6 | import retrofit2.Response 7 | 8 | interface MovieDetailRepository { 9 | suspend fun getMovieDetail(movieId: String): Response 10 | suspend fun getTVShowDetail(movieId: String): Response 11 | } -------------------------------------------------------------------------------- /data/src/main/java/isfaaghyth/app/data/repository/movie_detail/MovieDetailRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.data.repository.movie_detail 2 | 3 | import isfaaghyth.app.data.entity.Movie 4 | import isfaaghyth.app.data.entity.TVShow 5 | import isfaaghyth.app.data.service.NetworkServices 6 | import retrofit2.Response 7 | import javax.inject.Inject 8 | 9 | class MovieDetailRepositoryImpl @Inject constructor( 10 | private val service: NetworkServices 11 | ): MovieDetailRepository { 12 | 13 | override suspend fun getMovieDetail(movieId: String): Response { 14 | return service.getMovieDetail(movieId) 15 | } 16 | 17 | override suspend fun getTVShowDetail(movieId: String): Response { 18 | return service.getTvDetail(movieId) 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /data/src/main/java/isfaaghyth/app/data/repository/tvshow/TVShowRepository.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.data.repository.tvshow 2 | 3 | import isfaaghyth.app.data.entity.TVShows 4 | import kotlinx.coroutines.Deferred 5 | import retrofit2.Response 6 | 7 | interface TVShowRepository { 8 | suspend fun getPopularTVShow(): Response 9 | } -------------------------------------------------------------------------------- /data/src/main/java/isfaaghyth/app/data/repository/tvshow/TVShowRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.data.repository.tvshow 2 | 3 | import isfaaghyth.app.data.entity.TVShows 4 | import isfaaghyth.app.data.service.NetworkServices 5 | import retrofit2.Response 6 | import javax.inject.Inject 7 | 8 | class TVShowRepositoryImpl @Inject constructor( 9 | private val service: NetworkServices 10 | ): TVShowRepository { 11 | 12 | override suspend fun getPopularTVShow(): Response { 13 | return service.getPopularTVShow() 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /data/src/main/java/isfaaghyth/app/data/service/NetworkServices.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.data.service 2 | 3 | import isfaaghyth.app.data.entity.Movie 4 | import isfaaghyth.app.data.entity.Movies 5 | import isfaaghyth.app.data.entity.TVShow 6 | import isfaaghyth.app.data.entity.TVShows 7 | import retrofit2.Response 8 | import retrofit2.http.GET 9 | import retrofit2.http.Path 10 | import retrofit2.http.Query 11 | 12 | interface NetworkServices { 13 | 14 | @GET("movie/popular") 15 | suspend fun getPopularMovie(): Response 16 | 17 | @GET("tv/popular") 18 | suspend fun getPopularTVShow(): Response 19 | 20 | @GET("movie/{movie_id}") 21 | suspend fun getMovieDetail( 22 | @Path("movie_id") movieId: String 23 | ): Response 24 | 25 | @GET("tv/{movie_id}") 26 | suspend fun getTvDetail( 27 | @Path("movie_id") movieId: String 28 | ): Response 29 | 30 | } -------------------------------------------------------------------------------- /data/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/src/test/java/isfaaghyth/app/data/repository/NetworkMovieTest.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.data.repository 2 | 3 | import okhttp3.mockwebserver.MockResponse 4 | import okhttp3.mockwebserver.MockWebServer 5 | import org.junit.After 6 | import org.junit.Assert.assertEquals 7 | import org.junit.Before 8 | import org.junit.Test 9 | 10 | /** 11 | * Experimental purpose 12 | */ 13 | class NetworkMovieTest { 14 | 15 | private lateinit var mockWebServer: MockWebServer 16 | 17 | private lateinit var success: MockResponse 18 | private lateinit var error: MockResponse 19 | 20 | @Before fun setUp() { 21 | mockWebServer = MockWebServer() 22 | mockWebServer.url("/") 23 | 24 | success = MockResponse() 25 | .setBody(successJson) 26 | .setResponseCode(200) 27 | 28 | error = MockResponse() 29 | .setBody(errorJson) 30 | .setResponseCode(500) 31 | } 32 | 33 | @Test fun `test success mock web server`() { 34 | mockWebServer.enqueue(success) 35 | assertEquals("HTTP/1.1 200 OK", success.status) 36 | } 37 | 38 | @Test fun `test error mock web server`() { 39 | mockWebServer.enqueue(error) 40 | assertEquals("HTTP/1.1 500 Server Error", error.status) 41 | } 42 | 43 | @After fun tearDown() { 44 | mockWebServer.shutdown() 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /data/src/test/java/isfaaghyth/app/data/repository/movie/MovieRepositoryTest.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.data.repository.movie 2 | 3 | import isfaaghyth.app.data.entity.Movie 4 | import isfaaghyth.app.data.entity.Movies 5 | import isfaaghyth.app.data.service.NetworkServices 6 | import kotlinx.coroutines.runBlocking 7 | import okhttp3.MediaType 8 | import okhttp3.ResponseBody 9 | import org.junit.After 10 | import org.junit.Assert.assertEquals 11 | import org.junit.Before 12 | import org.junit.Test 13 | import org.mockito.Mockito.* 14 | import retrofit2.Response 15 | 16 | class MovieRepositoryTest { 17 | 18 | private var services = mock(NetworkServices::class.java) 19 | private lateinit var repository: MovieRepository 20 | 21 | private val movies = listOf( 22 | Movie( 23 | "id", 24 | "movieId", 25 | "title", 26 | "posterPath", 27 | "overview", 28 | "backdrop", 29 | 0, 30 | 0f, 31 | "relateDate" 32 | ) 33 | ) 34 | 35 | @Before fun setUp() { 36 | repository = MovieRepositoryImpl(services) 37 | } 38 | 39 | @Test fun `should get popular movie success`() = runBlocking { 40 | `when`(services.getPopularMovie()).thenReturn( 41 | Response.success(Movies(movies)) 42 | ) 43 | val repo = repository.getPopularMovie() 44 | 45 | assertEquals(repo.body(), Movies(movies)) 46 | } 47 | 48 | @Test fun `should get null and error`() = runBlocking { 49 | `when`(services.getPopularMovie()).thenReturn( 50 | Response.error(401, ResponseBody.create(MediaType.parse("application/json"), "")) 51 | ) 52 | val repo = repository.getPopularMovie() 53 | 54 | assertEquals(repo.body(), null) 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /data/src/test/java/isfaaghyth/app/data/repository/movie_detail/MovieDetailRepositoryTest.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.data.repository.movie_detail 2 | 3 | import isfaaghyth.app.data.entity.Movie 4 | import isfaaghyth.app.data.entity.TVShow 5 | import isfaaghyth.app.data.service.NetworkServices 6 | import kotlinx.coroutines.runBlocking 7 | import okhttp3.MediaType 8 | import okhttp3.ResponseBody 9 | import org.junit.Assert.assertEquals 10 | import org.junit.Before 11 | import org.junit.Test 12 | import org.mockito.Mockito 13 | import org.mockito.Mockito.mock 14 | import retrofit2.Response 15 | 16 | class MovieDetailRepositoryTest { 17 | 18 | private var services = mock(NetworkServices::class.java) 19 | private lateinit var repository: MovieDetailRepository 20 | 21 | private val movieId = "1" 22 | 23 | private val tvshow = TVShow( 24 | "id", 25 | "movieId", 26 | "title", 27 | "posterPath", 28 | "overview", 29 | "backdrop", 30 | 0, 31 | 0f, 32 | "relateDate" 33 | ) 34 | 35 | private val movie = Movie( 36 | "id", 37 | "movieId", 38 | "title", 39 | "posterPath", 40 | "overview", 41 | "backdrop", 42 | 0, 43 | 0f, 44 | "relateDate" 45 | ) 46 | 47 | @Before fun setUp() { 48 | repository = MovieDetailRepositoryImpl(services) 49 | } 50 | 51 | @Test fun `should get popular tv show detail success`() = runBlocking { 52 | Mockito.`when`(services.getTvDetail(movieId)) 53 | .thenReturn( 54 | Response.success(tvshow) 55 | ) 56 | val repo = repository.getTVShowDetail(movieId) 57 | assert(repo.body() === tvshow) 58 | } 59 | 60 | @Test fun `should get popular movie detail success`() = runBlocking { 61 | Mockito.`when`(services.getMovieDetail(movieId)) 62 | .thenReturn( 63 | Response.success(movie) 64 | ) 65 | val repo = repository.getMovieDetail(movieId) 66 | assert(repo.body() === movie) 67 | } 68 | 69 | @Test fun `should get null when getting tv show detail and error`() = runBlocking { 70 | Mockito.`when`(services.getTvDetail(movieId)) 71 | .thenReturn( 72 | Response.error(401, ResponseBody.create(MediaType.parse("application/json"), "")) 73 | ) 74 | val repo = repository.getTVShowDetail(movieId) 75 | assert(repo.body() === null) 76 | } 77 | 78 | @Test fun `should get null when getting movie detail and error`() = runBlocking { 79 | Mockito.`when`(services.getMovieDetail(movieId)) 80 | .thenReturn( 81 | Response.error(401, ResponseBody.create(MediaType.parse("application/json"), "")) 82 | ) 83 | val repo = repository.getMovieDetail(movieId) 84 | assert(repo.body() === null) 85 | } 86 | 87 | } -------------------------------------------------------------------------------- /data/src/test/java/isfaaghyth/app/data/repository/tvshow/TVShowRepositoryTest.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.data.repository.tvshow 2 | 3 | import isfaaghyth.app.data.entity.TVShows 4 | import isfaaghyth.app.data.entity.TVShow 5 | import isfaaghyth.app.data.service.NetworkServices 6 | import kotlinx.coroutines.runBlocking 7 | import okhttp3.MediaType 8 | import okhttp3.ResponseBody 9 | import org.junit.After 10 | import org.junit.Assert 11 | import org.junit.Before 12 | import org.junit.Test 13 | import org.mockito.Mockito 14 | import retrofit2.Response 15 | 16 | class TVShowRepositoryTest { 17 | 18 | private var services = Mockito.mock(NetworkServices::class.java) 19 | private lateinit var repository: TVShowRepository 20 | 21 | private val tvshows = listOf( 22 | TVShow( 23 | "id", 24 | "movieId", 25 | "title", 26 | "posterPath", 27 | "overview", 28 | "backdrop", 29 | 0, 30 | 0f, 31 | "relateDate" 32 | ) 33 | ) 34 | 35 | @Before fun setUp() { 36 | repository = TVShowRepositoryImpl(services) 37 | } 38 | 39 | @Test fun `should get popular tv show success`() = runBlocking { 40 | Mockito.`when`(services.getPopularTVShow()).thenReturn( 41 | Response.success(TVShows(tvshows)) 42 | ) 43 | val repo = repository.getPopularTVShow() 44 | 45 | Assert.assertEquals(repo.body(), TVShows(tvshows)) 46 | } 47 | 48 | @Test fun `should get null and error`() = runBlocking { 49 | Mockito.`when`(services.getPopularTVShow()).thenReturn( 50 | Response.error(401, ResponseBody.create(MediaType.parse("application/json"), "")) 51 | ) 52 | val repo = repository.getPopularTVShow() 53 | 54 | Assert.assertEquals(repo.body(), null) 55 | } 56 | } -------------------------------------------------------------------------------- /features/movie_details/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /features/movie_details/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "$rootDir/common.gradle" 2 | 3 | dependencies { 4 | //android support 5 | implementation Android.appCompat 6 | implementation Android.ktx 7 | implementation Android.constraintLayout 8 | implementation Android.cardView 9 | 10 | //network 11 | implementation Retrofit.retrofit 12 | implementation Retrofit.gsonConverter 13 | implementation Retrofit.okHttpLogging 14 | implementation Retrofit.coroutinesAdapter 15 | 16 | //jetpack 17 | implementation Jetpack.recyclerView 18 | implementation Jetpack.lifecycle 19 | kapt Jetpack.lifecycleCompiler 20 | 21 | //coroutines 22 | implementation Coroutines.core 23 | implementation Coroutines.android 24 | 25 | //modules 26 | implementation project(Modules.abstraction) 27 | implementation project(Modules.data) 28 | } -------------------------------------------------------------------------------- /features/movie_details/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 | -------------------------------------------------------------------------------- /features/movie_details/src/androidTest/java/isfaaghyth/app/movie_details/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.movie_details; 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("isfaaghyth.app.movie_details.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /features/movie_details/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /features/movie_details/src/main/java/isfaaghyth/app/movie_details/di/MovieDetailComponent.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.movie_details.di 2 | 3 | import dagger.Component 4 | import isfaaghyth.app.movie_details.ui.MovieDetailActivity 5 | import isfaaghyth.app.movie_details.ui.MovieDetailModule 6 | import isfaaghyth.app.movie_details.ui.MovieDetailViewModelModule 7 | 8 | @MovieDetailScope 9 | @Component(modules = [ 10 | MovieDetailModule::class, 11 | MovieDetailViewModelModule::class 12 | ]) 13 | interface MovieDetailComponent { 14 | fun inject(activity: MovieDetailActivity) 15 | } -------------------------------------------------------------------------------- /features/movie_details/src/main/java/isfaaghyth/app/movie_details/di/MovieDetailDeepLinkModule.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.movie_details.di 2 | 3 | import com.airbnb.deeplinkdispatch.DeepLinkModule 4 | 5 | @DeepLinkModule 6 | class MovieDetailDeepLinkModule -------------------------------------------------------------------------------- /features/movie_details/src/main/java/isfaaghyth/app/movie_details/di/MovieDetailScope.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.movie_details.di 2 | 3 | import javax.inject.Scope 4 | 5 | @Scope annotation class MovieDetailScope -------------------------------------------------------------------------------- /features/movie_details/src/main/java/isfaaghyth/app/movie_details/domain/MovieDetailUseCase.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.movie_details.domain 2 | 3 | import isfaaghyth.app.abstraction.util.state.ResultState 4 | import isfaaghyth.app.abstraction.util.UNSUCCESSFUL_MESSAGE 5 | import isfaaghyth.app.abstraction.util.ext.fetchState 6 | import isfaaghyth.app.data.entity.Movie 7 | import isfaaghyth.app.data.entity.TVShow 8 | import isfaaghyth.app.data.repository.movie_detail.MovieDetailRepository 9 | import retrofit2.Response 10 | import javax.inject.Inject 11 | 12 | class MovieDetailUseCase @Inject constructor(val repository: MovieDetailRepository) { 13 | 14 | suspend fun getMovieDetail(movieId: String): ResultState { 15 | return fetchState { 16 | wrapperDetail { 17 | repository.getMovieDetail(movieId) 18 | } 19 | } 20 | } 21 | 22 | suspend fun getTvDetail(movieId: String): ResultState { 23 | return fetchState { 24 | wrapperDetail { 25 | repository.getTVShowDetail(movieId) 26 | } 27 | } 28 | } 29 | 30 | private suspend fun wrapperDetail(call: suspend () -> Response): ResultState { 31 | return if (call.invoke().isSuccessful) { 32 | ResultState.Success(call.invoke().body()!!) 33 | } else { 34 | ResultState.Error(UNSUCCESSFUL_MESSAGE) 35 | } 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /features/movie_details/src/main/java/isfaaghyth/app/movie_details/ui/MovieDetailActivity.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.movie_details.ui 2 | 3 | import androidx.lifecycle.Observer 4 | import androidx.lifecycle.ViewModelProvider 5 | import androidx.lifecycle.ViewModelProviders 6 | import com.airbnb.deeplinkdispatch.DeepLink 7 | import isfaaghyth.app.abstraction.base.BaseActivity 8 | import isfaaghyth.app.abstraction.util.AppLink.MovieDetail.MOVIE_DETAIL 9 | import isfaaghyth.app.abstraction.util.AppLink.MovieDetail.PARAM_MOVIE_ID 10 | import isfaaghyth.app.abstraction.util.AppLink.MovieDetail.PARAM_TYPE 11 | import isfaaghyth.app.abstraction.util.ext.hide 12 | import isfaaghyth.app.abstraction.util.ext.load 13 | import isfaaghyth.app.abstraction.util.ext.show 14 | import isfaaghyth.app.abstraction.util.ext.toast 15 | import isfaaghyth.app.abstraction.util.state.LoaderState 16 | import isfaaghyth.app.movie_details.R 17 | import isfaaghyth.app.movie_details.di.DaggerMovieDetailComponent 18 | import kotlinx.android.synthetic.main.activity_movie_detail.* 19 | import javax.inject.Inject 20 | 21 | @DeepLink(MOVIE_DETAIL) 22 | class MovieDetailActivity: BaseActivity() { 23 | 24 | override fun contentView(): Int = R.layout.activity_movie_detail 25 | 26 | @Inject lateinit var viewModelFactory: ViewModelProvider.Factory 27 | private lateinit var viewModel: MovieDetailViewModel 28 | 29 | override fun initView() { 30 | viewModel = ViewModelProviders 31 | .of(this, viewModelFactory) 32 | .get(MovieDetailViewModel::class.java) 33 | 34 | initObserver() 35 | } 36 | 37 | private fun initObserver() { 38 | if (intent.getBooleanExtra(DeepLink.IS_DEEP_LINK, false)) { 39 | val parameters = intent.extras!! 40 | val movieId = parameters.getString(PARAM_MOVIE_ID)?: "" 41 | 42 | when(parameters.getString(PARAM_TYPE)) { 43 | TYPE_MOVIE -> viewModel.getMovieDetail(movieId) 44 | TYPE_TV -> viewModel.getTVShowDetail(movieId) 45 | else -> finish() 46 | } 47 | 48 | viewModel.state.observe(this, Observer { 49 | when (it) { 50 | is LoaderState.ShowLoading -> { 51 | rootView.hide() 52 | progressBar.show() 53 | } 54 | is LoaderState.HideLoading -> { 55 | rootView.show() 56 | progressBar.hide() 57 | } 58 | } 59 | }) 60 | 61 | viewModel.movieDetail.observe(this, Observer { movie -> 62 | showDetail( 63 | movie.bannerUrl(), 64 | movie.posterUrl(), 65 | movie.title, 66 | movie.overview, 67 | movie.voteCount.toString(), 68 | movie.voteAverage.toString() 69 | ) 70 | }) 71 | 72 | viewModel.tvDetail.observe(this, Observer { tv -> 73 | showDetail( 74 | tv.bannerUrl(), 75 | tv.posterUrl(), 76 | tv.title, 77 | tv.overview, 78 | tv.voteCount.toString(), 79 | tv.voteAverage.toString() 80 | ) 81 | }) 82 | 83 | viewModel.error.observe(this, Observer { toast(it) }) 84 | } 85 | } 86 | 87 | private fun showDetail(banner: String, poster: String, 88 | title: String, overview: String, 89 | voteCount: String, voteAverage: String) { 90 | imgBanner.load(banner) 91 | imgPoster.load(poster) 92 | txtMovieName.text = title 93 | txtContent.text = overview 94 | txtRating.text = voteCount 95 | txtVote.text = voteAverage 96 | } 97 | 98 | override fun initInjector() { 99 | DaggerMovieDetailComponent.builder() 100 | .movieDetailModule(MovieDetailModule()) 101 | .build() 102 | .inject(this) 103 | } 104 | 105 | companion object { 106 | const val TYPE_MOVIE = "movie" 107 | const val TYPE_TV = "tv" 108 | } 109 | 110 | } -------------------------------------------------------------------------------- /features/movie_details/src/main/java/isfaaghyth/app/movie_details/ui/MovieDetailModule.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.movie_details.ui 2 | 3 | import dagger.Module 4 | import dagger.Provides 5 | import isfaaghyth.app.abstraction.util.thread.ApplicationSchedulerProvider 6 | import isfaaghyth.app.abstraction.util.thread.SchedulerProvider 7 | import isfaaghyth.app.data.di.DataModule 8 | import isfaaghyth.app.data.di.DataScope 9 | import isfaaghyth.app.data.repository.movie_detail.MovieDetailRepository 10 | import isfaaghyth.app.data.repository.movie_detail.MovieDetailRepositoryImpl 11 | import isfaaghyth.app.data.service.NetworkServices 12 | import isfaaghyth.app.movie_details.di.MovieDetailScope 13 | import isfaaghyth.app.movie_details.domain.MovieDetailUseCase 14 | 15 | @Module(includes = [DataModule::class]) 16 | class MovieDetailModule { 17 | 18 | @MovieDetailScope @Provides 19 | fun provideRepository(@DataScope service: NetworkServices): MovieDetailRepository { 20 | return MovieDetailRepositoryImpl(service) 21 | } 22 | 23 | @MovieDetailScope @Provides 24 | fun provideUseCase(repository: MovieDetailRepository): MovieDetailUseCase { 25 | return MovieDetailUseCase(repository) 26 | } 27 | 28 | @MovieDetailScope @Provides 29 | fun provideSchedulerProvider(): SchedulerProvider = ApplicationSchedulerProvider() 30 | 31 | } -------------------------------------------------------------------------------- /features/movie_details/src/main/java/isfaaghyth/app/movie_details/ui/MovieDetailViewModel.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.movie_details.ui 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import isfaaghyth.app.abstraction.base.BaseViewModel 6 | import isfaaghyth.app.abstraction.helper.FetchingIdlingResource 7 | import isfaaghyth.app.abstraction.util.state.LoaderState 8 | import isfaaghyth.app.abstraction.util.state.ResultState 9 | import isfaaghyth.app.abstraction.util.thread.SchedulerProvider 10 | import isfaaghyth.app.data.entity.Movie 11 | import isfaaghyth.app.data.entity.TVShow 12 | import isfaaghyth.app.movie_details.domain.MovieDetailUseCase 13 | import kotlinx.coroutines.Dispatchers 14 | import kotlinx.coroutines.launch 15 | import kotlinx.coroutines.withContext 16 | import javax.inject.Inject 17 | 18 | interface MovieDetailContract { 19 | fun getMovieDetail(movieId: String) 20 | fun getTVShowDetail(movieId: String) 21 | } 22 | 23 | class MovieDetailViewModel @Inject constructor( 24 | private val useCase: MovieDetailUseCase, 25 | dispatcher: SchedulerProvider 26 | ): BaseViewModel(dispatcher), MovieDetailContract { 27 | 28 | private val _movieDetail = MutableLiveData() 29 | val movieDetail: LiveData 30 | get() = _movieDetail 31 | 32 | private val _tvDetail = MutableLiveData() 33 | val tvDetail: LiveData 34 | get() = _tvDetail 35 | 36 | private val _error = MutableLiveData() 37 | val error: LiveData 38 | get() = _error 39 | 40 | private val _state = MutableLiveData() 41 | val state: LiveData 42 | get() = _state 43 | 44 | override fun getMovieDetail(movieId: String) { 45 | FetchingIdlingResource.begin() 46 | _state.value = LoaderState.ShowLoading 47 | launch { 48 | val result = useCase.getMovieDetail(movieId) 49 | withContext(Dispatchers.Main) { 50 | FetchingIdlingResource.complete() 51 | _state.value = LoaderState.HideLoading 52 | when (result) { 53 | is ResultState.Success -> _movieDetail.postValue(result.data) 54 | is ResultState.Error -> _error.postValue(result.error) 55 | } 56 | } 57 | } 58 | } 59 | 60 | override fun getTVShowDetail(movieId: String) { 61 | FetchingIdlingResource.begin() 62 | _state.value = LoaderState.ShowLoading 63 | launch { 64 | val result = useCase.getTvDetail(movieId) 65 | withContext(Dispatchers.Main) { 66 | FetchingIdlingResource.complete() 67 | _state.value = LoaderState.HideLoading 68 | when (result) { 69 | is ResultState.Success -> _tvDetail.postValue(result.data) 70 | is ResultState.Error -> _error.postValue(result.error) 71 | } 72 | } 73 | } 74 | } 75 | 76 | } -------------------------------------------------------------------------------- /features/movie_details/src/main/java/isfaaghyth/app/movie_details/ui/MovieDetailViewModelModule.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.movie_details.ui 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.ViewModelProvider 5 | import dagger.Binds 6 | import dagger.Module 7 | import dagger.multibindings.IntoMap 8 | import isfaaghyth.app.abstraction.util.viewmodel.ViewModelFactory 9 | import isfaaghyth.app.abstraction.util.viewmodel.ViewModelKey 10 | import isfaaghyth.app.movie_details.di.MovieDetailScope 11 | 12 | @Module abstract class MovieDetailViewModelModule { 13 | 14 | @MovieDetailScope 15 | @Binds 16 | internal abstract fun bindViewModelFactory(viewModelFactory: ViewModelFactory): ViewModelProvider.Factory 17 | 18 | @MovieDetailScope 19 | @Binds 20 | @IntoMap 21 | @ViewModelKey(MovieDetailViewModel::class) 22 | internal abstract fun bindMovieViewModel(viewModel: MovieDetailViewModel): ViewModel 23 | 24 | } -------------------------------------------------------------------------------- /features/movie_details/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /features/movie_details/src/test/java/isfaaghyth/app/movie_details/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.movie_details; 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 | } -------------------------------------------------------------------------------- /features/movie_details/src/test/java/isfaaghyth/app/movie_details/domain/MovieDetailUseCaseTest.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.movie_details.domain 2 | 3 | import isfaaghyth.app.abstraction.util.state.ResultState 4 | import isfaaghyth.app.data.entity.Movie 5 | import isfaaghyth.app.data.entity.TVShow 6 | import isfaaghyth.app.data.repository.movie_detail.MovieDetailRepository 7 | import kotlinx.coroutines.ExperimentalCoroutinesApi 8 | import kotlinx.coroutines.runBlocking 9 | import org.junit.Before 10 | import org.junit.Test 11 | import org.mockito.Mockito.`when` 12 | import org.mockito.Mockito.mock 13 | import retrofit2.Response 14 | 15 | @ExperimentalCoroutinesApi 16 | class MovieDetailUseCaseTest { 17 | 18 | private var repository = mock(MovieDetailRepository::class.java) 19 | private lateinit var useCase: MovieDetailUseCase 20 | 21 | @Before fun setUp() { 22 | useCase = MovieDetailUseCase(repository) 23 | } 24 | 25 | @Test fun `should show movie detail response`() { 26 | val movieId = "60735" 27 | val returnValue = ResultState.Success(movie) 28 | val request = runBlocking { 29 | `when`(repository.getMovieDetail(movieId)).thenReturn( 30 | Response.success(movie) 31 | ) 32 | useCase.getMovieDetail(movieId) 33 | } 34 | assert(returnValue == request) 35 | } 36 | 37 | @Test fun `should show tv detail response`() { 38 | val movieId = "60735" 39 | val returnValue = ResultState.Success(tvshow) 40 | val request = runBlocking { 41 | `when`(repository.getTVShowDetail(movieId)).thenReturn( 42 | Response.success(tvshow) 43 | ) 44 | useCase.getTvDetail(movieId) 45 | } 46 | assert(returnValue == request) 47 | } 48 | 49 | companion object { 50 | private val movie = Movie( 51 | "id", 52 | "movieId", 53 | "title", 54 | "posterPath", 55 | "overview", 56 | "backdrop", 57 | 0, 58 | 0f, 59 | "relateDate" 60 | ) 61 | 62 | private val tvshow = TVShow( 63 | "id", 64 | "movieId", 65 | "title", 66 | "posterPath", 67 | "overview", 68 | "backdrop", 69 | 0, 70 | 0f, 71 | "relateDate" 72 | ) 73 | } 74 | 75 | } -------------------------------------------------------------------------------- /features/movies/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /features/movies/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "$rootDir/common.gradle" 2 | 3 | dependencies { 4 | //android support 5 | implementation Android.appCompat 6 | implementation Android.ktx 7 | implementation Android.constraintLayout 8 | 9 | //network 10 | implementation Retrofit.retrofit 11 | implementation Retrofit.gsonConverter 12 | implementation Retrofit.okHttpLogging 13 | implementation Retrofit.coroutinesAdapter 14 | 15 | //coroutines 16 | implementation Coroutines.core 17 | implementation Coroutines.android 18 | 19 | //jetpack 20 | implementation Jetpack.recyclerView 21 | implementation Jetpack.lifecycle 22 | kapt Jetpack.lifecycleCompiler 23 | 24 | //modules 25 | implementation project(Modules.abstraction) 26 | implementation project(Modules.network) 27 | implementation project(Modules.data) 28 | 29 | //movie_detail features (easily purpose, just pass it with parcelable) 30 | implementation project(Modules.movieDetail) 31 | } -------------------------------------------------------------------------------- /features/movies/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 | -------------------------------------------------------------------------------- /features/movies/src/androidTest/java/isfaaghyth/app/movies/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.movies; 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("isfaaghyth.app.movies.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /features/movies/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /features/movies/src/main/java/isfaaghyth/app/movies/di/MovieComponent.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.movies.di 2 | 3 | import dagger.Component 4 | import isfaaghyth.app.movies.ui.MovieFragment 5 | import isfaaghyth.app.movies.ui.MovieModule 6 | import isfaaghyth.app.movies.ui.MovieViewModelModule 7 | 8 | @MovieScope 9 | @Component(modules = [ 10 | MovieModule::class, 11 | MovieViewModelModule::class 12 | ]) 13 | interface MovieComponent { 14 | fun inject(fragment: MovieFragment) 15 | } -------------------------------------------------------------------------------- /features/movies/src/main/java/isfaaghyth/app/movies/di/MovieScope.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.movies.di 2 | 3 | import javax.inject.Scope 4 | 5 | @Scope annotation class MovieScope -------------------------------------------------------------------------------- /features/movies/src/main/java/isfaaghyth/app/movies/domain/MovieUseCase.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.movies.domain 2 | 3 | import isfaaghyth.app.abstraction.util.state.ResultState 4 | import isfaaghyth.app.abstraction.util.UNSUCCESSFUL_MESSAGE 5 | import isfaaghyth.app.abstraction.util.ext.fetchState 6 | import isfaaghyth.app.data.entity.Movies 7 | import isfaaghyth.app.data.repository.movie.MovieRepository 8 | import javax.inject.Inject 9 | 10 | class MovieUseCase @Inject constructor(private val repository: MovieRepository) { 11 | 12 | suspend fun getPopularMovie(): ResultState { 13 | return fetchState { 14 | val response = repository.getPopularMovie() 15 | if (response.isSuccessful) { 16 | ResultState.Success(response.body()!!) 17 | } else { 18 | ResultState.Error(UNSUCCESSFUL_MESSAGE) 19 | } 20 | } 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /features/movies/src/main/java/isfaaghyth/app/movies/ui/MovieAdapter.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.movies.ui 2 | 3 | import android.content.Intent 4 | import android.net.Uri 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.recyclerview.widget.RecyclerView 9 | import isfaaghyth.app.abstraction.util.ext.load 10 | import isfaaghyth.app.data.entity.Movie 11 | import isfaaghyth.app.movies.R 12 | import kotlinx.android.synthetic.main.item_movie.view.* 13 | 14 | class MovieAdapter(private val movies: List): RecyclerView.Adapter() { 15 | 16 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder { 17 | return Holder.inflate(parent) 18 | } 19 | 20 | override fun getItemCount(): Int = movies.size 21 | 22 | override fun onBindViewHolder(holder: Holder, position: Int) { 23 | holder.bind(movies[position]) 24 | } 25 | 26 | class Holder(private val view: View): RecyclerView.ViewHolder(view) { 27 | private val title = view.txt_movie_name 28 | private val cardItem = view.card_movie 29 | private val poster = view.img_poster 30 | private val year = view.txt_year 31 | 32 | companion object { 33 | fun inflate(parent: ViewGroup): Holder { 34 | return Holder( 35 | LayoutInflater.from(parent.context).inflate( 36 | R.layout.item_movie, 37 | parent, 38 | false) 39 | ) 40 | } 41 | } 42 | 43 | fun bind(movie: Movie) { 44 | title.text = movie.title 45 | year.text = movie.releaseDate 46 | poster.load(movie.bannerUrl()) 47 | cardItem.setOnClickListener { 48 | val context = view.context 49 | context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("jetmovie://detail/movie/${movie.id}"))) 50 | } 51 | } 52 | } 53 | 54 | } -------------------------------------------------------------------------------- /features/movies/src/main/java/isfaaghyth/app/movies/ui/MovieFragment.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.movies.ui 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import androidx.lifecycle.Observer 9 | import androidx.lifecycle.ViewModelProvider 10 | import androidx.lifecycle.ViewModelProviders 11 | import isfaaghyth.app.abstraction.util.ext.toast 12 | import isfaaghyth.app.abstraction.util.state.LoaderState 13 | import isfaaghyth.app.data.entity.Movie 14 | import isfaaghyth.app.movies.R 15 | import isfaaghyth.app.movies.di.DaggerMovieComponent 16 | import kotlinx.android.synthetic.main.fragment_movie.* 17 | import javax.inject.Inject 18 | 19 | class MovieFragment: Fragment() { 20 | 21 | private fun contentView(): Int = R.layout.fragment_movie 22 | 23 | @Inject lateinit var viewModelFactory: ViewModelProvider.Factory 24 | private lateinit var viewModel: MovieViewModel 25 | 26 | private var movieData = arrayListOf() 27 | private val adapter: MovieAdapter by lazy { 28 | MovieAdapter(movieData) 29 | } 30 | 31 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 32 | return inflater.inflate(contentView(), container, false) 33 | } 34 | 35 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 36 | super.onViewCreated(view, savedInstanceState) 37 | initInjector() 38 | initView() 39 | } 40 | 41 | private fun initView() { 42 | viewModel = ViewModelProviders 43 | .of(this, viewModelFactory) 44 | .get(MovieViewModel::class.java) 45 | 46 | lstMovies.adapter = adapter 47 | 48 | initObserver() 49 | } 50 | 51 | private fun initObserver() { 52 | viewModel.state.observe(this, Observer { state -> 53 | when (state) { 54 | is LoaderState.ShowLoading -> toast("loading") 55 | is LoaderState.HideLoading -> toast("complete") 56 | } 57 | }) 58 | 59 | viewModel.result.observe(this, Observer { movies -> 60 | movieData.addAll(movies) 61 | adapter.notifyDataSetChanged() 62 | }) 63 | 64 | viewModel.error.observe(this, Observer { error -> 65 | toast(error) 66 | }) 67 | } 68 | 69 | private fun initInjector() { 70 | DaggerMovieComponent.builder() 71 | .movieModule(MovieModule()) 72 | .build() 73 | .inject(this) 74 | } 75 | 76 | override fun onDestroy() { 77 | super.onDestroy() 78 | viewModel.clear() 79 | } 80 | 81 | } -------------------------------------------------------------------------------- /features/movies/src/main/java/isfaaghyth/app/movies/ui/MovieModule.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.movies.ui 2 | 3 | import dagger.Module 4 | import dagger.Provides 5 | import isfaaghyth.app.abstraction.util.thread.ApplicationSchedulerProvider 6 | import isfaaghyth.app.abstraction.util.thread.SchedulerProvider 7 | import isfaaghyth.app.data.di.DataModule 8 | import isfaaghyth.app.data.di.DataScope 9 | import isfaaghyth.app.data.repository.movie.MovieRepository 10 | import isfaaghyth.app.data.repository.movie.MovieRepositoryImpl 11 | import isfaaghyth.app.data.service.NetworkServices 12 | import isfaaghyth.app.movies.di.MovieScope 13 | import isfaaghyth.app.movies.domain.MovieUseCase 14 | 15 | @Module(includes = [DataModule::class]) 16 | class MovieModule { 17 | 18 | @MovieScope @Provides 19 | fun provideRepository(@DataScope service: NetworkServices): MovieRepository { 20 | return MovieRepositoryImpl(service) 21 | } 22 | 23 | @MovieScope @Provides 24 | fun provideUseCase(repository: MovieRepository): MovieUseCase { 25 | return MovieUseCase(repository) 26 | } 27 | 28 | @MovieScope @Provides 29 | fun provideSchedulerProvider(): SchedulerProvider = ApplicationSchedulerProvider() 30 | 31 | } -------------------------------------------------------------------------------- /features/movies/src/main/java/isfaaghyth/app/movies/ui/MovieViewModel.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.movies.ui 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import isfaaghyth.app.abstraction.base.BaseViewModel 6 | import isfaaghyth.app.abstraction.helper.FetchingIdlingResource 7 | import isfaaghyth.app.abstraction.util.state.LoaderState 8 | import isfaaghyth.app.abstraction.util.state.ResultState 9 | import isfaaghyth.app.abstraction.util.thread.SchedulerProvider 10 | import isfaaghyth.app.data.entity.Movie 11 | import isfaaghyth.app.movies.domain.MovieUseCase 12 | import kotlinx.coroutines.Dispatchers 13 | import kotlinx.coroutines.launch 14 | import kotlinx.coroutines.withContext 15 | import javax.inject.Inject 16 | 17 | interface MovieContract { 18 | fun getPopularMovie() 19 | } 20 | 21 | class MovieViewModel @Inject constructor( 22 | private val useCase: MovieUseCase, 23 | dispatcher: SchedulerProvider 24 | ): BaseViewModel(dispatcher), MovieContract { 25 | 26 | private val _state = MutableLiveData() 27 | val state: LiveData 28 | get() = _state 29 | 30 | private val _result = MutableLiveData>() 31 | val result: LiveData> 32 | get() = _result 33 | 34 | private val _error = MutableLiveData() 35 | val error: LiveData 36 | get() = _error 37 | 38 | init { 39 | getPopularMovie() 40 | } 41 | 42 | override fun getPopularMovie() { 43 | FetchingIdlingResource.begin() 44 | _state.value = LoaderState.ShowLoading 45 | launch(coroutineContext) { 46 | val result = useCase.getPopularMovie() 47 | withContext(Dispatchers.Main) { 48 | FetchingIdlingResource.complete() 49 | _state.value = LoaderState.HideLoading 50 | when (result) { 51 | is ResultState.Success -> _result.postValue(result.data.resultsIntent) 52 | is ResultState.Error -> _error.postValue(result.error) 53 | } 54 | } 55 | } 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /features/movies/src/main/java/isfaaghyth/app/movies/ui/MovieViewModelModule.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.movies.ui 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.ViewModelProvider 5 | import dagger.Binds 6 | import dagger.Module 7 | import dagger.multibindings.IntoMap 8 | import isfaaghyth.app.abstraction.util.viewmodel.ViewModelFactory 9 | import isfaaghyth.app.abstraction.util.viewmodel.ViewModelKey 10 | import isfaaghyth.app.movies.di.MovieScope 11 | 12 | @Module abstract class MovieViewModelModule { 13 | 14 | @MovieScope 15 | @Binds 16 | internal abstract fun bindViewModelFactory(viewModelFactory: ViewModelFactory): ViewModelProvider.Factory 17 | 18 | @Binds 19 | @IntoMap 20 | @ViewModelKey(MovieViewModel::class) 21 | internal abstract fun bindMovieViewModel(viewModel: MovieViewModel): ViewModel 22 | 23 | } -------------------------------------------------------------------------------- /features/movies/src/main/res/layout/fragment_movie.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | -------------------------------------------------------------------------------- /features/movies/src/main/res/layout/item_movie.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | 19 | 20 | 27 | 28 | 33 | 34 | 39 | 40 | 52 | 53 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /features/movies/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | movies 3 | 4 | -------------------------------------------------------------------------------- /features/movies/src/test/java/isfaaghyth/app/movies/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.movies; 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 | } -------------------------------------------------------------------------------- /features/movies/src/test/java/isfaaghyth/app/movies/domain/MovieUseCaseTest.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.movies.domain 2 | 3 | import isfaaghyth.app.abstraction.util.state.ResultState 4 | import isfaaghyth.app.data.entity.Movie 5 | import isfaaghyth.app.data.entity.Movies 6 | import isfaaghyth.app.data.repository.movie.MovieRepository 7 | import kotlinx.coroutines.runBlocking 8 | import okhttp3.MediaType 9 | import okhttp3.ResponseBody 10 | import org.junit.Before 11 | import org.junit.Test 12 | import org.mockito.Mockito.`when` 13 | import org.mockito.Mockito.mock 14 | import retrofit2.Response 15 | 16 | class MovieUseCaseTest { 17 | 18 | private var repository = mock(MovieRepository::class.java) 19 | private lateinit var useCase: MovieUseCase 20 | 21 | private val movies = listOf( 22 | Movie( 23 | "id", 24 | "movieId", 25 | "title", 26 | "posterPath", 27 | "overview", 28 | "backdrop", 29 | 0, 30 | 0f, 31 | "relateDate" 32 | ) 33 | ) 34 | 35 | @Before fun setUp() { 36 | useCase = MovieUseCase(repository) 37 | } 38 | 39 | @Test fun `should return success`() { 40 | val actual = ResultState.Success(Movies(movies)) 41 | val result = runBlocking { 42 | `when`(repository.getPopularMovie()) 43 | .thenReturn(Response.success(Movies(movies))) 44 | useCase.getPopularMovie() 45 | } 46 | assert(result == actual) 47 | } 48 | 49 | @Test fun `should return error`() { 50 | val actual = ResultState.Error("") 51 | val result = runBlocking { 52 | `when`(repository.getPopularMovie()) 53 | .thenReturn( 54 | Response.error(401, ResponseBody.create(MediaType.parse("application/json"), "")) 55 | ) 56 | useCase.getPopularMovie() 57 | } 58 | 59 | //probably has different error message, so you can check by type of java class 60 | assert(result.javaClass === actual.javaClass) 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /features/movies/src/test/java/isfaaghyth/app/movies/ui/MovieViewModelTest.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.movies.ui 2 | 3 | import androidx.arch.core.executor.testing.InstantTaskExecutorRule 4 | import androidx.lifecycle.Observer 5 | import isfaaghyth.app.abstraction.util.state.ResultState 6 | import isfaaghyth.app.abstraction.util.thread.TestSchedulerProvider 7 | import isfaaghyth.app.data.entity.Movie 8 | import isfaaghyth.app.data.entity.Movies 9 | import isfaaghyth.app.movies.domain.MovieUseCase 10 | import junit.framework.Assert.assertEquals 11 | import kotlinx.coroutines.Dispatchers 12 | import kotlinx.coroutines.ExperimentalCoroutinesApi 13 | import kotlinx.coroutines.runBlocking 14 | import kotlinx.coroutines.test.resetMain 15 | import kotlinx.coroutines.test.setMain 16 | import org.junit.* 17 | import org.mockito.ArgumentCaptor 18 | import org.mockito.Captor 19 | import org.mockito.Mock 20 | import org.mockito.Mockito.* 21 | import org.mockito.MockitoAnnotations 22 | 23 | @ExperimentalCoroutinesApi 24 | class MovieViewModelTest { 25 | 26 | @get:Rule val instantExecutorRule = InstantTaskExecutorRule() 27 | 28 | @Mock lateinit var result: Observer> 29 | @Mock lateinit var error: Observer 30 | 31 | @Captor lateinit var argResultCaptor: ArgumentCaptor> 32 | @Captor lateinit var argErrorCaptor: ArgumentCaptor 33 | 34 | @Mock lateinit var useCase: MovieUseCase 35 | 36 | private lateinit var viewModel: MovieViewModel 37 | 38 | private val movies = listOf( 39 | Movie( 40 | "id", 41 | "movieId", 42 | "title", 43 | "posterPath", 44 | "overview", 45 | "backdrop", 46 | 0, 47 | 0f, 48 | "relateDate" 49 | ) 50 | ) 51 | 52 | private val moviesData = Movies(movies) 53 | private val schedulerProvider = TestSchedulerProvider() 54 | 55 | @Before fun setUp() { 56 | MockitoAnnotations.initMocks(this) 57 | Dispatchers.setMain(schedulerProvider.ui()) 58 | 59 | viewModel = MovieViewModel(useCase, schedulerProvider) 60 | viewModel.result.observeForever(result) 61 | viewModel.error.observeForever(error) 62 | } 63 | 64 | @Test fun `should return a response of movies data`() = runBlocking { 65 | val returnValue = ResultState.Success(moviesData) 66 | `when`(useCase.getPopularMovie()).thenReturn(returnValue) 67 | viewModel.getPopularMovie() 68 | verify(result, atLeastOnce()).onChanged(argResultCaptor.capture()) 69 | assertEquals(returnValue.data.resultsIntent, argResultCaptor.allValues.first()) 70 | clearInvocations(useCase, result) 71 | } 72 | 73 | @Test fun `should return an error without api key`() = runBlocking { 74 | val returnValue = ResultState.Error("API Key Not Found") 75 | `when`(useCase.getPopularMovie()).thenReturn(returnValue) 76 | viewModel.getPopularMovie() 77 | verify(error, atLeastOnce()).onChanged(argErrorCaptor.capture()) 78 | assertEquals(returnValue.error, argErrorCaptor.allValues.first()) 79 | clearInvocations(useCase, error) 80 | } 81 | 82 | @After fun tearDown() { 83 | Dispatchers.resetMain() 84 | } 85 | } -------------------------------------------------------------------------------- /features/tvshows/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /features/tvshows/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "$rootDir/common.gradle" 2 | 3 | dependencies { 4 | //android support 5 | implementation Android.appCompat 6 | implementation Android.ktx 7 | implementation Android.constraintLayout 8 | 9 | //network 10 | implementation Retrofit.retrofit 11 | implementation Retrofit.gsonConverter 12 | implementation Retrofit.okHttpLogging 13 | implementation Retrofit.coroutinesAdapter 14 | 15 | //coroutines 16 | implementation Coroutines.core 17 | implementation Coroutines.android 18 | 19 | //jetpack 20 | implementation Jetpack.recyclerView 21 | implementation Jetpack.lifecycle 22 | kapt Jetpack.lifecycleCompiler 23 | 24 | //coroutines 25 | implementation Coroutines.core 26 | implementation Coroutines.android 27 | 28 | //modules 29 | implementation project(Modules.abstraction) 30 | implementation project(Modules.network) 31 | implementation project(Modules.data) 32 | 33 | //movie_detail features (easily purpose, just pass it with parcelable) 34 | implementation project(Modules.movieDetail) 35 | } -------------------------------------------------------------------------------- /features/tvshows/build/generated/source/buildConfig/debug/isfaaghyth/app/tvshows/BuildConfig.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Automatically generated file. DO NOT MODIFY 3 | */ 4 | package isfaaghyth.app.tvshows; 5 | 6 | public final class BuildConfig { 7 | public static final boolean DEBUG = Boolean.parseBoolean("true"); 8 | public static final String APPLICATION_ID = "isfaaghyth.app.tvshows"; 9 | public static final String BUILD_TYPE = "debug"; 10 | public static final String FLAVOR = ""; 11 | public static final int VERSION_CODE = 1; 12 | public static final String VERSION_NAME = "1.0"; 13 | // Fields from default config. 14 | public static final String API_KEY = "9351b653885866a95fcef04c4f0c7426"; 15 | public static final String IMAGE_URL = "https://image.tmdb.org/t/p/w500"; 16 | public static final String MOVIE_URL = "https://api.themoviedb.org/3/"; 17 | } 18 | -------------------------------------------------------------------------------- /features/tvshows/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 | -------------------------------------------------------------------------------- /features/tvshows/src/androidTest/java/isfaaghyth/app/tvshows/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.tvshows; 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("isfaaghyth.app.tvshows.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /features/tvshows/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /features/tvshows/src/main/java/isfaaghyth/app/tvshows/di/TVShowComponent.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.tvshows.di 2 | 3 | import dagger.Component 4 | import isfaaghyth.app.tvshows.ui.TVShowFragment 5 | import isfaaghyth.app.tvshows.ui.TVShowModule 6 | import isfaaghyth.app.tvshows.ui.TVShowViewModelModule 7 | 8 | @TVShowScope 9 | @Component(modules = [ 10 | TVShowModule::class, 11 | TVShowViewModelModule::class 12 | ]) 13 | interface TVShowComponent { 14 | fun inject(activity: TVShowFragment) 15 | } -------------------------------------------------------------------------------- /features/tvshows/src/main/java/isfaaghyth/app/tvshows/di/TVShowScope.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.tvshows.di 2 | 3 | annotation class TVShowScope -------------------------------------------------------------------------------- /features/tvshows/src/main/java/isfaaghyth/app/tvshows/domain/TVShowUseCase.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.tvshows.domain 2 | 3 | import isfaaghyth.app.abstraction.util.state.ResultState 4 | import isfaaghyth.app.abstraction.util.UNSUCCESSFUL_MESSAGE 5 | import isfaaghyth.app.abstraction.util.ext.fetchState 6 | import isfaaghyth.app.data.entity.TVShows 7 | import isfaaghyth.app.data.repository.tvshow.TVShowRepository 8 | import javax.inject.Inject 9 | 10 | class TVShowUseCase @Inject constructor(private val repository: TVShowRepository) { 11 | 12 | suspend fun getPopularTvShow(): ResultState { 13 | return fetchState { 14 | val response = repository.getPopularTVShow() 15 | if (response.isSuccessful) { 16 | ResultState.Success(response.body()!!) 17 | } else { 18 | ResultState.Error(UNSUCCESSFUL_MESSAGE) 19 | } 20 | } 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /features/tvshows/src/main/java/isfaaghyth/app/tvshows/ui/TVShowAdapter.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.tvshows.ui 2 | 3 | import android.content.Intent 4 | import android.net.Uri 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.recyclerview.widget.RecyclerView 9 | import isfaaghyth.app.abstraction.util.ext.load 10 | import isfaaghyth.app.data.entity.TVShow 11 | import isfaaghyth.app.tvshows.R 12 | import kotlinx.android.synthetic.main.item_tvshow.view.* 13 | 14 | class TVShowAdapter(private val tvs: List): RecyclerView.Adapter() { 15 | 16 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder { 17 | return Holder.inflate(parent) 18 | } 19 | 20 | override fun getItemCount(): Int = tvs.size 21 | 22 | override fun onBindViewHolder(holder: Holder, position: Int) { 23 | holder.bind(tvs[position]) 24 | } 25 | 26 | class Holder(private val view: View): RecyclerView.ViewHolder(view) { 27 | private val title = view.txt_movie_name 28 | private val cardItem = view.card_movie 29 | private val poster = view.img_poster 30 | private val year = view.txt_year 31 | 32 | companion object { 33 | fun inflate(parent: ViewGroup): Holder { 34 | return Holder( 35 | LayoutInflater.from(parent.context).inflate( 36 | R.layout.item_tvshow, 37 | parent, 38 | false) 39 | ) 40 | } 41 | } 42 | 43 | fun bind(tvshow: TVShow) { 44 | title.text = tvshow.title 45 | year.text = tvshow.releaseDate 46 | poster.load(tvshow.bannerUrl()) 47 | cardItem.setOnClickListener { 48 | val context = view.context 49 | context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("jetmovie://detail/tv/${tvshow.id}"))) 50 | } 51 | } 52 | } 53 | 54 | } -------------------------------------------------------------------------------- /features/tvshows/src/main/java/isfaaghyth/app/tvshows/ui/TVShowFragment.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.tvshows.ui 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import androidx.lifecycle.Observer 9 | import androidx.lifecycle.ViewModelProvider 10 | import androidx.lifecycle.ViewModelProviders 11 | import isfaaghyth.app.abstraction.util.ext.toast 12 | import isfaaghyth.app.abstraction.util.state.LoaderState 13 | import isfaaghyth.app.data.entity.TVShow 14 | import isfaaghyth.app.tvshows.R 15 | import isfaaghyth.app.tvshows.di.DaggerTVShowComponent 16 | import kotlinx.android.synthetic.main.fragment_tvshow.* 17 | import javax.inject.Inject 18 | 19 | class TVShowFragment: Fragment() { 20 | 21 | private fun contentView(): Int = R.layout.fragment_tvshow 22 | 23 | @Inject 24 | lateinit var viewModelFactory: ViewModelProvider.Factory 25 | private lateinit var viewModel: TVShowViewModel 26 | 27 | private var tvshowData = arrayListOf() 28 | private val adapter: TVShowAdapter by lazy { 29 | TVShowAdapter(tvshowData) 30 | } 31 | 32 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 33 | return inflater.inflate(contentView(), container, false) 34 | } 35 | 36 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 37 | super.onViewCreated(view, savedInstanceState) 38 | initInjector() 39 | initView() 40 | } 41 | 42 | private fun initView() { 43 | viewModel = ViewModelProviders 44 | .of(this, viewModelFactory) 45 | .get(TVShowViewModel::class.java) 46 | 47 | lstTvShows.adapter = adapter 48 | 49 | initObserver() 50 | } 51 | 52 | private fun initObserver() { 53 | viewModel.state.observe(this, Observer { state -> 54 | when (state) { 55 | is LoaderState.ShowLoading -> toast("loading") 56 | is LoaderState.HideLoading -> toast("complete") 57 | } 58 | }) 59 | 60 | viewModel.result.observe(this, Observer { tvshows -> 61 | tvshowData.addAll(tvshows) 62 | adapter.notifyDataSetChanged() 63 | }) 64 | 65 | viewModel.error.observe(this, Observer { error -> 66 | toast(error) 67 | }) 68 | } 69 | 70 | private fun initInjector() { 71 | DaggerTVShowComponent.builder() 72 | .tVShowModule(TVShowModule()) 73 | .build() 74 | .inject(this) 75 | } 76 | 77 | override fun onDestroyView() { 78 | super.onDestroyView() 79 | viewModel.clear() 80 | } 81 | 82 | } -------------------------------------------------------------------------------- /features/tvshows/src/main/java/isfaaghyth/app/tvshows/ui/TVShowModule.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.tvshows.ui 2 | 3 | import dagger.Module 4 | import dagger.Provides 5 | import isfaaghyth.app.abstraction.util.thread.ApplicationSchedulerProvider 6 | import isfaaghyth.app.abstraction.util.thread.SchedulerProvider 7 | import isfaaghyth.app.data.di.DataModule 8 | import isfaaghyth.app.data.di.DataScope 9 | import isfaaghyth.app.data.repository.tvshow.TVShowRepository 10 | import isfaaghyth.app.data.repository.tvshow.TVShowRepositoryImpl 11 | import isfaaghyth.app.data.service.NetworkServices 12 | import isfaaghyth.app.tvshows.di.TVShowScope 13 | import isfaaghyth.app.tvshows.domain.TVShowUseCase 14 | 15 | @Module(includes = [DataModule::class]) 16 | class TVShowModule { 17 | 18 | @TVShowScope @Provides 19 | fun provideRepository(@DataScope service: NetworkServices): TVShowRepository { 20 | return TVShowRepositoryImpl(service) 21 | } 22 | 23 | @TVShowScope @Provides 24 | fun provideUseCase(repository: TVShowRepository): TVShowUseCase { 25 | return TVShowUseCase(repository) 26 | } 27 | 28 | @TVShowScope @Provides 29 | fun provideSchedulerProvider(): SchedulerProvider = ApplicationSchedulerProvider() 30 | 31 | } -------------------------------------------------------------------------------- /features/tvshows/src/main/java/isfaaghyth/app/tvshows/ui/TVShowViewModel.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.tvshows.ui 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import isfaaghyth.app.abstraction.base.BaseViewModel 6 | import isfaaghyth.app.abstraction.helper.FetchingIdlingResource 7 | import isfaaghyth.app.abstraction.util.state.LoaderState 8 | import isfaaghyth.app.abstraction.util.state.ResultState 9 | import isfaaghyth.app.abstraction.util.thread.SchedulerProvider 10 | import isfaaghyth.app.data.entity.TVShow 11 | import isfaaghyth.app.tvshows.domain.TVShowUseCase 12 | import kotlinx.coroutines.Dispatchers 13 | import kotlinx.coroutines.launch 14 | import kotlinx.coroutines.withContext 15 | import javax.inject.Inject 16 | 17 | interface TVShowContract { 18 | fun getPopularTvShow() 19 | } 20 | 21 | class TVShowViewModel @Inject constructor( 22 | private val useCase: TVShowUseCase, 23 | dispatcher: SchedulerProvider 24 | ): BaseViewModel(dispatcher), TVShowContract { 25 | 26 | private val _state = MutableLiveData() 27 | val state: LiveData 28 | get() = _state 29 | 30 | private val _result = MutableLiveData>() 31 | val result: LiveData> 32 | get() = _result 33 | 34 | private val _error = MutableLiveData() 35 | val error: LiveData 36 | get() = _error 37 | 38 | init { 39 | getPopularTvShow() 40 | } 41 | 42 | override fun getPopularTvShow() { 43 | FetchingIdlingResource.begin() 44 | _state.value = LoaderState.ShowLoading 45 | launch { 46 | val result = useCase.getPopularTvShow() 47 | withContext(Dispatchers.Main) { 48 | FetchingIdlingResource.complete() 49 | _state.value = LoaderState.HideLoading 50 | when (result) { 51 | is ResultState.Success -> _result.postValue(result.data.resultsIntent) 52 | is ResultState.Error -> _error.postValue(result.error) 53 | } 54 | } 55 | } 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /features/tvshows/src/main/java/isfaaghyth/app/tvshows/ui/TVShowViewModelModule.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.tvshows.ui 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.ViewModelProvider 5 | import dagger.Binds 6 | import dagger.Module 7 | import dagger.multibindings.IntoMap 8 | import isfaaghyth.app.abstraction.util.viewmodel.ViewModelFactory 9 | import isfaaghyth.app.abstraction.util.viewmodel.ViewModelKey 10 | import isfaaghyth.app.tvshows.di.TVShowScope 11 | 12 | @Module abstract class TVShowViewModelModule { 13 | 14 | @TVShowScope 15 | @Binds 16 | internal abstract fun bindViewModelFactory(viewModelFactory: ViewModelFactory): ViewModelProvider.Factory 17 | 18 | @Binds 19 | @IntoMap 20 | @ViewModelKey(TVShowViewModel::class) 21 | internal abstract fun bindMovieViewModel(viewModel: TVShowViewModel): ViewModel 22 | 23 | } -------------------------------------------------------------------------------- /features/tvshows/src/main/res/layout/fragment_tvshow.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | -------------------------------------------------------------------------------- /features/tvshows/src/main/res/layout/item_tvshow.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | 19 | 20 | 27 | 28 | 33 | 34 | 39 | 40 | 52 | 53 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /features/tvshows/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | tvshows 3 | 4 | -------------------------------------------------------------------------------- /features/tvshows/src/test/java/isfaaghyth/app/tvshows/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.tvshows; 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 | } -------------------------------------------------------------------------------- /features/tvshows/src/test/java/isfaaghyth/app/tvshows/domain/TVShowUseCaseTest.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.tvshows.domain 2 | 3 | import isfaaghyth.app.abstraction.util.state.ResultState 4 | import isfaaghyth.app.data.entity.TVShow 5 | import isfaaghyth.app.data.entity.TVShows 6 | import isfaaghyth.app.data.repository.tvshow.TVShowRepository 7 | import kotlinx.coroutines.runBlocking 8 | import okhttp3.MediaType 9 | import okhttp3.ResponseBody 10 | import org.junit.Before 11 | import org.junit.Test 12 | import org.mockito.Mockito.`when` 13 | import org.mockito.Mockito.mock 14 | import retrofit2.Response 15 | 16 | class TVShowUseCaseTest { 17 | 18 | private var repository = mock(TVShowRepository::class.java) 19 | private lateinit var usecase: TVShowUseCase 20 | 21 | private val tvshows = listOf( 22 | TVShow( 23 | "id", 24 | "movieId", 25 | "title", 26 | "posterPath", 27 | "overview", 28 | "backdrop", 29 | 0, 30 | 0f, 31 | "relateDate" 32 | ) 33 | ) 34 | 35 | @Before fun setUp() { 36 | usecase = TVShowUseCase(repository) 37 | } 38 | 39 | @Test fun `should return success`() { 40 | val actual = ResultState.Success(TVShows(tvshows)) 41 | val result = runBlocking { 42 | `when`(repository.getPopularTVShow()) 43 | .thenReturn( 44 | Response.success(TVShows(tvshows)) 45 | ) 46 | usecase.getPopularTvShow() 47 | } 48 | assert(actual.javaClass === result.javaClass) 49 | } 50 | 51 | @Test fun `should return error`() { 52 | val actual = ResultState.Error("") 53 | val result = runBlocking { 54 | `when`(repository.getPopularTVShow()) 55 | .thenReturn( 56 | Response.error(401, ResponseBody.create(MediaType.parse("application/json"), "")) 57 | ) 58 | usecase.getPopularTvShow() 59 | } 60 | assert(actual.javaClass === result.javaClass) 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /features/tvshows/src/test/java/isfaaghyth/app/tvshows/ui/TVShowViewModelTest.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.tvshows.ui 2 | 3 | import androidx.arch.core.executor.testing.InstantTaskExecutorRule 4 | import androidx.lifecycle.Observer 5 | import isfaaghyth.app.abstraction.util.state.ResultState 6 | import isfaaghyth.app.abstraction.util.thread.TestSchedulerProvider 7 | import isfaaghyth.app.data.entity.TVShow 8 | import isfaaghyth.app.data.entity.TVShows 9 | import isfaaghyth.app.tvshows.domain.TVShowUseCase 10 | import kotlinx.coroutines.Dispatchers 11 | import kotlinx.coroutines.ExperimentalCoroutinesApi 12 | import kotlinx.coroutines.runBlocking 13 | import kotlinx.coroutines.test.resetMain 14 | import kotlinx.coroutines.test.setMain 15 | import org.junit.After 16 | import org.junit.Assert.assertEquals 17 | import org.junit.Before 18 | import org.junit.Rule 19 | import org.junit.Test 20 | import org.mockito.* 21 | import org.mockito.Mockito.* 22 | 23 | @ExperimentalCoroutinesApi 24 | class TVShowViewModelTest { 25 | 26 | @get:Rule val instantExecutorRule = InstantTaskExecutorRule() 27 | 28 | @Mock lateinit var result: Observer> 29 | @Mock lateinit var error: Observer 30 | 31 | @Captor lateinit var argResultCaptor: ArgumentCaptor> 32 | @Captor lateinit var argErrorCaptor: ArgumentCaptor 33 | 34 | @Mock lateinit var useCase: TVShowUseCase 35 | 36 | private lateinit var viewModel: TVShowViewModel 37 | 38 | private val tvShow = listOf( 39 | TVShow( 40 | "id", 41 | "movieId", 42 | "title", 43 | "posterPath", 44 | "overview", 45 | "backdrop", 46 | 0, 47 | 0f, 48 | "relateDate" 49 | ) 50 | ) 51 | 52 | private val moviesData = TVShows(tvShow) 53 | private val schedulerProvider = TestSchedulerProvider() 54 | 55 | @Before fun setUp() { 56 | MockitoAnnotations.initMocks(this) 57 | Dispatchers.setMain(schedulerProvider.ui()) 58 | 59 | viewModel = TVShowViewModel(useCase, schedulerProvider) 60 | viewModel.result.observeForever(result) 61 | viewModel.error.observeForever(error) 62 | } 63 | 64 | @Test fun `should return a response of tvShows data`() = runBlocking { 65 | val returnValue = ResultState.Success(moviesData) 66 | Mockito.`when`(useCase.getPopularTvShow()).thenReturn(returnValue) 67 | viewModel.getPopularTvShow() 68 | verify(result, atLeastOnce()).onChanged(argResultCaptor.capture()) 69 | assertEquals(returnValue.data.resultsIntent, argResultCaptor.allValues.first()) 70 | clearInvocations(useCase, result) 71 | } 72 | 73 | @Test fun `should return an error without api key`() = runBlocking { 74 | val returnValue = ResultState.Error("error") 75 | Mockito.`when`(useCase.getPopularTvShow()).thenReturn(returnValue) 76 | viewModel.getPopularTvShow() 77 | verify(error, atLeastOnce()).onChanged(argErrorCaptor.capture()) 78 | assertEquals(returnValue.error, argErrorCaptor.allValues.first()) 79 | clearInvocations(useCase, error) 80 | } 81 | 82 | @After fun tearDown() { 83 | Dispatchers.resetMain() 84 | } 85 | 86 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | # the movie db 23 | movieApiKey="9351b653885866a95fcef04c4f0c7426" 24 | movieImageUrl="https://image.tmdb.org/t/p/w500" 25 | movieApiUrl="https://api.themoviedb.org/3/" -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Jul 31 23:35:46 WIB 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /libraries/abstraction/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /libraries/abstraction/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "$rootDir/common.gradle" 2 | 3 | dependencies { 4 | //android support 5 | implementation Android.appCompat 6 | implementation Android.ktx 7 | implementation Android.constraintLayout 8 | implementation Jetpack.recyclerView 9 | 10 | //network 11 | implementation Retrofit.retrofit 12 | 13 | //coroutines 14 | implementation Coroutines.core 15 | implementation Jetpack.lifecycle 16 | 17 | //glide 18 | implementation Glide.glide 19 | kapt Glide.compiler 20 | } -------------------------------------------------------------------------------- /libraries/abstraction/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 | -------------------------------------------------------------------------------- /libraries/abstraction/src/androidTest/java/isfaaghyth/app/abstraction/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.abstraction; 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("isfaaghyth.app.abstraction.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /libraries/abstraction/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libraries/abstraction/src/main/ic_placeholder-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/libraries/abstraction/src/main/ic_placeholder-web.png -------------------------------------------------------------------------------- /libraries/abstraction/src/main/ic_rating-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/libraries/abstraction/src/main/ic_rating-web.png -------------------------------------------------------------------------------- /libraries/abstraction/src/main/ic_voter-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/libraries/abstraction/src/main/ic_voter-web.png -------------------------------------------------------------------------------- /libraries/abstraction/src/main/java/isfaaghyth/app/abstraction/annotation/Testable.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.abstraction.annotation 2 | 3 | @Target(AnnotationTarget.ANNOTATION_CLASS) 4 | annotation class OpenClass 5 | 6 | @OpenClass 7 | @Target(AnnotationTarget.CLASS) 8 | annotation class Testable -------------------------------------------------------------------------------- /libraries/abstraction/src/main/java/isfaaghyth/app/abstraction/base/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.abstraction.base 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import isfaaghyth.app.abstraction.util.view.KeyboardUtils 6 | import isfaaghyth.app.abstraction.util.ext.toast 7 | 8 | abstract class BaseActivity: AppCompatActivity(), BaseView { 9 | 10 | /** 11 | * lifecycle method 12 | * @method contentView(): @return resLayoutId 13 | * @method initView()depe 14 | */ 15 | abstract fun contentView(): Int 16 | abstract fun initView() 17 | abstract fun initInjector() 18 | 19 | /** 20 | * (optional, use it if needed) 21 | */ 22 | protected lateinit var savedInstanceState: Bundle 23 | 24 | override fun onCreate(savedInstanceState: Bundle?) { 25 | super.onCreate(savedInstanceState) 26 | if (savedInstanceState != null) { 27 | this.savedInstanceState = savedInstanceState 28 | } 29 | setContentView(contentView()) 30 | initInjector() 31 | initView() 32 | } 33 | 34 | override fun onMessage(message: String?) { 35 | toast(message) 36 | } 37 | 38 | override fun onMessage(stringResId: Int) { 39 | onMessage(getString(stringResId)) 40 | } 41 | 42 | /** 43 | * check internet connection 44 | */ 45 | override fun isNetworkConnect(): Boolean { 46 | return true //TODO(make a utilities class for this) 47 | } 48 | 49 | /** 50 | * hide keyboard layout 51 | */ 52 | override fun hideKeyboard() { 53 | return KeyboardUtils().hide(this) 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /libraries/abstraction/src/main/java/isfaaghyth/app/abstraction/base/BaseView.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.abstraction.base 2 | 3 | interface BaseView { 4 | fun onMessage(message: String?) 5 | fun onMessage(stringResId: Int) 6 | fun isNetworkConnect(): Boolean 7 | fun hideKeyboard() 8 | } -------------------------------------------------------------------------------- /libraries/abstraction/src/main/java/isfaaghyth/app/abstraction/base/BaseViewModel.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.abstraction.base 2 | 3 | import androidx.lifecycle.ViewModel 4 | import isfaaghyth.app.abstraction.util.thread.SchedulerProvider 5 | import kotlinx.coroutines.CoroutineScope 6 | import kotlinx.coroutines.SupervisorJob 7 | import kotlinx.coroutines.isActive 8 | import kotlin.coroutines.CoroutineContext 9 | 10 | abstract class BaseViewModel(private val baseDispatcher: SchedulerProvider): ViewModel(), CoroutineScope { 11 | 12 | private val supervisorJob = SupervisorJob() 13 | 14 | override val coroutineContext: CoroutineContext 15 | get() = baseDispatcher.ui() + supervisorJob 16 | 17 | open fun clear() { 18 | if (isActive && !supervisorJob.isCancelled) { 19 | supervisorJob.children.map { 20 | it.cancel() 21 | } 22 | } 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /libraries/abstraction/src/main/java/isfaaghyth/app/abstraction/helper/EspressoIdlingResource.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.abstraction.helper 2 | 3 | import androidx.test.espresso.IdlingResource 4 | 5 | //fetcher contract 6 | interface FetcherListener { 7 | fun begin() 8 | fun complete() 9 | } 10 | 11 | //singleton idling resources 12 | object FetchingIdlingResource { 13 | private var idlingResource = EspressoIdlingResource() 14 | fun begin() = idlingResource.begin() 15 | fun complete() = idlingResource.complete() 16 | fun get(): EspressoIdlingResource = idlingResource 17 | } 18 | 19 | class EspressoIdlingResource: IdlingResource, FetcherListener { 20 | private var idle = true 21 | private var resourceCallback: IdlingResource.ResourceCallback? = null 22 | 23 | override fun getName(): String { 24 | return EspressoIdlingResource::class.java.simpleName 25 | } 26 | 27 | override fun isIdleNow() = idle 28 | 29 | override fun registerIdleTransitionCallback( 30 | callback: IdlingResource.ResourceCallback?) { 31 | resourceCallback = callback 32 | } 33 | 34 | override fun complete() { 35 | idle = true 36 | resourceCallback?.onTransitionToIdle() 37 | } 38 | 39 | override fun begin() { 40 | idle = false 41 | } 42 | } -------------------------------------------------------------------------------- /libraries/abstraction/src/main/java/isfaaghyth/app/abstraction/ui/ViewPagerAdapter.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.abstraction.ui 2 | 3 | import androidx.fragment.app.Fragment 4 | import androidx.fragment.app.FragmentManager 5 | import androidx.fragment.app.FragmentStatePagerAdapter 6 | 7 | class ViewPagerAdapter(fm: FragmentManager): FragmentStatePagerAdapter(fm) { 8 | 9 | private val mFragmentList = arrayListOf() 10 | private val mFragmentTitleList = arrayListOf() 11 | 12 | override fun getItem(position: Int): Fragment = mFragmentList[position] 13 | 14 | override fun getCount(): Int = mFragmentList.size 15 | 16 | override fun getPageTitle(position: Int): CharSequence? = mFragmentTitleList[position] 17 | 18 | fun addFragment(fragment: Fragment, title: String) { 19 | mFragmentList.add(fragment) 20 | mFragmentTitleList.add(title) 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /libraries/abstraction/src/main/java/isfaaghyth/app/abstraction/util/Consts.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.abstraction.util 2 | 3 | const val UNSUCCESSFUL_MESSAGE = "Something Error!" 4 | 5 | object AppLink { 6 | object MovieDetail { 7 | const val MOVIE_DETAIL = "jetmovie://detail/{type}/{movie_id}" 8 | const val PARAM_TYPE = "type" 9 | const val PARAM_MOVIE_ID = "movie_id" 10 | } 11 | } -------------------------------------------------------------------------------- /libraries/abstraction/src/main/java/isfaaghyth/app/abstraction/util/ext/CoroutinesUtil.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.abstraction.util.ext 2 | 3 | import isfaaghyth.app.abstraction.util.state.ResultState 4 | import java.net.ConnectException 5 | 6 | suspend fun fetchState(call: suspend () -> ResultState): ResultState { 7 | return try { 8 | call.invoke() 9 | } catch (e: ConnectException) { 10 | ResultState.Error(e.message) 11 | } catch (e: Exception) { 12 | ResultState.Error(e.message) 13 | } catch (e: Throwable) { 14 | ResultState.Error(e.message) 15 | } 16 | } -------------------------------------------------------------------------------- /libraries/abstraction/src/main/java/isfaaghyth/app/abstraction/util/ext/ImageExt.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.abstraction.util.ext 2 | 3 | import android.widget.ImageView 4 | import com.bumptech.glide.Glide 5 | import com.bumptech.glide.load.resource.bitmap.CircleCrop 6 | import com.bumptech.glide.request.RequestOptions 7 | 8 | /** 9 | * Created by isfaaghyth on 27/04/19. 10 | * github: @isfaaghyth 11 | */ 12 | 13 | fun ImageView.load(imageUri: Any) { 14 | Glide.with(context) 15 | .load(imageUri) 16 | .apply(RequestOptions()) 17 | .into(this) 18 | } 19 | 20 | fun ImageView.circle(imageUri: Any) { 21 | Glide.with(context) 22 | .asBitmap() 23 | .load(imageUri) 24 | .apply(RequestOptions() 25 | .transform(CircleCrop()) 26 | ) 27 | .into(this) 28 | } -------------------------------------------------------------------------------- /libraries/abstraction/src/main/java/isfaaghyth/app/abstraction/util/ext/UtilExt.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.abstraction.util.ext 2 | 3 | import android.app.Activity 4 | import android.view.View 5 | import android.widget.Toast 6 | import androidx.fragment.app.Fragment 7 | 8 | /** 9 | * Created by isfaaghyth on 29/04/19. 10 | * github: @isfaaghyth 11 | */ 12 | 13 | fun Activity.toast(message: String?) { 14 | Toast.makeText(this, message, Toast.LENGTH_SHORT).show() 15 | } 16 | 17 | fun Fragment.toast(message: String?) { 18 | Toast.makeText(context, message, Toast.LENGTH_SHORT).show() 19 | } 20 | 21 | fun View.show() { 22 | visibility = View.VISIBLE 23 | } 24 | 25 | fun View.hide() { 26 | visibility = View.GONE 27 | } -------------------------------------------------------------------------------- /libraries/abstraction/src/main/java/isfaaghyth/app/abstraction/util/state/LoaderState.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.abstraction.util.state 2 | 3 | sealed class LoaderState { 4 | object ShowLoading: LoaderState() 5 | object HideLoading: LoaderState() 6 | } -------------------------------------------------------------------------------- /libraries/abstraction/src/main/java/isfaaghyth/app/abstraction/util/state/ResultState.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.abstraction.util.state 2 | 3 | sealed class ResultState { 4 | data class Success(val data: T): ResultState() 5 | data class Error(val error: String?): ResultState() 6 | } -------------------------------------------------------------------------------- /libraries/abstraction/src/main/java/isfaaghyth/app/abstraction/util/thread/ApplicationSchedulerProvider.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.abstraction.util.thread 2 | 3 | import kotlinx.coroutines.CoroutineDispatcher 4 | import kotlinx.coroutines.Dispatchers 5 | 6 | class ApplicationSchedulerProvider: SchedulerProvider { 7 | override fun ui(): CoroutineDispatcher = Dispatchers.Main 8 | } -------------------------------------------------------------------------------- /libraries/abstraction/src/main/java/isfaaghyth/app/abstraction/util/thread/SchedulerProvider.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.abstraction.util.thread 2 | 3 | import kotlinx.coroutines.CoroutineDispatcher 4 | 5 | interface SchedulerProvider { 6 | fun ui(): CoroutineDispatcher 7 | } -------------------------------------------------------------------------------- /libraries/abstraction/src/main/java/isfaaghyth/app/abstraction/util/thread/TestSchedulerProvider.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.abstraction.util.thread 2 | 3 | import kotlinx.coroutines.CoroutineDispatcher 4 | import kotlinx.coroutines.Dispatchers 5 | 6 | class TestSchedulerProvider: SchedulerProvider { 7 | override fun ui(): CoroutineDispatcher = Dispatchers.Unconfined 8 | } -------------------------------------------------------------------------------- /libraries/abstraction/src/main/java/isfaaghyth/app/abstraction/util/view/KeyboardUtils.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.abstraction.util.view 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.view.View 6 | import android.view.inputmethod.InputMethodManager 7 | import android.widget.EditText 8 | 9 | class KeyboardUtils { 10 | 11 | fun hide(activity: Activity) { 12 | var view = activity.currentFocus 13 | if (view == null) view = View(activity) 14 | val imm = activity.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager 15 | imm.hideSoftInputFromWindow(view.windowToken, 0) 16 | } 17 | 18 | fun show(edit: EditText, context: Context) { 19 | edit.isFocusable = true 20 | edit.isFocusableInTouchMode = true 21 | edit.requestFocus() 22 | val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager 23 | imm.showSoftInput(edit, 0) 24 | } 25 | 26 | fun toggle(context: Context) { 27 | val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager 28 | imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0) 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /libraries/abstraction/src/main/java/isfaaghyth/app/abstraction/util/viewmodel/ViewModelFactory.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.abstraction.util.viewmodel 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.ViewModelProvider 5 | import javax.inject.Inject 6 | import javax.inject.Provider 7 | 8 | class ViewModelFactory @Inject constructor( 9 | private val creators: Map, @JvmSuppressWildcards Provider>): ViewModelProvider.Factory { 10 | 11 | override fun create(modelClass: Class): T { 12 | val creator = creators[modelClass] ?: creators.asIterable().firstOrNull { 13 | modelClass.isAssignableFrom(it.key) 14 | }?.value ?: throw IllegalArgumentException("unknown model class $modelClass") 15 | 16 | return try { 17 | creator.get() as T 18 | } catch (e: Exception) { 19 | throw RuntimeException(e) 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /libraries/abstraction/src/main/java/isfaaghyth/app/abstraction/util/viewmodel/ViewModelKey.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.abstraction.util.viewmodel 2 | 3 | import androidx.lifecycle.ViewModel 4 | import dagger.MapKey 5 | import kotlin.reflect.KClass 6 | 7 | @MustBeDocumented 8 | @Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) 9 | @Retention(AnnotationRetention.RUNTIME) 10 | @MapKey 11 | annotation class ViewModelKey(val value: KClass) -------------------------------------------------------------------------------- /libraries/abstraction/src/main/res/drawable/bg_gradient.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /libraries/abstraction/src/main/res/mipmap-hdpi/ic_placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/libraries/abstraction/src/main/res/mipmap-hdpi/ic_placeholder.png -------------------------------------------------------------------------------- /libraries/abstraction/src/main/res/mipmap-hdpi/ic_rating.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/libraries/abstraction/src/main/res/mipmap-hdpi/ic_rating.png -------------------------------------------------------------------------------- /libraries/abstraction/src/main/res/mipmap-hdpi/ic_voter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/libraries/abstraction/src/main/res/mipmap-hdpi/ic_voter.png -------------------------------------------------------------------------------- /libraries/abstraction/src/main/res/mipmap-mdpi/ic_placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/libraries/abstraction/src/main/res/mipmap-mdpi/ic_placeholder.png -------------------------------------------------------------------------------- /libraries/abstraction/src/main/res/mipmap-mdpi/ic_rating.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/libraries/abstraction/src/main/res/mipmap-mdpi/ic_rating.png -------------------------------------------------------------------------------- /libraries/abstraction/src/main/res/mipmap-mdpi/ic_voter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/libraries/abstraction/src/main/res/mipmap-mdpi/ic_voter.png -------------------------------------------------------------------------------- /libraries/abstraction/src/main/res/mipmap-xhdpi/ic_placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/libraries/abstraction/src/main/res/mipmap-xhdpi/ic_placeholder.png -------------------------------------------------------------------------------- /libraries/abstraction/src/main/res/mipmap-xhdpi/ic_rating.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/libraries/abstraction/src/main/res/mipmap-xhdpi/ic_rating.png -------------------------------------------------------------------------------- /libraries/abstraction/src/main/res/mipmap-xhdpi/ic_voter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/libraries/abstraction/src/main/res/mipmap-xhdpi/ic_voter.png -------------------------------------------------------------------------------- /libraries/abstraction/src/main/res/mipmap-xxhdpi/ic_placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/libraries/abstraction/src/main/res/mipmap-xxhdpi/ic_placeholder.png -------------------------------------------------------------------------------- /libraries/abstraction/src/main/res/mipmap-xxhdpi/ic_rating.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/libraries/abstraction/src/main/res/mipmap-xxhdpi/ic_rating.png -------------------------------------------------------------------------------- /libraries/abstraction/src/main/res/mipmap-xxhdpi/ic_voter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/libraries/abstraction/src/main/res/mipmap-xxhdpi/ic_voter.png -------------------------------------------------------------------------------- /libraries/abstraction/src/main/res/mipmap-xxxhdpi/ic_placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/libraries/abstraction/src/main/res/mipmap-xxxhdpi/ic_placeholder.png -------------------------------------------------------------------------------- /libraries/abstraction/src/main/res/mipmap-xxxhdpi/ic_rating.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/libraries/abstraction/src/main/res/mipmap-xxxhdpi/ic_rating.png -------------------------------------------------------------------------------- /libraries/abstraction/src/main/res/mipmap-xxxhdpi/ic_voter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isfaaghyth/kotlin-everywhere-example/c055a424869755f32b058a4c46f90a39dff97c17/libraries/abstraction/src/main/res/mipmap-xxxhdpi/ic_voter.png -------------------------------------------------------------------------------- /libraries/abstraction/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /libraries/abstraction/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /libraries/abstraction/src/test/java/isfaaghyth/app/abstraction/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.abstraction; 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 | } -------------------------------------------------------------------------------- /libraries/network/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /libraries/network/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "$rootDir/common.gradle" 2 | 3 | dependencies { 4 | //network 5 | implementation Retrofit.retrofit 6 | implementation Retrofit.gsonConverter 7 | implementation Retrofit.coroutinesAdapter 8 | implementation Retrofit.okHttpLogging 9 | } -------------------------------------------------------------------------------- /libraries/network/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 | -------------------------------------------------------------------------------- /libraries/network/src/androidTest/java/isfaaghyth/app/network/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.network; 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("isfaaghyth.app.network.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /libraries/network/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /libraries/network/src/main/java/isfaaghyth/app/network/Network.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.network 2 | 3 | import com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory 4 | import okhttp3.OkHttpClient 5 | import okhttp3.logging.HttpLoggingInterceptor 6 | import retrofit2.Retrofit 7 | import retrofit2.converter.gson.GsonConverterFactory 8 | import java.util.concurrent.TimeUnit 9 | 10 | object Network { 11 | 12 | fun retrofitClient(url: String = BuildConfig.MOVIE_URL): Retrofit { 13 | return Retrofit.Builder() 14 | .baseUrl(url) 15 | .addCallAdapterFactory(CoroutineCallAdapterFactory()) 16 | .addConverterFactory(GsonConverterFactory.create()) 17 | .client(okHttpClient()) 18 | .build() 19 | } 20 | 21 | private fun okHttpClient(): OkHttpClient { 22 | return OkHttpClient.Builder() 23 | .retryOnConnectionFailure(true) 24 | .addInterceptor(NetworkInterceptor()) 25 | .addInterceptor(createLoggingInterceptor()) 26 | .pingInterval(30, TimeUnit.SECONDS) 27 | .readTimeout(1, TimeUnit.MINUTES) 28 | .connectTimeout(1, TimeUnit.MINUTES) 29 | .build() 30 | } 31 | 32 | private fun createLoggingInterceptor(): HttpLoggingInterceptor { 33 | val interceptor = HttpLoggingInterceptor() 34 | interceptor.level = HttpLoggingInterceptor.Level.BODY 35 | return interceptor 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /libraries/network/src/main/java/isfaaghyth/app/network/NetworkInterceptor.kt: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.network 2 | 3 | import okhttp3.Interceptor 4 | import okhttp3.Response 5 | 6 | class NetworkInterceptor: Interceptor { 7 | 8 | override fun intercept(chain: Interceptor.Chain): Response { 9 | var request = chain.request() 10 | val url = request.url() 11 | .newBuilder() 12 | .addQueryParameter("api_key", BuildConfig.API_KEY) 13 | .build() 14 | request = request.newBuilder().url(url).build() 15 | return chain.proceed(request) 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /libraries/network/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /libraries/network/src/test/java/isfaaghyth/app/network/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package isfaaghyth.app.network; 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 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | //app 2 | include Modules.app 3 | 4 | //features 5 | include Modules.movies 6 | include Modules.tvshows 7 | include Modules.movieDetail 8 | 9 | //libraries 10 | include Modules.network 11 | include Modules.abstraction 12 | 13 | //data 14 | include Modules.data --------------------------------------------------------------------------------