10 | }
11 |
12 | interface FlowableRetrieveInteractor : Interactor {
13 | fun execute(params: P): Flowable
14 | }
15 | // marker class
16 | abstract class Params
17 | }
--------------------------------------------------------------------------------
/shows/shows_details_presentation/src/main/java/com/raqun/movies/shows/details/presentation/TvShowDetailsActivityModule.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.shows.details.presentation
2 |
3 | import com.raqun.movies.core.injection.scope.ActivityScope
4 | import dagger.Module
5 | import dagger.android.ContributesAndroidInjector
6 |
7 | @Module
8 | abstract class TvShowDetailsActivityModule {
9 | @ActivityScope
10 | @ContributesAndroidInjector(modules = [TvShowsDetailsFragmentModule::class])
11 | abstract fun provideTvShowsDetailsActivity(): TvShowDetailsActivity
12 | }
--------------------------------------------------------------------------------
/shows/shows_details_presentation/src/test/java/com/raqun/movies/shows/details/presentation/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.shows.details.presentation;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/shows/shows_presentation/src/main/java/com/raqun/movie/shows/presentation/PopularTvShowViewEntity.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movie.shows.presentation
2 |
3 | import com.raqun.movies.core.presentation.entity.ViewEntity
4 | import com.raqun.movies.core.presentation.recyclerview.DisplayItem
5 | import java.lang.NumberFormatException
6 |
7 | class PopularTvShowViewEntity(
8 | val id: Int?,
9 | val name: String?,
10 | val rating: Number?,
11 | val picture: String?
12 | ) : ViewEntity, DisplayItem {
13 |
14 | override fun type() = PopularTvShowsPresentationConstants.TYPES.SHOW
15 | }
--------------------------------------------------------------------------------
/base/core_navigation/src/main/java/com/raqun/movies/core/navigation/Loader.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.core.navigation
2 |
3 | const val PACKAGE_NAME = "com.raqun.movies"
4 |
5 | private val classMap = mutableMapOf>()
6 |
7 | private inline fun Any.castOrReturnNull() = this as? T
8 |
9 | internal fun String.loadClassOrReturnNull(): Class? =
10 | classMap.getOrPut(this) {
11 | try {
12 | Class.forName(this)
13 | } catch (e: ClassNotFoundException) {
14 | return null
15 | }
16 | }.castOrReturnNull()
--------------------------------------------------------------------------------
/base/core_presentation/src/main/java/com/raqun/movies/core/presentation/recyclerview/ViewHolder.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.core.presentation.recyclerview
2 |
3 | import android.view.View
4 | import androidx.recyclerview.widget.RecyclerView
5 | import com.raqun.movies.core.presentation.entity.ViewEntity
6 |
7 | abstract class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
8 |
9 | var itemClickListener: ((item: DisplayItem) -> Unit)? = null
10 | var itemLongClickListener: ((item: DisplayItem) -> Boolean)? = null
11 |
12 | abstract fun bind(item: T)
13 | }
--------------------------------------------------------------------------------
/shows/shows_data/src/main/java/com/raqun/movies/shows/data/TvShowMapper.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.shows.data
2 |
3 | import com.raqun.movies.core.data.db.entity.TvShowEntity
4 | import com.raqun.movies.shows.domain.TvShow
5 | import io.reactivex.functions.Function
6 |
7 | class TvShowMapper : Function {
8 |
9 | override fun apply(t: TvShowEntity): TvShow {
10 | return TvShow(
11 | t.tvShowId.toInt(),
12 | t.name,
13 | t.voteAvarage,
14 | t.posterPath,
15 | t.popularity
16 | )
17 | }
18 |
19 | }
--------------------------------------------------------------------------------
/base/core_data/src/main/java/com/raqun/movies/core/data/injection/modules/DbModule.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.core.data.injection.modules
2 |
3 | import android.content.Context
4 | import androidx.room.Room
5 | import com.raqun.movies.core.data.db.Db
6 | import com.raqun.movies.core.data.db.MoviesDb
7 | import dagger.Module
8 | import dagger.Provides
9 | import javax.inject.Singleton
10 |
11 |
12 | @Module
13 | class DbModule {
14 | @Singleton
15 | @Provides
16 | fun provideDb(context: Context): MoviesDb = Room.databaseBuilder(
17 | context,
18 | MoviesDb::class.java, Db.Config.DB_NAME
19 | ).build()
20 | }
--------------------------------------------------------------------------------
/shows/shows_data/src/main/java/com/raqun/movies/shows/data/TvShowEntityMapper.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.shows.data
2 |
3 | import com.raqun.movies.core.data.db.entity.TvShowEntity
4 | import com.raqun.movies.shows.domain.TvShow
5 | import io.reactivex.functions.Function
6 |
7 | class TvShowEntityMapper : Function {
8 |
9 | override fun apply(p0: TvShow): TvShowEntity {
10 | return TvShowEntity(
11 | p0.id!!.toLong(),
12 | p0.name!!,
13 | p0.posterPath!!,
14 | p0.votesAverage!!.toDouble(),
15 | p0.popularity.toInt()
16 | )
17 | }
18 |
19 | }
--------------------------------------------------------------------------------
/base/core_presentation/src/main/java/com/raqun/movies/core/presentation/viewmodel/ReactiveViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.core.presentation.viewmodel
2 |
3 | import androidx.lifecycle.ViewModel
4 | import io.reactivex.disposables.CompositeDisposable
5 | import io.reactivex.disposables.Disposable
6 |
7 | abstract class ReactiveViewModel() : ViewModel() {
8 |
9 | private val compositeDisposable = CompositeDisposable()
10 |
11 | protected fun action(disposable: Disposable) {
12 | compositeDisposable.add(disposable)
13 | }
14 |
15 | override fun onCleared() {
16 | compositeDisposable.clear()
17 | super.onCleared()
18 | }
19 | }
--------------------------------------------------------------------------------
/shows/shows_data/src/main/java/com/raqun/movies/shows/data/TvShowDetailMapper.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.shows.data
2 |
3 | import com.raqun.movies.core.data.db.entity.TvShowDetailEntity
4 | import com.raqun.movies.shows.domain.TvShowDetail
5 | import io.reactivex.functions.Function
6 |
7 | class TvShowDetailMapper : Function {
8 |
9 | override fun apply(t: TvShowDetailEntity): TvShowDetail {
10 | return TvShowDetail(
11 | t.tvShowId.toInt(),
12 | t.name,
13 | t.voteAvarage,
14 | t.posterPath,
15 | t.overview,
16 | t.voteCount
17 | )
18 | }
19 |
20 | }
--------------------------------------------------------------------------------
/shows/shows_data/src/main/java/com/raqun/movies/shows/data/TvShowsServices.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.shows.data
2 |
3 | import com.raqun.movies.core.data.api.PagedApiResponse
4 | import com.raqun.movies.shows.domain.TvShow
5 | import com.raqun.movies.shows.domain.TvShowDetail
6 | import io.reactivex.Single
7 | import retrofit2.http.GET
8 | import retrofit2.http.Path
9 | import retrofit2.http.Query
10 |
11 | interface TvShowsServices {
12 | @GET("/3/tv/popular")
13 | fun getPopularTvShows(@Query("page") page: Int = 1): Single>>
14 |
15 | @GET("/3/tv/{tv_id}")
16 | fun getTvShowDetail(@Path("tv_id") id: Int): Single
17 | }
--------------------------------------------------------------------------------
/base/core_data/src/main/java/com/raqun/movies/core/data/db/dao/TvShowDetailsDao.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.core.data.db.dao
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Insert
5 | import androidx.room.OnConflictStrategy
6 | import androidx.room.Query
7 | import com.raqun.movies.core.data.db.entity.TvShowDetailEntity
8 | import io.reactivex.Flowable
9 |
10 | @Dao
11 | interface TvShowDetailsDao {
12 | @Query("SELECT * FROM tv_show_details WHERE id = :tvShowId")
13 | fun getTvShowDetail(tvShowId: Long): Flowable
14 |
15 | @Insert(onConflict = OnConflictStrategy.REPLACE)
16 | fun addTvShowDetail(tvShowDetailEntity: TvShowDetailEntity): Long
17 | }
--------------------------------------------------------------------------------
/base/core_presentation/src/main/java/com/raqun/movies/core/presentation/recyclerview/DiffAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.core.presentation.recyclerview
2 |
3 | import androidx.recyclerview.widget.DiffUtil
4 |
5 | interface DiffAdapter {
6 | fun addItems(newItems: List)
7 |
8 | fun update(newItems: List)
9 |
10 | fun updateAllItems(newItems: List)
11 |
12 | fun updateOnlyChangedItems(newItems: List)
13 |
14 | fun updateItems(newItems: List)
15 |
16 | fun calculateDiff(newItems: List): DiffUtil.DiffResult
17 |
18 | fun updateWithOnlyDiffResult(result: DiffUtil.DiffResult)
19 | }
--------------------------------------------------------------------------------
/shows/shows_details_presentation/src/main/java/com/raqun/movies/shows/details/presentation/TvShowDetailsViewEntity.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.shows.details.presentation
2 |
3 | import com.google.gson.annotations.SerializedName
4 | import com.raqun.movies.core.presentation.entity.ViewEntity
5 |
6 | data class TvShowDetailsViewEntity(
7 | @SerializedName("id") val id: Int?,
8 | @SerializedName("name") val name: String?,
9 | @SerializedName("vote_average") val votesAverage: Number?,
10 | @SerializedName("poster_path") val posterPath: String?,
11 | @SerializedName("overview") val overview: String,
12 | @SerializedName("vote_count") val VoteCount: Int?
13 | ) : ViewEntity
--------------------------------------------------------------------------------
/shows/shows_details_presentation/src/main/java/com/raqun/movies/shows/details/presentation/TvShowDetailsViewEntityMapper.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.shows.details.presentation
2 |
3 | import com.raqun.movies.shows.domain.TvShowDetail
4 | import io.reactivex.functions.Function
5 |
6 | class TvShowDetailsViewEntityMapper : Function {
7 |
8 | override fun apply(t: TvShowDetail): TvShowDetailsViewEntity {
9 | return TvShowDetailsViewEntity(
10 | t.id,
11 | t.name,
12 | t.votesAverage,
13 | t.posterPath,
14 | t.overview,
15 | t.voteCount
16 | )
17 | }
18 |
19 | }
--------------------------------------------------------------------------------
/base/core_navigation/src/main/java/com/raqun/movies/core/navigation/features/TvShowDetails.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.core.navigation.features
2 |
3 | import android.content.Intent
4 | import com.raqun.movies.core.navigation.PACKAGE_NAME
5 | import com.raqun.movies.core.navigation.loadIntentOrReturnNull
6 |
7 | object TvShowDetails : Feature {
8 |
9 | private const val SHOW_DETAIL = "$PACKAGE_NAME.shows.details.presentation.TvShowDetailsActivity"
10 |
11 | const val BUNDLE_ID = "id"
12 |
13 | override val dynamicStart: Intent?
14 | get() = SHOW_DETAIL.loadIntentOrReturnNull()
15 |
16 | fun dynamicStart(id: Int) = dynamicStart?.putExtra(BUNDLE_ID, id)
17 | }
18 |
--------------------------------------------------------------------------------
/shows/shows_data/src/main/java/com/raqun/movies/shows/data/TvShowDetailEntityMapper.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.shows.data
2 |
3 | import com.raqun.movies.core.data.db.entity.TvShowDetailEntity
4 | import com.raqun.movies.shows.domain.TvShowDetail
5 | import io.reactivex.functions.Function
6 |
7 | class TvShowDetailEntityMapper : Function {
8 |
9 | override fun apply(t: TvShowDetail): TvShowDetailEntity {
10 | return TvShowDetailEntity(
11 | t.id!!.toLong(),
12 | t.name!!,
13 | t.posterPath!!,
14 | t.votesAverage!!.toDouble(),
15 | t.overview,
16 | t.voteCount!!
17 | )
18 | }
19 |
20 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/raqun/movies/injection/modules/ViewModelModule.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.injection.modules
2 |
3 | import androidx.lifecycle.ViewModelProvider
4 | import com.raqun.movie.shows.presentation.PopularTvShowsViewModelModule
5 | import com.raqun.movies.core.presentation.viewmodel.VmFactory
6 | import com.raqun.movies.shows.details.presentation.TvShowDetailsViewModelModule
7 | import dagger.Binds
8 | import dagger.Module
9 |
10 | @Module(
11 | includes = [PopularTvShowsViewModelModule::class,
12 | TvShowDetailsViewModelModule::class]
13 | )
14 | internal abstract class ViewModelModule {
15 | @Binds
16 | abstract fun bindViewModelFactory(vmFactory: VmFactory): ViewModelProvider.Factory
17 | }
--------------------------------------------------------------------------------
/shows/shows_presentation/src/main/java/com/raqun/movie/shows/presentation/PopularTvShowsViewEntityMapper.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movie.shows.presentation
2 |
3 | import com.raqun.movies.core.presentation.Constants.Companion.IMAGE_BASE_URL
4 | import com.raqun.movies.core.presentation.recyclerview.DisplayItem
5 | import com.raqun.movies.shows.domain.TvShow
6 | import io.reactivex.functions.Function
7 |
8 | class PopularTvShowsViewEntityMapper : Function {
9 |
10 | override fun apply(t: TvShow): DisplayItem {
11 | return PopularTvShowViewEntity(
12 | t.id,
13 | t.name,
14 | t.votesAverage,
15 | IMAGE_BASE_URL + t.posterPath
16 | )
17 | }
18 | }
--------------------------------------------------------------------------------
/shows/shows_data/src/main/java/com/raqun/movies/shows/data/GetTvShowDetailRemoteDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.shows.data
2 |
3 | import android.annotation.SuppressLint
4 | import com.raqun.movies.core.data.source.DataSource
5 | import com.raqun.movies.shows.domain.PagedTvShows
6 | import com.raqun.movies.shows.domain.TvShowDetail
7 | import io.reactivex.Single
8 | import javax.inject.Inject
9 |
10 | class GetTvShowDetailRemoteDataSource @Inject constructor(private val tvShowsServices: TvShowsServices) :
11 | DataSource.RetrieveRemoteDataSource {
12 |
13 | @SuppressLint("CheckResult")
14 | override fun getResult(request: Int): Single = tvShowsServices.getTvShowDetail(request)
15 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/raqun/movies/injection/modules/ActivityModule.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.injection.modules
2 |
3 | import com.raqun.movie.shows.presentation.PopularTvShowsFragmentModule
4 | import com.raqun.movies.core.injection.scope.ActivityScope
5 | import com.raqun.movies.shows.details.presentation.TvShowDetailsActivityModule
6 | import com.raqun.movies.ui.MainActivity
7 | import dagger.Module
8 | import dagger.android.ContributesAndroidInjector
9 |
10 | @Module(includes = [TvShowDetailsActivityModule::class])
11 | internal abstract class ActivityModule {
12 | @ContributesAndroidInjector(modules = [PopularTvShowsFragmentModule::class])
13 | @ActivityScope
14 | abstract fun contributeMainActivityInjector(): MainActivity
15 | }
--------------------------------------------------------------------------------
/base/core_data/src/main/java/com/raqun/movies/core/data/db/dao/TvShowsDao.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.core.data.db.dao
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Insert
5 | import androidx.room.OnConflictStrategy
6 | import androidx.room.Query
7 | import com.raqun.movies.core.data.db.entity.TvShowEntity
8 | import io.reactivex.Flowable
9 |
10 | @Dao
11 | interface TvShowsDao {
12 |
13 | @Query("SELECT * FROM tv_shows ORDER BY popularity")
14 | fun getTvShows(): Flowable>
15 |
16 | @Insert(onConflict = OnConflictStrategy.REPLACE)
17 | fun addTvShow(tvShowEntity: TvShowEntity): Long
18 |
19 | @Insert(onConflict = OnConflictStrategy.REPLACE)
20 | fun addTvShows(shows: List): List
21 | }
--------------------------------------------------------------------------------
/base/core_presentation/src/main/java/com/raqun/movies/core/presentation/base/BaseInjectionFragment.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.core.presentation.base
2 |
3 | import android.content.Context
4 | import androidx.annotation.CallSuper
5 | import dagger.android.support.AndroidSupportInjection
6 | import dagger.android.support.HasSupportFragmentInjector
7 |
8 | abstract class BaseInjectionFragment : BaseFragment() {
9 |
10 | @CallSuper
11 | override fun onAttach(context: Context?) {
12 | if (activity is HasSupportFragmentInjector) {
13 | AndroidSupportInjection.inject(this)
14 | onInject()
15 | }
16 | super.onAttach(context)
17 | }
18 |
19 | open fun onInject() {
20 | // empty for override
21 | }
22 | }
--------------------------------------------------------------------------------
/shows/shows_domain/src/main/java/com/raqun/movies/shows/domain/GetPopularTvShowsInteractor.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.shows.domain
2 |
3 | import com.raqun.movies.core.domain.Interactor
4 | import io.reactivex.Flowable
5 | import javax.inject.Inject
6 |
7 | class GetPopularTvShowsInteractor @Inject constructor(private val tvShowsRepository: TvShowsRepository) :
8 | Interactor.FlowableRetrieveInteractor> {
9 |
10 | override fun execute(params: Params): Flowable> {
11 | require(params.page > 0) { "Invalid current page number" }
12 | return tvShowsRepository.getPopularTShows(params.page)
13 | }
14 |
15 | class Params(val page: Int, val totalPage: Int) : Interactor.Params()
16 | }
--------------------------------------------------------------------------------
/shows/shows_domain/src/main/java/com/raqun/movies/shows/domain/ShowsDomainModule.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.shows.domain
2 |
3 | import com.raqun.movies.core.domain.Interactor
4 | import dagger.Module
5 | import dagger.Provides
6 |
7 | @Module
8 | class ShowsDomainModule {
9 |
10 | @Provides
11 | fun providePopularTvShowsInteractor(tvShowsRepository: TvShowsRepository):
12 | Interactor.FlowableRetrieveInteractor> =
13 | GetPopularTvShowsInteractor(tvShowsRepository)
14 |
15 | @Provides
16 | fun provideGetTvShowDetail(tvShowsRepository: TvShowsRepository): Interactor.ReactiveRetrieveInteractor =
17 | GetTvShowDetailInteractor(tvShowsRepository)
18 | }
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/raqun/movies/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies
2 |
3 | import android.support.test.InstrumentationRegistry
4 | import android.support.test.runner.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getTargetContext()
22 | assertEquals("com.raqun.movies", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/base/core_data/src/main/java/com/raqun/movies/core/data/db/MoviesDb.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.core.data.db
2 |
3 | import androidx.room.Database
4 | import androidx.room.RoomDatabase
5 | import com.raqun.movies.core.data.db.dao.TvShowDetailsDao
6 | import com.raqun.movies.core.data.db.dao.TvShowsDao
7 | import com.raqun.movies.core.data.db.entity.TvShowDetailEntity
8 | import com.raqun.movies.core.data.db.entity.TvShowEntity
9 | import javax.inject.Singleton
10 |
11 | @Singleton
12 | @Database(
13 | entities = [TvShowEntity::class, TvShowDetailEntity::class],
14 | version = Db.Config.DB_VERSION,
15 | exportSchema = true
16 | )
17 | abstract class MoviesDb : RoomDatabase() {
18 | abstract fun tvShowsDao(): TvShowsDao
19 |
20 | abstract fun tvShowDetailsDao(): TvShowDetailsDao
21 | }
--------------------------------------------------------------------------------
/base/core_data/src/main/java/com/raqun/movies/core/data/source/DataSource.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.core.data.source
2 |
3 | import io.reactivex.Flowable
4 | import io.reactivex.Single
5 |
6 | interface DataSource {
7 |
8 | interface RetrieveRemoteDataSource : DataSource {
9 | fun getResult(request: Req): Single
10 | }
11 |
12 | interface FlowableLocal : DataSource {
13 |
14 | fun get(key: K?): Flowable
15 |
16 | fun put(key: K?, data: V): Boolean
17 |
18 | fun remove(value: V): Boolean
19 |
20 | fun clear()
21 | }
22 |
23 | interface Cache : DataSource {
24 | fun get(key: KEY): VALUE?
25 |
26 | fun put(key: KEY, value: VALUE): Boolean
27 |
28 | fun drop()
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/shows/shows_domain/src/main/java/com/raqun/movies/shows/domain/GetTvShowDetailInteractor.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.shows.domain
2 |
3 | import com.raqun.movies.core.domain.Interactor
4 | import io.reactivex.Observable
5 | import java.lang.IllegalArgumentException
6 | import javax.inject.Inject
7 |
8 | class GetTvShowDetailInteractor @Inject constructor(private val tvShowsRepository: TvShowsRepository) :
9 | Interactor.ReactiveRetrieveInteractor {
10 |
11 | override fun execute(params: Params): Observable {
12 | require(!(params.id == null || params.id <= 0)) { "Id cannot be null or negative!" }
13 | return tvShowsRepository.getShowDetail(params.id).toObservable()
14 | }
15 |
16 | class Params(val id: Int?) : Interactor.Params()
17 | }
--------------------------------------------------------------------------------
/base/core_data/src/main/java/com/raqun/movies/core/data/db/entity/TvShowEntity.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.core.data.db.entity
2 |
3 | import androidx.room.ColumnInfo
4 | import androidx.room.Entity
5 | import androidx.room.PrimaryKey
6 | import com.raqun.movies.core.data.db.Db
7 |
8 | @Entity(tableName = Db.TABLES.TVSHOWS.NAME)
9 | class TvShowEntity constructor(
10 | @PrimaryKey(autoGenerate = true) @ColumnInfo(name = Db.TABLES.TVSHOWS.COLUMNS.ID) val tvShowId: Long,
11 | @ColumnInfo(name = Db.TABLES.TVSHOWS.COLUMNS.SHOW_NAME) val name: String,
12 | @ColumnInfo(name = Db.TABLES.TVSHOWS.COLUMNS.POSTER_PATH) val posterPath: String,
13 | @ColumnInfo(name = Db.TABLES.TVSHOWS.COLUMNS.VOTE_AVARAGE) val voteAvarage: Double,
14 | @ColumnInfo(name = Db.TABLES.TVSHOWS.COLUMNS.POPULARITY) val popularity: Int
15 | ) : DbEntity
--------------------------------------------------------------------------------
/app/src/main/java/com/raqun/movies/MoviesApp.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies
2 |
3 | import android.app.Activity
4 | import android.app.Application
5 | import com.raqun.movies.injection.components.DaggerAppComponent
6 | import dagger.android.AndroidInjector
7 | import dagger.android.DispatchingAndroidInjector
8 | import dagger.android.HasActivityInjector
9 | import javax.inject.Inject
10 |
11 | class MoviesApp : Application(), HasActivityInjector {
12 |
13 | @Inject
14 | lateinit var dispachingAndroidInjector: DispatchingAndroidInjector
15 |
16 | override fun onCreate() {
17 | super.onCreate()
18 | DaggerAppComponent.builder()
19 | .application(this)
20 | .build()
21 | .inject(this)
22 | }
23 |
24 | override fun activityInjector() = dispachingAndroidInjector
25 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/base/core/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/base/core_data/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/base/core_domain/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/shows/shows_data/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/base/core_navigation/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/base/core_presentation/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/base/core_presentation/src/main/res/layout/toolbar_default.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/shows/shows_domain/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/shows/shows_presentation/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/shows/shows_details_presentation/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/base/core_data/src/main/java/com/raqun/movies/core/data/api/DefaultRequestInterceptor.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.core.data.api
2 |
3 | import okhttp3.Interceptor
4 | import okhttp3.Response
5 | import javax.inject.Inject
6 |
7 | private const val CONTENT_TYPE = "Content-Type"
8 | private const val JSON = "application/json"
9 | private const val KEY_API = "api_key"
10 |
11 | class DefaultRequestInterceptor @Inject constructor() : Interceptor {
12 |
13 | override fun intercept(chain: Interceptor.Chain): Response {
14 | val url = chain.request()
15 | .url()
16 | .newBuilder().addQueryParameter(KEY_API, ApiConstants.API_KEY)
17 | .build()
18 |
19 | return chain.proceed(with(chain.request().newBuilder()) {
20 | url(url)
21 | addHeader(CONTENT_TYPE, JSON)
22 | build()
23 | })
24 | }
25 | }
--------------------------------------------------------------------------------
/base/core_presentation/src/main/java/com/raqun/movies/core/presentation/base/BaseViewModelFragment.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.core.presentation.base
2 |
3 | import android.os.Bundle
4 | import androidx.annotation.CallSuper
5 | import androidx.lifecycle.ViewModel
6 | import androidx.lifecycle.ViewModelProvider
7 | import androidx.lifecycle.ViewModelProviders
8 | import javax.inject.Inject
9 |
10 | abstract class BaseViewModelFragment : BaseInjectionFragment() {
11 |
12 | @Inject
13 | protected lateinit var vmFactory: ViewModelProvider.Factory
14 |
15 | protected lateinit var viewModel: VM
16 |
17 | abstract fun getModelClass(): Class
18 |
19 | @CallSuper
20 | override fun onCreate(savedInstanceState: Bundle?) {
21 | super.onCreate(savedInstanceState)
22 | viewModel = ViewModelProviders.of(this, vmFactory).get(getModelClass())
23 | }
24 | }
--------------------------------------------------------------------------------
/base/core_presentation/src/main/java/com/raqun/movies/core/presentation/recyclerview/DiffUtilImpl.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.core.presentation.recyclerview
2 |
3 | import androidx.recyclerview.widget.DiffUtil
4 |
5 | class DiffUtilImpl(
6 | private val oldItems: List,
7 | private val newItems: List,
8 | private val comparator: DisplayItemComperator
9 | ) : DiffUtil.Callback() {
10 |
11 | override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
12 | comparator.areItemsSame(oldItems[oldItemPosition], newItems[newItemPosition])
13 |
14 | override fun getOldListSize() = oldItems.size
15 |
16 | override fun getNewListSize() = newItems.size
17 |
18 | override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
19 | comparator.areContentsSame(oldItems[oldItemPosition], newItems[newItemPosition])
20 |
21 | }
--------------------------------------------------------------------------------
/shows/shows_presentation/src/main/res/layout/fragment_popular_tv_shows.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/base/core_presentation/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #0f0f0f
5 | #0e0e0e
6 | #8BC34A
7 | #212121
8 | #757575
9 | #BDBDBD
10 | #303030
11 | #0f0f0f
12 | #262626
13 | #0e0e0e
14 | #3D3B3C
15 | #FFFFFF
16 | #e5e5e5
17 | #757575
18 |
19 |
--------------------------------------------------------------------------------
/base/core/src/androidTest/java/com/raqun/movies/core/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.core;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.raqun.movies.core.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/raqun/movies/injection/components/AppComponent.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.injection.components
2 |
3 | import com.raqun.movies.MoviesApp
4 | import com.raqun.movies.injection.modules.*
5 | import dagger.BindsInstance
6 | import dagger.Component
7 | import dagger.android.AndroidInjectionModule
8 | import javax.inject.Singleton
9 |
10 | @Singleton
11 | @Component(
12 | modules = [
13 | AndroidInjectionModule::class,
14 | ApplicationModule::class,
15 | ActivityModule::class,
16 | DomainModule::class,
17 | DataModule::class,
18 | ViewModelModule::class
19 | ]
20 | )
21 | interface AppComponent {
22 |
23 | @Component.Builder
24 | interface Builder {
25 | @BindsInstance
26 | fun application(application: MoviesApp): Builder
27 |
28 | fun build(): AppComponent
29 | }
30 |
31 | fun inject(application: MoviesApp)
32 | }
--------------------------------------------------------------------------------
/base/core_data/src/androidTest/java/com/raqun/movies/core/data/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.core.data;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.raqun.movies.core.data.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/shows/shows_data/src/androidTest/java/com/raqun/movies/shows/data/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.shows.data;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.raqun.movies.shows.data.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/base/core_domain/src/androidTest/java/com/raqun/movies/core/domain/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.core.domain;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.raqun.movies.core.domain.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/base/core_navigation/src/androidTest/java/com/raqun/movies/core/navigation/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.core.navigation;
2 |
3 | import android.content.Context;
4 | import androidx.test.InstrumentationRegistry;
5 | import androidx.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.raqun.movies.core.navigation.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # Kotlin code style for this project: "official" or "obsolete":
15 | kotlin.code.style=official
16 | android.useAndroidX=true
17 | android.enableJetifier=true
18 |
--------------------------------------------------------------------------------
/base/core_presentation/src/main/java/com/raqun/movies/core/presentation/extensions/RecyclerViewExt.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.core.presentation.extensions
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import androidx.recyclerview.widget.LinearLayoutManager
6 | import androidx.recyclerview.widget.RecyclerView
7 | import com.raqun.movies.core.presentation.recyclerview.RecyclerViewAdapter
8 |
9 | /*
10 | * Setups RecyclerView with Default parameters
11 | */
12 | @SuppressLint("WrongConstant")
13 | fun RecyclerView.setup(
14 | context: Context,
15 | orientation: Int = LinearLayoutManager.VERTICAL,
16 | adapter: RecyclerViewAdapter?
17 | ) {
18 | val layoutManager = LinearLayoutManager(context)
19 | layoutManager.orientation = orientation
20 | this.layoutManager = layoutManager
21 | this.setHasFixedSize(false)
22 | adapter?.let {
23 | this.adapter = adapter
24 | }
25 | }
--------------------------------------------------------------------------------
/shows/shows_domain/src/androidTest/java/com/raqun/movies/shows/domain/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.shows.domain;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.raqun.movies.shows.domain.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/base/core_presentation/src/androidTest/java/com/raqun/movies/core/presentation/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.core.presentation;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.raqun.movies.core.presentation.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/shows/shows_presentation/src/androidTest/java/com/raqun/movie/shows/presentation/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.raqun.movie.shows.presentation;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.raqun.movie.shows.presentation.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/base/core_data/src/main/java/com/raqun/movies/core/data/db/entity/TvShowDetailEntity.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.core.data.db.entity
2 |
3 | import androidx.room.ColumnInfo
4 | import androidx.room.Entity
5 | import androidx.room.PrimaryKey
6 | import com.raqun.movies.core.data.db.Db
7 |
8 | @Entity(tableName = Db.TABLES.TVSHOW_DETAILS.NAME)
9 | class TvShowDetailEntity constructor(
10 | @PrimaryKey(autoGenerate = true) @ColumnInfo(name = Db.TABLES.TVSHOW_DETAILS.COLUMNS.ID) val tvShowId: Long,
11 | @ColumnInfo(name = Db.TABLES.TVSHOW_DETAILS.COLUMNS.SHOW_NAME) val name: String,
12 | @ColumnInfo(name = Db.TABLES.TVSHOW_DETAILS.COLUMNS.POSTER_PATH) val posterPath: String,
13 | @ColumnInfo(name = Db.TABLES.TVSHOW_DETAILS.COLUMNS.VOTE_AVARAGE) val voteAvarage: Double,
14 | @ColumnInfo(name = Db.TABLES.TVSHOW_DETAILS.COLUMNS.OVERVIEW) val overview: String,
15 | @ColumnInfo(name = Db.TABLES.TVSHOW_DETAILS.COLUMNS.VOTE_COOUNT) val voteCount: Int
16 | ) : DbEntity
--------------------------------------------------------------------------------
/shows/shows_details_presentation/src/androidTest/java/com/raqun/movies/shows/details/presentation/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.shows.details.presentation;
2 |
3 | import android.content.Context;
4 | import androidx.test.InstrumentationRegistry;
5 | import androidx.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.raqun.movies.shows.details.presentation.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/raqun/movies/ui/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.ui
2 |
3 | import android.os.Bundle
4 | import com.raqun.movie.shows.presentation.PopularTvShowsFragment
5 | import com.raqun.movies.R
6 | import com.raqun.movies.core.presentation.base.BaseInjectionActivity
7 | import com.raqun.movies.core.presentation.extensions.transact
8 | import com.raqun.movies.core.presentation.navigation.UiNavigation
9 |
10 | class MainActivity : BaseInjectionActivity() {
11 |
12 | override val uiNavigation = UiNavigation.ROOT
13 |
14 | override val toolbarRes: Int = R.id.toolbarDefault
15 |
16 | override fun getLayoutRes() = R.layout.activity_main
17 |
18 | override fun onCreate(savedInstanceState: Bundle?) {
19 | super.onCreate(savedInstanceState)
20 | if (savedInstanceState == null) {
21 | supportFragmentManager.transact {
22 | replace(R.id.framelayout_main, PopularTvShowsFragment.newInstance())
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/shows/shows_details_presentation/src/main/java/com/raqun/movies/shows/details/presentation/TvShowDetailsViewModelModule.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.shows.details.presentation
2 |
3 | import androidx.lifecycle.ViewModel
4 | import com.raqun.movies.core.presentation.viewmodel.ViewModelKey
5 | import com.raqun.movies.shows.domain.TvShowDetail
6 | import dagger.Binds
7 | import dagger.Module
8 | import dagger.Provides
9 | import dagger.multibindings.IntoMap
10 | import io.reactivex.functions.Function
11 |
12 | @Module
13 | abstract class TvShowDetailsViewModelModule {
14 | @Binds
15 | @IntoMap
16 | @ViewModelKey(TvShowDetailsViewModel::class)
17 | abstract fun bindTvShowDetailsViewModel(tvShowDetailsViewModel: TvShowDetailsViewModel): ViewModel
18 |
19 | @Module
20 | companion object {
21 | @JvmStatic
22 | @Provides
23 | fun provieTvShowDetailsViewEntityMapper(): Function =
24 | TvShowDetailsViewEntityMapper()
25 | }
26 | }
--------------------------------------------------------------------------------
/shows/shows_presentation/src/main/java/com/raqun/movie/shows/presentation/PopularTvShowsViewModelModule.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movie.shows.presentation
2 |
3 | import androidx.lifecycle.ViewModel
4 | import com.raqun.movies.core.presentation.recyclerview.DisplayItem
5 | import com.raqun.movies.core.presentation.viewmodel.ViewModelKey
6 | import com.raqun.movies.shows.domain.TvShow
7 | import dagger.Binds
8 | import dagger.Module
9 | import dagger.Provides
10 | import dagger.multibindings.IntoMap
11 | import io.reactivex.functions.Function
12 |
13 | @Module
14 | abstract class PopularTvShowsViewModelModule {
15 | @Binds
16 | @IntoMap
17 | @ViewModelKey(PopularTvShowsViewModel::class)
18 | abstract fun bindShowsViewModel(popularTvShowsViewModel: PopularTvShowsViewModel): ViewModel
19 |
20 | @Module
21 | companion object {
22 | @JvmStatic
23 | @Provides
24 | fun proviePopularTvShowsViewEntityMapper(): Function = PopularTvShowsViewEntityMapper()
25 | }
26 | }
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: Plugins.androidApplication
2 | apply from: "$rootDir/common.gradle"
3 |
4 | android {
5 | flavorDimensions Dimensions.default
6 | productFlavors {
7 | prod {
8 | // empty
9 | }
10 |
11 | dev {
12 | applicationIdSuffix Dev.applicationIdSuffix
13 | versionNameSuffix Dev.versionNameSuffix
14 | }
15 | }
16 | }
17 |
18 | dependencies {
19 |
20 | // Core
21 | implementation project(Modules.core)
22 | implementation project(Modules.coreData)
23 | implementation project(Modules.coreDomain)
24 | implementation project(Modules.corePresentation)
25 |
26 | // Tv Shows
27 | implementation project(Modules.showsPresentation)
28 | implementation project(Modules.showsDomain)
29 | implementation project(Modules.showsData)
30 | implementation project(Modules.showsDetailsPresentation)
31 |
32 | // Support Libraries
33 | implementation SupportLibraries.appCompat
34 | implementation SupportLibraries.design
35 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/base/core_presentation/src/main/java/com/raqun/movies/core/presentation/base/BaseInjectionActivity.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.core.presentation.base
2 |
3 | import android.os.Bundle
4 | import androidx.fragment.app.Fragment
5 | import com.raqun.movies.core.presentation.base.BaseActivity
6 | import dagger.android.AndroidInjection
7 | import dagger.android.AndroidInjector
8 | import dagger.android.DispatchingAndroidInjector
9 | import dagger.android.support.HasSupportFragmentInjector
10 | import javax.inject.Inject
11 |
12 | abstract class BaseInjectionActivity : BaseActivity(), HasSupportFragmentInjector {
13 |
14 | @Inject
15 | lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector
16 |
17 | override fun supportFragmentInjector(): AndroidInjector = dispatchingAndroidInjector
18 |
19 | override fun onCreate(savedInstanceState: Bundle?) {
20 | AndroidInjection.inject(this)
21 | super.onCreate(savedInstanceState)
22 | }
23 |
24 | open fun onInject() {
25 | // empty for override
26 | }
27 | }
--------------------------------------------------------------------------------
/shows/shows_data/src/main/java/com/raqun/movies/shows/data/GetPopularTvShowsRemoteDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.shows.data
2 |
3 | import android.annotation.SuppressLint
4 | import com.raqun.movies.core.data.source.DataSource
5 | import com.raqun.movies.shows.domain.PagedTvShows
6 | import io.reactivex.Single
7 | import javax.inject.Inject
8 |
9 | class GetPopularTvShowsRemoteDataSource @Inject constructor(private val tvShowsServices: TvShowsServices) :
10 | DataSource.RetrieveRemoteDataSource {
11 |
12 | @SuppressLint("CheckResult")
13 | override fun getResult(request: Int): Single =
14 | tvShowsServices.getPopularTvShows(request)
15 | .map {
16 | if (it.page == null
17 | || it.totalResults == null
18 | || it.totalPages == null
19 | || it.results == null
20 | ) {
21 | throw Exception("Unexpected response!")
22 | }
23 | return@map PagedTvShows(it.page!!, it.totalResults!!, it.totalPages!!, it.results!!)
24 | }
25 | }
--------------------------------------------------------------------------------
/shows/shows_details_presentation/src/main/java/com/raqun/movies/shows/details/presentation/TvShowDetailsActivity.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.shows.details.presentation
2 |
3 | import android.os.Bundle
4 | import com.raqun.movies.core.navigation.features.TvShowDetails
5 | import com.raqun.movies.core.presentation.base.BaseInjectionActivity
6 | import com.raqun.movies.core.presentation.extensions.transact
7 |
8 | class TvShowDetailsActivity : BaseInjectionActivity() {
9 |
10 | override fun getLayoutRes() = R.layout.activity_show_details
11 |
12 | override fun onCreate(savedInstanceState: Bundle?) {
13 | super.onCreate(savedInstanceState)
14 | if (savedInstanceState == null) {
15 | supportFragmentManager.transact {
16 | replace(
17 | R.id.framelayout_main,
18 | TvShowDetailsFragment.newInstance(
19 | intent?.getIntExtra(
20 | TvShowDetails.BUNDLE_ID,
21 | 0
22 | )
23 | )
24 | )
25 | }
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/base/core_presentation/src/main/java/com/raqun/movies/core/presentation/viewmodel/VmFactory.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.core.presentation.viewmodel
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.ViewModelProvider
5 | import javax.inject.Inject
6 | import javax.inject.Provider
7 | import javax.inject.Singleton
8 |
9 | @Singleton
10 | class VmFactory @Inject constructor(private val creators: Map, @JvmSuppressWildcards Provider>) :
11 | ViewModelProvider.Factory {
12 |
13 | @SuppressWarnings("Unchecked")
14 | override fun create(modelClass: Class): T {
15 | var creator = creators[modelClass]
16 |
17 | if (creator == null) {
18 | for (entry in creators) {
19 | if (modelClass.isAssignableFrom(entry.key)) {
20 | creator = entry.value
21 | break
22 | }
23 | }
24 | }
25 |
26 | if (creator == null) throw IllegalArgumentException("Unknown model class$modelClass")
27 |
28 | try {
29 | return creator.get() as T
30 | } catch (e: Exception) {
31 | throw RuntimeException(e)
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
11 |
12 |
17 |
18 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/base/core_data/src/main/java/com/raqun/movies/core/data/db/Db.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.core.data.db
2 |
3 | class Db private constructor() {
4 |
5 | object Config {
6 | const val DB_NAME = "movies"
7 | const val DB_VERSION = 1
8 | const val ID = "id"
9 | const val CREATE_DATE = "create_date"
10 | const val UPDATE_DATE = "update_date"
11 | }
12 |
13 | object TABLES {
14 | object TVSHOWS {
15 | const val NAME = "tv_shows"
16 |
17 | object COLUMNS {
18 | const val ID = Config.ID
19 | const val SHOW_NAME = "name"
20 | const val VOTE_AVARAGE = "vote_avarate"
21 | const val POSTER_PATH = "poster_path"
22 | const val POPULARITY = "popularity"
23 | const val CREATE_DATE = Config.CREATE_DATE
24 | const val UPDATE_DATE = Config.UPDATE_DATE
25 | }
26 | }
27 |
28 | object TVSHOW_DETAILS {
29 | const val NAME = "tv_show_details"
30 |
31 | object COLUMNS {
32 | const val ID = Config.ID
33 | const val SHOW_NAME = "name"
34 | const val VOTE_AVARAGE = "vote_avarate"
35 | const val POSTER_PATH = "poster_path"
36 | const val OVERVIEW = "overview"
37 | const val VOTE_COOUNT = "vote_count"
38 | const val CREATE_DATE = Config.CREATE_DATE
39 | const val UPDATE_DATE = Config.UPDATE_DATE
40 | }
41 | }
42 |
43 | }
44 | }
--------------------------------------------------------------------------------
/shows/shows_data/src/main/java/com/raqun/movies/shows/data/TvShowDetailsLocalDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.shows.data
2 |
3 | import com.raqun.movies.core.data.db.MoviesDb
4 | import com.raqun.movies.core.data.db.entity.TvShowDetailEntity
5 | import com.raqun.movies.core.data.source.DataSource
6 | import com.raqun.movies.shows.domain.TvShowDetail
7 | import io.reactivex.Flowable
8 | import io.reactivex.functions.Function
9 | import javax.inject.Inject
10 |
11 | class TvShowDetailsLocalDataSource @Inject constructor(
12 | private val db: MoviesDb,
13 | private val tvShowDetailMapper: Function,
14 | private val tvShowDetailEntityMapper: Function
15 | ) : DataSource.FlowableLocal {
16 |
17 | override fun get(key: Int?): Flowable {
18 | return if (key == null) {
19 | Flowable.empty()
20 | } else {
21 | db.tvShowDetailsDao().getTvShowDetail(key.toLong()).map { t: TvShowDetailEntity ->
22 | tvShowDetailMapper.apply(t)
23 | }
24 | }
25 | }
26 |
27 | override fun put(key: Int?, data: TvShowDetail): Boolean {
28 | return try {
29 | db.tvShowDetailsDao().addTvShowDetail(tvShowDetailEntityMapper.apply(data))
30 | true
31 | } catch (e: Exception) {
32 | false
33 | }
34 | }
35 |
36 | override fun remove(value: TvShowDetail): Boolean {
37 | TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
38 | }
39 |
40 | override fun clear() {
41 | TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
42 | }
43 |
44 | }
--------------------------------------------------------------------------------
/shows/shows_data/src/main/java/com/raqun/movies/shows/data/TvShowsLocalDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.shows.data
2 |
3 | import com.raqun.movies.core.data.db.MoviesDb
4 | import com.raqun.movies.core.data.db.entity.TvShowEntity
5 | import com.raqun.movies.core.data.source.DataSource
6 | import com.raqun.movies.shows.domain.TvShow
7 | import io.reactivex.Flowable
8 | import io.reactivex.Observable
9 | import io.reactivex.functions.Function
10 | import javax.inject.Inject
11 |
12 | class TvShowsLocalDataSource @Inject constructor(
13 | private val db: MoviesDb,
14 | private val tvShowMapper: Function,
15 | private val tvShowEntityMapper: Function
16 | ) : DataSource.FlowableLocal> {
17 |
18 | override fun get(key: Int?): Flowable> =
19 | db.tvShowsDao().getTvShows().map {
20 | it.map { tvShowEntity ->
21 | tvShowMapper.apply(tvShowEntity)
22 | }
23 | }
24 |
25 | override fun put(key: Int?, data: List): Boolean {
26 | return try {
27 | val shows = Observable.fromIterable(data)
28 | .map { tvShow ->
29 | tvShowEntityMapper.apply(tvShow)
30 | }
31 | .toList()
32 | .blockingGet()
33 | db.tvShowsDao().addTvShows(shows)
34 | true
35 | } catch (e: Exception) {
36 | false
37 | }
38 | }
39 |
40 | override fun remove(value: List): Boolean {
41 | TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
42 | }
43 |
44 | override fun clear() {
45 | TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
46 | }
47 |
48 | }
--------------------------------------------------------------------------------
/shows/shows_presentation/src/main/java/com/raqun/movie/shows/presentation/PopularTvShowsPresentationModule.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movie.shows.presentation
2 |
3 | import com.raqun.movie.shows.presentation.PopularTvShowsPresentationConstants.TYPES.SHOW
4 | import com.raqun.movies.core.presentation.recyclerview.*
5 | import com.raqun.movies.shows.domain.TvShow
6 | import dagger.Binds
7 | import dagger.Module
8 | import dagger.Provides
9 | import dagger.multibindings.IntKey
10 | import dagger.multibindings.IntoMap
11 | import io.reactivex.functions.Function
12 |
13 | @Module
14 | abstract class PopularTvShowsPresentationModule {
15 |
16 | @Binds
17 | @IntoMap
18 | @IntKey(SHOW)
19 | internal abstract fun bindPopularTvShowsViewHolderFactory(viewHolderFactory: PopularTvShowsViewHolder.PopularTvShowsViewHolderFactory): ViewHolderFactory
20 |
21 | @Binds
22 | @IntoMap
23 | @IntKey(SHOW)
24 | internal abstract fun bindPopularTvShowsViewHolderBinder(viewHolderBinder: PopularTvShowsViewHolder.PopularTvShowsViewHolderBinder): ViewHolderBinder
25 |
26 | @Module
27 | companion object {
28 |
29 | @JvmStatic
30 | @Provides
31 | fun provideDisplayItemComperator(): DisplayItemComperator = DefaultDisplayItemComperator()
32 |
33 | @JvmStatic
34 | @Provides
35 | fun provideRecyclerAdapter(
36 | itemComparator: DisplayItemComperator,
37 | factoryMap: Map,
38 | binderMap: Map
39 | ): RecyclerViewAdapter {
40 | return RecyclerViewAdapter(
41 | itemComperator = itemComparator,
42 | viewHolderFactoryMap = factoryMap,
43 | viewBinderFactoryMap = binderMap
44 | )
45 | }
46 | }
47 | }
--------------------------------------------------------------------------------
/shows/shows_presentation/src/main/res/layout/item_popular_tv_show.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
13 |
14 |
17 |
18 |
21 |
22 |
26 |
27 |
32 |
33 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/base/core_presentation/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 196dp
5 | 10dp
6 |
7 |
8 | 16sp
9 | 14sp
10 |
11 |
12 | 8dp
13 | 8dp
14 |
15 |
16 | 24dp
17 | 16dp
18 | 8dp
19 | 4dp
20 | 16dp
21 |
22 |
23 | 10dp
24 | 2dp
25 | 4dp
26 |
27 |
28 | 14sp
29 | 16sp
30 | 12sp
31 |
32 |
33 | 48dp
34 | 144dp
35 |
36 |
37 | 2dp
38 | 16dp
39 | 172dp
40 | 144dp
41 | 104dp
42 | 156dp
43 |
44 | 72dp
45 |
46 |
--------------------------------------------------------------------------------
/common.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: Plugins.kotlinAndroid
2 | apply plugin: Plugins.kotlinAndroidExtensions
3 | apply plugin: Plugins.kotlinKapt
4 |
5 | android {
6 | compileSdkVersion Config.compileSdkVersion
7 |
8 | defaultConfig {
9 | minSdkVersion Config.minSdkVersion
10 | targetSdkVersion Config.targetSdkVersion
11 | versionCode Config.versionCode
12 | versionName Config.versionName
13 | vectorDrawables.useSupportLibrary = true
14 | }
15 |
16 | flavorDimensions Dimensions.default
17 | productFlavors {
18 | prod {
19 | versionCode Prod.versionCode
20 | versionName Prod.versionName
21 | }
22 |
23 | dev {
24 | versionCode Dev.versionCode
25 | versionName Dev.versionName
26 | }
27 | }
28 |
29 | buildTypes {
30 | release {
31 | minifyEnabled false
32 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
33 | }
34 |
35 | debug {
36 | debuggable true
37 | }
38 | }
39 | }
40 |
41 | dependencies {
42 | implementation CoreLibraries.kotlin
43 | kapt Libraries.dagger2Compiler
44 | kapt Libraries.dagger2AndroidProcessor
45 | testAnnotationProcessor Libraries.dagger2Compiler
46 | compileOnly Libraries.javaxAnnotation
47 | kapt Libraries.roomCompiler
48 |
49 | // Testing
50 | testImplementation TestLibraries.jUnit
51 | androidTestImplementation TestLibraries.runnner
52 | androidTestImplementation TestLibraries.espressoCore
53 | testImplementation TestLibraries.mockitoCore
54 | androidTestImplementation TestLibraries.mockitoAndroid
55 | }
56 |
57 | configurations.all {
58 | resolutionStrategy.eachDependency { DependencyResolveDetails details ->
59 | def requested = details.requested
60 | if (requested.group == 'org.jetbrains.kotlin') {
61 | details.useVersion Versions.kotlinVersion
62 | }
63 | }
64 | }
--------------------------------------------------------------------------------
/shows/shows_presentation/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 |
11 |
16 |
17 |
22 |
23 |
30 |
31 |
37 |
38 |
44 |
45 |
--------------------------------------------------------------------------------
/shows/shows_data/src/main/java/com/raqun/movies/shows/data/TvShowsRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.shows.data
2 |
3 | import android.annotation.SuppressLint
4 | import com.raqun.movies.core.data.source.DataSource
5 | import com.raqun.movies.shows.domain.PagedTvShows
6 | import com.raqun.movies.shows.domain.TvShow
7 | import com.raqun.movies.shows.domain.TvShowDetail
8 | import com.raqun.movies.shows.domain.TvShowsRepository
9 | import io.reactivex.Flowable
10 | import io.reactivex.rxkotlin.subscribeBy
11 | import io.reactivex.schedulers.Schedulers
12 | import javax.inject.Inject
13 |
14 | class TvShowsRepositoryImpl @Inject constructor(
15 | private val showsRemoteDataSource: DataSource.RetrieveRemoteDataSource,
16 | private val showDetailsRemoteDataSource: DataSource.RetrieveRemoteDataSource,
17 | private val showsLocalDataSource: DataSource.FlowableLocal>,
18 | private val showDetailsLocalDataSource: DataSource.FlowableLocal
19 | ) : TvShowsRepository {
20 |
21 | @SuppressLint("CheckResult")
22 | override fun getPopularTShows(page: Int): Flowable> {
23 | showsRemoteDataSource.getResult(page)
24 | .subscribeOn(Schedulers.io())
25 | .observeOn(Schedulers.io())
26 | .subscribeBy(onSuccess = {
27 | showsLocalDataSource.put(null, it.results)
28 | }, onError = {
29 | // ignored
30 | })
31 | return showsLocalDataSource.get(null)
32 | }
33 |
34 | @SuppressLint("CheckResult")
35 | override fun getShowDetail(id: Int): Flowable {
36 | showDetailsRemoteDataSource.getResult(id)
37 | .subscribeOn(Schedulers.io())
38 | .observeOn(Schedulers.io())
39 | .subscribeBy(onSuccess = {
40 | showDetailsLocalDataSource.put(it.id, it)
41 | }, onError = {
42 | // ignored
43 | })
44 |
45 | return showDetailsLocalDataSource.get(id)
46 | }
47 |
48 | }
--------------------------------------------------------------------------------
/shows/shows_details_presentation/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Lorem ipsum dolor sit amet, an sit fugit officiis deserunt, audiam disputando at sit. Eu mel expetenda voluptatibus. Ius no accusam scripserit.\n\n Sea error iracundia ne, duo ne dicat partem sententiae. Nibh admodum facilisi ius in. Ius legere intellegam ei, in zril deserunt qui.At alii audire scripta mea, errem disputando quo in.\n\n Elit vocent legimus cum ei. Eu nonumes eligendi pericula sit. Ceteros omnesque facilisis id vix, at dico choro perpetua est, et dolore oblique vituperata qui. \n\nLorem ipsum dolor sit amet, an sit fugit officiis deserunt, audiam disputando at sit. Eu mel expetenda voluptatibus. Ius no accusam scripserit.\n\n Sea error iracundia ne, duo ne dicat partem sententiae. Nibh admodum facilisi ius in. Ius legere intellegam ei, in zril deserunt qui.At alii audire scripta mea, errem disputando quo in.\n\n Elit vocent legimus cum ei. Eu nonumes eligendi pericula sit. Ceteros omnesque facilisis id vix, at dico choro perpetua est, et dolore oblique vituperata qui.\n\nLorem ipsum dolor sit amet, an sit fugit officiis deserunt, audiam disputando at sit. Eu mel expetenda voluptatibus. Ius no accusam scripserit.\n\n Sea error iracundia ne, duo ne dicat partem sententiae. Nibh admodum facilisi ius in. Ius legere intellegam ei, in zril deserunt qui.At alii audire scripta mea, errem disputando quo in.\n\n Elit vocent legimus cum ei. Eu nonumes eligendi pericula sit. Ceteros omnesque facilisis id vix, at dico choro perpetua est, et dolore oblique vituperata qui. \n\nLorem ipsum dolor sit amet, an sit fugit officiis deserunt, audiam disputando at sit. Eu mel expetenda voluptatibus. Ius no accusam scripserit.\n\n Sea error iracundia ne, duo ne dicat partem sententiae. Nibh admodum facilisi ius in. Ius legere intellegam ei, in zril deserunt qui.At alii audire scripta mea, errem disputando quo in.\n\n Elit vocent legimus cum ei. Eu nonumes eligendi pericula sit. Ceteros omnesque facilisis id vix, at dico choro perpetua est, et dolore oblique vituperata qui.
3 |
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # movies
2 | An example approach for Android Application modularization and Reactive Clean architecture.
3 |
4 | ## Modularization:
5 | This repository is created to demonstrate how to implement __modular android application__ and __reactive clean architecture.__
6 | In __Base__ directory, there are four modules:
7 |
8 | ```
9 | - core
10 | - core_presentation
11 | - core_domain
12 | - core_data
13 | ```
14 |
15 | __Core module__ contains classes which can be used in every layer such as injection annotations, injection scopes, error factories,
16 | data holder models. _Core presentation,_ includes core module and classes which can be used in other features presentation modules
17 | such as base ui classes, generic RecyclerView Adapter, ViewModel factories etc. __Core data__ inclues core module and domain spesific
18 | interfaces such as Interactors. Core Data also includes core module, data source interfaces, default request interceptors and
19 | api module.
20 |
21 | All features is implemented as 3 modules which are seperated by their scope.
22 | ```
23 | - feature_presentation
24 | - feature_domain
25 | - feature_data
26 | ```
27 |
28 | __Presentation layer,__ contains, ui classes, injection modules for ui and view entities. Presentation layers includes core_presentation
29 | module. __Domain layer__ containes feature spesific domain objects, interactor implementations and
30 | repository interace to provide a contract between data and domain layer of feature. __Data layer__ contains core_data module and
31 | other data related classes such as repository implementaions, remote local data sources etc..
32 |
33 | ## Tech Stack:
34 | ```
35 | - Kotlin
36 | - MVVM
37 | - Clean Architecture
38 | - Repository Pattern
39 | - RxJava
40 | - Dagger2
41 | - Retrofit
42 | - Room
43 | - Architecture Components
44 | - Lifecycle Aware Components
45 | - Modularization
46 | - Unit Testing
47 | - Mockito
48 | - Kotlin DSL
49 | - SSOT with RxJava and Room
50 | ```
51 |
52 | ## Screenshots:
53 |
55 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/shows/shows_details_presentation/src/main/java/com/raqun/movies/shows/details/presentation/TvShowDetailsViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.shows.details.presentation
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.MediatorLiveData
5 | import com.raqun.movies.core.domain.Interactor
6 | import com.raqun.movies.core.error.ErrorFactory
7 | import com.raqun.movies.core.model.DataHolder
8 | import com.raqun.movies.core.presentation.viewmodel.ReactiveViewModel
9 | import com.raqun.movies.shows.domain.GetTvShowDetailInteractor
10 | import com.raqun.movies.shows.domain.TvShowDetail
11 | import io.reactivex.functions.Function
12 | import io.reactivex.schedulers.Schedulers
13 | import javax.inject.Inject
14 |
15 | class TvShowDetailsViewModel @Inject constructor(
16 | private val getTvShowDetailsViewInteractor: Interactor.ReactiveRetrieveInteractor,
17 | private val tvShowDetailsViewentityMapper: Function,
18 | private val errorFactory: ErrorFactory
19 | ) : ReactiveViewModel() {
20 |
21 | private val _tvShowDetails = MediatorLiveData>()
22 | val tvShowDetails: LiveData>
23 | get() = _tvShowDetails
24 |
25 | fun getTvShowDetails(id: Int) {
26 | _tvShowDetails.value = DataHolder.Loading()
27 | val getTvShowDetailsParams = GetTvShowDetailInteractor.Params(id)
28 | val getTvShowsDisposable = getTvShowDetailsViewInteractor.execute(getTvShowDetailsParams)
29 | .observeOn(Schedulers.computation())
30 | .subscribeOn(Schedulers.io())
31 | .subscribe({
32 | _tvShowDetails.postValue(DataHolder.Success(tvShowDetailsViewentityMapper.apply(it)))
33 | }, {
34 | _tvShowDetails.postValue(
35 | DataHolder.Fail(
36 | errorFactory.createErrorFromThrowable(
37 | it
38 | )
39 | )
40 | )
41 | })
42 | action(getTvShowsDisposable)
43 | }
44 | }
--------------------------------------------------------------------------------
/base/core_data/src/main/java/com/raqun/movies/core/data/injection/modules/ApiModule.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.core.data.injection.modules
2 |
3 | import com.raqun.movies.core.data.BuildConfig
4 | import com.raqun.movies.core.data.api.ApiConstants
5 | import com.raqun.movies.core.data.api.DefaultRequestInterceptor
6 | import dagger.Module
7 | import dagger.Provides
8 | import okhttp3.Interceptor
9 | import okhttp3.OkHttpClient
10 | import okhttp3.logging.HttpLoggingInterceptor
11 | import retrofit2.Retrofit
12 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
13 | import retrofit2.converter.gson.GsonConverterFactory
14 | import java.util.concurrent.TimeUnit
15 | import javax.inject.Named
16 | import javax.inject.Singleton
17 |
18 | @Module
19 | class ApiModule {
20 |
21 | @Provides
22 | @Singleton
23 | @Named(NAME_URL)
24 | fun provideBaseUrl(): String = "https://api.themoviedb.org/"
25 |
26 | @Provides
27 | @Singleton
28 | fun provideRequestInterceptor(): Interceptor = DefaultRequestInterceptor()
29 |
30 | @Provides
31 | @Singleton
32 | fun provideLoggingInterceptor(): HttpLoggingInterceptor =
33 | HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY }
34 |
35 | @Provides
36 | @Singleton
37 | fun provideOkHttpClient(
38 | requestInterceptor: DefaultRequestInterceptor,
39 | loggingInterceptor: HttpLoggingInterceptor
40 | ): OkHttpClient =
41 | with(OkHttpClient.Builder()) {
42 | addInterceptor(requestInterceptor)
43 | if (BuildConfig.DEBUG) addInterceptor(loggingInterceptor)
44 | connectTimeout(ApiConstants.TIMEOUT_INMILIS, TimeUnit.MILLISECONDS)
45 | build()
46 | }
47 |
48 | @Provides
49 | @Singleton
50 | fun provideRetrofit(@Named(NAME_URL) baseUrl: String, client: OkHttpClient): Retrofit =
51 | with(Retrofit.Builder()) {
52 | baseUrl(baseUrl)
53 | client(client)
54 | addConverterFactory(GsonConverterFactory.create())
55 | addCallAdapterFactory(RxJava2CallAdapterFactory.create())
56 | build()
57 | }
58 |
59 | companion object {
60 | private const val NAME_URL = "url"
61 | }
62 | }
--------------------------------------------------------------------------------
/shows/shows_presentation/src/main/java/com/raqun/movie/shows/presentation/PopularTvShowsViewHolder.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movie.shows.presentation
2 |
3 | import android.view.LayoutInflater
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import android.widget.ImageView
7 | import android.widget.TextView
8 | import androidx.recyclerview.widget.RecyclerView
9 | import com.raqun.movies.core.presentation.extensions.loadImage
10 | import com.raqun.movies.core.presentation.recyclerview.DisplayItem
11 | import com.raqun.movies.core.presentation.recyclerview.ViewHolder
12 | import com.raqun.movies.core.presentation.recyclerview.ViewHolderBinder
13 | import com.raqun.movies.core.presentation.recyclerview.ViewHolderFactory
14 | import javax.inject.Inject
15 |
16 | class PopularTvShowsViewHolder private constructor(itemView: View) :
17 | ViewHolder(itemView) {
18 |
19 | private val textViewName: TextView = itemView.findViewById(R.id.textview_name)
20 | private val textViewRating: TextView = itemView.findViewById(R.id.textview_rating)
21 | private val imageviewPoster: ImageView = itemView.findViewById(R.id.imageview_poster)
22 |
23 | override fun bind(item: PopularTvShowViewEntity) {
24 | textViewName.text = item.name
25 | textViewRating.text = itemView.context.getString(
26 | R.string.avarage_rating, item.rating.toString())
27 | item.picture?.let {
28 | imageviewPoster.loadImage(it)
29 | }
30 | itemView.setOnClickListener {
31 | itemClickListener?.invoke(item)
32 | }
33 | }
34 |
35 | internal class PopularTvShowsViewHolderFactory @Inject constructor() : ViewHolderFactory {
36 |
37 | override fun createViewHolder(parent: ViewGroup): RecyclerView.ViewHolder =
38 | PopularTvShowsViewHolder(
39 | LayoutInflater.from(parent.context).inflate(
40 | R.layout.item_popular_tv_show,
41 | parent,
42 | false
43 | )
44 | )
45 | }
46 |
47 | internal class PopularTvShowsViewHolderBinder @Inject constructor() : ViewHolderBinder {
48 |
49 | override fun bind(holder: RecyclerView.ViewHolder, item: DisplayItem) {
50 | (holder as PopularTvShowsViewHolder).bind(item as PopularTvShowViewEntity)
51 | }
52 | }
53 | }
--------------------------------------------------------------------------------
/shows/shows_details_presentation/src/main/java/com/raqun/movies/shows/details/presentation/TvShowDetailsFragment.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.shows.details.presentation
2 |
3 | import android.os.Bundle
4 | import androidx.lifecycle.Observer
5 | import com.raqun.movies.core.model.DataHolder
6 | import com.raqun.movies.core.navigation.features.TvShowDetails
7 | import com.raqun.movies.core.presentation.Constants.Companion.IMAGE_BASE_URL
8 | import com.raqun.movies.core.presentation.base.BaseViewModelFragment
9 | import com.raqun.movies.core.presentation.extensions.loadImage
10 | import com.raqun.movies.core.presentation.navigation.UiNavigation
11 | import kotlinx.android.synthetic.main.fragment_show_details.*
12 |
13 | class TvShowDetailsFragment : BaseViewModelFragment() {
14 |
15 | private var id: Int? = null
16 |
17 | override fun getModelClass() = TvShowDetailsViewModel::class.java
18 |
19 | override fun getLayoutRes() = R.layout.fragment_show_details
20 |
21 | override val uiNavigation = UiNavigation.BACK
22 |
23 | override fun onCreate(savedInstanceState: Bundle?) {
24 | super.onCreate(savedInstanceState)
25 | id = arguments?.getInt(TvShowDetails.BUNDLE_ID, 0)
26 | }
27 |
28 | override fun onActivityCreated(savedInstanceState: Bundle?) {
29 | super.onActivityCreated(savedInstanceState)
30 | viewModel.tvShowDetails.observe(this, Observer {
31 | when (it) {
32 | is DataHolder.Success -> {
33 | toolbar.title = it.data.name
34 | textViewOverView.text = it.data.overview
35 | if (!it.data.posterPath.isNullOrEmpty()) {
36 | imageViewPoster.loadImage(IMAGE_BASE_URL + it.data.posterPath!!)
37 | }
38 | }
39 |
40 | is DataHolder.Fail -> {
41 | // handle fail
42 | }
43 |
44 | is DataHolder.Loading -> {
45 | // handle loading
46 | }
47 | }
48 | })
49 |
50 | id?.let {
51 | viewModel.getTvShowDetails(it)
52 | }
53 | }
54 |
55 | companion object {
56 | fun newInstance(id: Int?) = TvShowDetailsFragment().apply {
57 | val args = Bundle()
58 | if (id != null) {
59 | args.putInt(TvShowDetails.BUNDLE_ID, id)
60 | }
61 | arguments = args
62 | }
63 | }
64 | }
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/base/core_presentation/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
13 |
18 |
19 |
24 |
25 |
34 |
35 |
38 |
39 |
43 |
44 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/shows/shows_data/src/main/java/com/raqun/movies/shows/data/ShowsDataModule.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.shows.data
2 |
3 | import com.raqun.movies.core.data.db.MoviesDb
4 | import com.raqun.movies.core.data.db.entity.TvShowDetailEntity
5 | import com.raqun.movies.core.data.db.entity.TvShowEntity
6 | import com.raqun.movies.core.data.source.DataSource
7 | import com.raqun.movies.shows.domain.PagedTvShows
8 | import com.raqun.movies.shows.domain.TvShow
9 | import com.raqun.movies.shows.domain.TvShowDetail
10 | import com.raqun.movies.shows.domain.TvShowsRepository
11 | import dagger.Module
12 | import dagger.Provides
13 | import io.reactivex.functions.Function
14 | import retrofit2.Retrofit
15 | import javax.inject.Singleton
16 |
17 | @Module
18 | class ShowsDataModule {
19 |
20 | @Provides
21 | @Singleton
22 | fun provideShowsServices(retrofit: Retrofit): TvShowsServices =
23 | retrofit.create(TvShowsServices::class.java)
24 |
25 | @Provides
26 | fun provideGetPopularTvShowsRemoteDataSource(tvShowsServices: TvShowsServices): DataSource.RetrieveRemoteDataSource =
27 | GetPopularTvShowsRemoteDataSource(tvShowsServices)
28 |
29 | @Provides
30 | fun provideGetTvShowDetailsRemoteDataSource(tvShowsServices: TvShowsServices): DataSource.RetrieveRemoteDataSource =
31 | GetTvShowDetailRemoteDataSource(tvShowsServices)
32 |
33 | @Provides
34 | fun provideShowsLocalDataSource(
35 | db: MoviesDb, tvShowMapper: Function,
36 | tvShowEntityMapper: Function
37 | ): DataSource.FlowableLocal> =
38 | TvShowsLocalDataSource(db, tvShowMapper, tvShowEntityMapper)
39 |
40 | @Provides
41 | fun provideTvShowEntityMapper(): Function = TvShowEntityMapper()
42 |
43 | @Provides
44 | fun provideTvShowMapper(): Function = TvShowMapper()
45 |
46 | @Provides
47 | fun provideTvShowDetailEntityMapper(): Function =
48 | TvShowDetailEntityMapper()
49 |
50 | @Provides
51 | fun provideTvShowDetailMapper(): Function =
52 | TvShowDetailMapper()
53 |
54 | @Provides
55 | fun provideTvShowDetailsLocalDataSource(
56 | db: MoviesDb,
57 | tvShowDetailMapper: Function,
58 | tvShowEntityMapper: Function
59 | ): DataSource.FlowableLocal =
60 | TvShowDetailsLocalDataSource(db, tvShowDetailMapper, tvShowEntityMapper)
61 |
62 | @Provides
63 | @Singleton
64 | fun provideShowsRepository(
65 | getPopularTvShowsRemoteDataSource: DataSource.RetrieveRemoteDataSource,
66 | getTvShowDetailRemoteDatASource: DataSource.RetrieveRemoteDataSource,
67 | tvShowsLocalDataSource: DataSource.FlowableLocal>,
68 | tvShowDetailsLocalDataSource: DataSource.FlowableLocal
69 | ): TvShowsRepository =
70 | TvShowsRepositoryImpl(
71 | getPopularTvShowsRemoteDataSource,
72 | getTvShowDetailRemoteDatASource,
73 | tvShowsLocalDataSource,
74 | tvShowDetailsLocalDataSource
75 | )
76 | }
--------------------------------------------------------------------------------
/base/core_presentation/src/main/java/com/raqun/movies/core/presentation/recyclerview/RecyclerViewAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.core.presentation.recyclerview
2 |
3 | import android.annotation.SuppressLint
4 | import android.view.ViewGroup
5 | import androidx.recyclerview.widget.DiffUtil
6 | import androidx.recyclerview.widget.RecyclerView
7 | import io.reactivex.Single
8 | import io.reactivex.android.schedulers.AndroidSchedulers
9 | import io.reactivex.schedulers.Schedulers
10 |
11 | class RecyclerViewAdapter constructor(
12 | private val items: MutableList = ArrayList(),
13 | private val itemComperator: DisplayItemComperator,
14 | private val viewHolderFactoryMap: Map,
15 | private val viewBinderFactoryMap: Map
16 | ) : RecyclerView.Adapter(), DiffAdapter {
17 |
18 | var itemClickListener: ((item: DisplayItem) -> Unit)? = null
19 | var itemLongClickListener: ((item: DisplayItem) -> Boolean)? = null
20 |
21 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =
22 | viewHolderFactoryMap[viewType]?.createViewHolder(parent)!!
23 |
24 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
25 | viewBinderFactoryMap[items[position].type()]?.bind(holder, items[position])
26 | (holder as ViewHolder<*>).itemClickListener = itemClickListener
27 | holder.itemLongClickListener = itemLongClickListener
28 | }
29 |
30 | override fun getItemCount() = items.size
31 |
32 | override fun getItemViewType(position: Int) = items[position].type()
33 |
34 | override fun update(newItems: List) {
35 | if (items.isEmpty()) {
36 | updateAllItems(newItems)
37 | } else {
38 | updateOnlyChangedItems(newItems)
39 | }
40 | }
41 |
42 | override fun updateAllItems(newItems: List) {
43 | updateItems(newItems)
44 | notifyDataSetChanged()
45 | }
46 |
47 | @SuppressLint("CheckResult")
48 | override fun updateOnlyChangedItems(newItems: List) {
49 | Single.fromCallable { calculateDiffResult(newItems) }
50 | .subscribeOn(Schedulers.computation())
51 | .observeOn(AndroidSchedulers.mainThread())
52 | .doOnSuccess { updateItems(newItems) }
53 | .subscribe(this::updateWithOnlyDiffResult)
54 | }
55 |
56 | override fun updateItems(newItems: List) {
57 | items.clear()
58 | items.addAll(newItems)
59 | }
60 |
61 | override fun calculateDiff(newItems: List): DiffUtil.DiffResult =
62 | DiffUtil.calculateDiff(
63 | DiffUtilImpl(
64 | items,
65 | newItems,
66 | itemComperator
67 | )
68 | )
69 |
70 | override fun updateWithOnlyDiffResult(result: DiffUtil.DiffResult) {
71 | result.dispatchUpdatesTo(this)
72 | }
73 |
74 | override fun addItems(newItems: List) {
75 | val startRange = items.size
76 | items.addAll(newItems)
77 | notifyItemRangeChanged(startRange, newItems.size)
78 | }
79 |
80 | private fun calculateDiffResult(newItems: List): DiffUtil.DiffResult =
81 | calculateDiff(newItems)
82 | }
--------------------------------------------------------------------------------
/base/core_presentation/src/main/java/com/raqun/movies/core/presentation/base/BaseActivity.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.core.presentation.base
2 |
3 | import android.content.res.Resources
4 | import android.os.Bundle
5 | import android.view.Menu
6 | import android.view.MenuItem
7 | import androidx.annotation.*
8 | import androidx.appcompat.app.AppCompatActivity
9 | import androidx.appcompat.widget.Toolbar
10 | import com.raqun.movies.core.presentation.Constants
11 | import com.raqun.movies.core.presentation.R
12 | import com.raqun.movies.core.presentation.navigation.UiNavigation
13 | import dagger.android.support.HasSupportFragmentInjector
14 |
15 | abstract class BaseActivity : AppCompatActivity(), HasSupportFragmentInjector {
16 |
17 | @LayoutRes
18 | abstract fun getLayoutRes(): Int
19 |
20 | @StringRes
21 | open val titleRes = R.string.app_name
22 |
23 | @MenuRes
24 | open val menuRes = Constants.NO_RES
25 |
26 | open val uiNavigation = UiNavigation.BACK
27 |
28 | @IdRes
29 | open val toolbarRes = Constants.NO_RES
30 |
31 | @CallSuper
32 | override fun onCreate(savedInstanceState: Bundle?) {
33 | super.onCreate(savedInstanceState)
34 | setContentView(getLayoutRes())
35 | if (toolbarRes != Constants.NO_RES) {
36 | setToolbar(findViewById(toolbarRes))
37 | }
38 | initNavigation(uiNavigation)
39 | setScreenTitle(getString(titleRes))
40 | }
41 |
42 | override fun onCreateOptionsMenu(menu: Menu?): Boolean {
43 | if (menuRes != Constants.NO_RES) {
44 | menuInflater.inflate(menuRes, menu)
45 | return true
46 | }
47 |
48 | return super.onCreateOptionsMenu(menu)
49 | }
50 |
51 | override fun onOptionsItemSelected(item: MenuItem?): Boolean {
52 | when (item?.itemId) {
53 | android.R.id.home -> onBackPressed()
54 | }
55 |
56 | return super.onOptionsItemSelected(item)
57 | }
58 |
59 | fun setScreenTitle(@StringRes titleRes: Int) {
60 | var title: String? = null
61 | try {
62 | title = getString(titleRes)
63 | } catch (e: Resources.NotFoundException) {
64 | // ignored
65 | }
66 | setScreenTitle(title)
67 | }
68 |
69 | fun setScreenTitle(title: String?) {
70 | supportActionBar?.title = title
71 | }
72 |
73 | fun setToolbar(toolbar: Toolbar?) {
74 | setSupportActionBar(toolbar)
75 | }
76 |
77 | fun setNavigation(uiNavigation: UiNavigation) {
78 | initNavigation(uiNavigation)
79 | }
80 |
81 | fun initNavigation(uiNavigation: UiNavigation) {
82 | when (uiNavigation) {
83 | UiNavigation.BACK -> {
84 | supportActionBar?.apply {
85 | setDisplayShowHomeEnabled(true)
86 | setHomeButtonEnabled(true)
87 | setDisplayHomeAsUpEnabled(true)
88 | }
89 |
90 | }
91 | UiNavigation.ROOT -> {
92 | supportActionBar?.apply {
93 | setDisplayShowHomeEnabled(false)
94 | setHomeButtonEnabled(false)
95 | setDisplayHomeAsUpEnabled(false)
96 | }
97 | }
98 | UiNavigation.NONE -> {
99 | // no-op
100 | }
101 | }
102 | }
103 | }
--------------------------------------------------------------------------------
/shows/shows_presentation/src/main/java/com/raqun/movie/shows/presentation/PopularTvShowsFragment.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movie.shows.presentation
2 |
3 | import android.os.Bundle
4 | import android.util.Log
5 | import androidx.lifecycle.Observer
6 | import androidx.recyclerview.widget.LinearLayoutManager
7 | import androidx.recyclerview.widget.RecyclerView
8 | import com.raqun.movies.core.model.DataHolder
9 | import com.raqun.movies.core.navigation.features.TvShowDetails
10 | import com.raqun.movies.core.presentation.base.BaseViewModelFragment
11 | import com.raqun.movies.core.presentation.extensions.setup
12 | import com.raqun.movies.core.presentation.recyclerview.RecyclerViewAdapter
13 | import kotlinx.android.synthetic.main.fragment_popular_tv_shows.*
14 | import javax.inject.Inject
15 |
16 |
17 | class PopularTvShowsFragment : BaseViewModelFragment() {
18 |
19 | @Inject
20 | protected lateinit var popularTvShowsAdapter: RecyclerViewAdapter
21 |
22 | override fun getLayoutRes() = R.layout.fragment_popular_tv_shows
23 |
24 | override fun getModelClass() = PopularTvShowsViewModel::class.java
25 |
26 | private val recyclerViewOnScrollListener = object : RecyclerView.OnScrollListener() {
27 |
28 | override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
29 | super.onScrolled(recyclerView, dx, dy)
30 | val visibleItemCount = recyclerViewPopularTvShows.childCount
31 | val totalItemCount = recyclerViewPopularTvShows.layoutManager?.itemCount ?: 0
32 | val firstVisibleItemPosition =
33 | (recyclerViewPopularTvShows.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
34 |
35 | if (viewModel.popularTvShowsLiveData.value !is DataHolder.Loading) {
36 | if (visibleItemCount + firstVisibleItemPosition >= totalItemCount - 5
37 | && firstVisibleItemPosition >= 0
38 | ) {
39 | viewModel.getPopularTvShowsByPagination()
40 | }
41 | }
42 | }
43 | }
44 |
45 | override fun initView() {
46 | recyclerViewPopularTvShows.apply {
47 | setup(context = activity!!, adapter = popularTvShowsAdapter)
48 | addOnScrollListener(recyclerViewOnScrollListener)
49 | }
50 | swipteToRefreshLayoutShows.setOnRefreshListener {
51 | viewModel.refreshPopularTvShows()
52 | }
53 | popularTvShowsAdapter.itemClickListener = {
54 | val showId = (it as? PopularTvShowViewEntity)?.id
55 | showId?.let {
56 | val intent = TvShowDetails.dynamicStart(showId)
57 | intent?.let {
58 | startActivity(intent)
59 | }
60 | }
61 | }
62 | }
63 |
64 | override fun onActivityCreated(savedInstanceState: Bundle?) {
65 | super.onActivityCreated(savedInstanceState)
66 | viewModel.popularTvShowsLiveData.observe(viewLifecycleOwner, Observer {
67 | swipteToRefreshLayoutShows.isRefreshing = it is DataHolder.Loading
68 | when (it) {
69 | is DataHolder.Success -> popularTvShowsAdapter.addItems(it.data)
70 | is DataHolder.Fail -> onError(it.e)
71 | is DataHolder.Loading -> Log.e("loading", "tv shows")
72 | }
73 | })
74 |
75 | viewModel.getPopularTvShowsByPagination()
76 | }
77 |
78 | companion object {
79 | fun newInstance() = PopularTvShowsFragment()
80 | }
81 | }
--------------------------------------------------------------------------------
/shows/shows_presentation/src/main/java/com/raqun/movie/shows/presentation/PopularTvShowsViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movie.shows.presentation
2 |
3 | import android.annotation.SuppressLint
4 | import androidx.lifecycle.LiveData
5 | import androidx.lifecycle.MediatorLiveData
6 | import androidx.lifecycle.MutableLiveData
7 | import com.raqun.movies.core.domain.Interactor
8 | import com.raqun.movies.core.error.ErrorFactory
9 | import com.raqun.movies.core.model.DataHolder
10 | import com.raqun.movies.core.presentation.recyclerview.DisplayItem
11 | import com.raqun.movies.core.presentation.viewmodel.ReactiveViewModel
12 | import com.raqun.movies.shows.domain.GetPopularTvShowsInteractor
13 | import com.raqun.movies.shows.domain.TvShow
14 | import io.reactivex.Observable
15 | import io.reactivex.functions.Function
16 | import io.reactivex.schedulers.Schedulers
17 | import java.util.*
18 | import javax.inject.Inject
19 |
20 | class PopularTvShowsViewModel @Inject constructor(
21 | private val getPopularTvShowsInteractor: Interactor.FlowableRetrieveInteractor>,
22 | private val itemMapper: Function,
23 | private val errorFactory: ErrorFactory
24 | ) : ReactiveViewModel() {
25 |
26 | private val _popularTvShowsLiveData = MediatorLiveData>>()
27 | private val _pageLiveData = MutableLiveData()
28 | private val popularTvShows = ArrayList()
29 | private val page = Page()
30 |
31 | val popularTvShowsLiveData: LiveData>>
32 | get() = _popularTvShowsLiveData
33 |
34 | init {
35 | _popularTvShowsLiveData.value = DataHolder.Success(popularTvShows)
36 | _popularTvShowsLiveData.addSource(_pageLiveData) {
37 | fetchPopularTvShows(it)
38 | }
39 | }
40 |
41 | fun getPopularTvShowsByPagination() {
42 | if (_pageLiveData.value == null) {
43 | _pageLiveData.value = page.currentPage
44 | } else {
45 | val nextPage = page.currentPage + 1
46 | if (nextPage > page.totalPages) {
47 | return
48 | }
49 | _pageLiveData.value = nextPage
50 | }
51 | }
52 |
53 | fun refreshPopularTvShows() {
54 | page.currentPage = 0
55 | getPopularTvShowsByPagination()
56 | }
57 |
58 | @SuppressLint("CheckResult")
59 | private fun fetchPopularTvShows(currentPage: Int) {
60 | _popularTvShowsLiveData.value = DataHolder.Loading()
61 | val pagedParams = GetPopularTvShowsInteractor.Params(currentPage, page.totalPages)
62 | val popularTvShowsFetchDisposible = getPopularTvShowsInteractor.execute(pagedParams)
63 | .observeOn(Schedulers.computation())
64 | .subscribeOn(Schedulers.io())
65 | .subscribe({
66 |
67 | Observable.fromIterable(it)
68 | .map { item -> itemMapper.apply(item) }
69 | .toList()
70 | .blockingGet()
71 | .run {
72 | _popularTvShowsLiveData.postValue(DataHolder.Success(this))
73 | popularTvShows.addAll(this)
74 | }
75 | }, {
76 | _popularTvShowsLiveData.postValue(
77 | DataHolder.Fail(
78 | errorFactory.createErrorFromThrowable(
79 | it
80 | )
81 | )
82 | )
83 | })
84 | action(popularTvShowsFetchDisposible!!)
85 | }
86 |
87 | data class Page(var currentPage: Int = 1, var totalPages: Int = 1)
88 | }
--------------------------------------------------------------------------------
/shows/shows_domain/src/test/java/com/raqun/movies/shows/domain/GetPopularTvShowsInteractorTest.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.shows.domain
2 |
3 | import com.raqun.movies.shows.domain.util.DataProvider
4 | import io.reactivex.Single
5 | import org.junit.Test
6 | import org.mockito.ArgumentMatchers.anyInt
7 | import org.mockito.Mockito
8 | import java.io.IOException
9 |
10 | class GetPopularTvShowsInteractorTest {
11 |
12 | private var tvShowsRepository = Mockito.mock(TvShowsRepository::class.java)
13 |
14 | private val popularTvShowsInteractor by lazy {
15 | GetPopularTvShowsInteractor(tvShowsRepository)
16 | }
17 |
18 | /*
19 | * Tests successful response
20 | */
21 | @Test
22 | fun testPopularTvShowsInteractor_getPopularTvShows_Success() {
23 | val currentPage = 1
24 | val totalPage = 2
25 | val pageResultCount = 10
26 | val totalResults = totalPage * pageResultCount
27 | val params = GetPopularTvShowsInteractor.PopularTvShowsParams(currentPage, totalPage)
28 |
29 | val successResponse = Single.just(
30 | PagedTvShows(
31 | currentPage,
32 | totalResults,
33 | totalPage,
34 | DataProvider.createDummyTvShows(pageResultCount)
35 | )
36 | )
37 |
38 | Mockito.`when`(tvShowsRepository.getPopularTShows(anyInt()))
39 | .thenReturn(successResponse)
40 |
41 | popularTvShowsInteractor.execute(params)
42 | .test()
43 | .assertSubscribed()
44 | .assertComplete()
45 | .assertNoErrors()
46 | .dispose()
47 | }
48 |
49 | /*
50 | * Test failed response
51 | */
52 | @Test
53 | fun testPopularTvShowsInteractor_getPopularTvShows_Fail() {
54 | val errorResponse = IOException("Failed to connect to network!")
55 | val currentPage = 1
56 | val totalPage = 2
57 | val params = GetPopularTvShowsInteractor.PopularTvShowsParams(currentPage, totalPage)
58 |
59 | Mockito.`when`(tvShowsRepository.getPopularTShows(anyInt()))
60 | .thenReturn(Single.error(errorResponse))
61 |
62 | popularTvShowsInteractor.execute(params)
63 | .test()
64 | .assertSubscribed()
65 | .assertError(errorResponse)
66 | .dispose()
67 | }
68 |
69 | /*
70 | * Test if page number is unexpected or not
71 | */
72 | @Test
73 | fun testPopularTvShowsInteractor_getPopularTvShows_Fail_UnexpectedCurrentPageNumber() {
74 | val currentPage = 0
75 | val totalPage = 2
76 | val params = GetPopularTvShowsInteractor.PopularTvShowsParams(currentPage, totalPage)
77 |
78 | var result: java.lang.IllegalArgumentException? = null
79 | try {
80 | popularTvShowsInteractor.execute(params)
81 | .test()
82 | } catch (e: java.lang.IllegalArgumentException) {
83 | result = e
84 | }
85 |
86 | assert(result != null)
87 | }
88 |
89 | /*
90 | * Test if trying to reach above total page number
91 | */
92 | @Test
93 | fun testPopularTvShowsInteractor_getPopularTvShows_Fail_UnexpectedCurrentPageRange() {
94 | val currentPage = 3
95 | val totalPage = 2
96 | val params = GetPopularTvShowsInteractor.PopularTvShowsParams(currentPage, totalPage)
97 |
98 | var result: java.lang.IllegalStateException? = null
99 | try {
100 | popularTvShowsInteractor.execute(params)
101 | .test()
102 | } catch (e: java.lang.IllegalStateException) {
103 | result = e
104 | }
105 |
106 | assert(result != null)
107 | }
108 | }
--------------------------------------------------------------------------------
/base/core_presentation/src/main/java/com/raqun/movies/core/presentation/base/BaseFragment.kt:
--------------------------------------------------------------------------------
1 | package com.raqun.movies.core.presentation.base
2 |
3 | import android.content.Context
4 | import android.os.Bundle
5 | import android.view.*
6 | import android.widget.Toast
7 | import androidx.annotation.IdRes
8 | import androidx.annotation.LayoutRes
9 | import androidx.annotation.MenuRes
10 | import androidx.annotation.StringRes
11 | import androidx.appcompat.widget.Toolbar
12 | import androidx.fragment.app.Fragment
13 | import com.raqun.movies.core.error.Error
14 | import com.raqun.movies.core.presentation.Constants
15 | import com.raqun.movies.core.presentation.R
16 | import com.raqun.movies.core.presentation.navigation.UiNavigation
17 | import dagger.android.support.AndroidSupportInjection
18 | import dagger.android.support.HasSupportFragmentInjector
19 |
20 | abstract class BaseFragment : Fragment(), BaseView {
21 |
22 | @LayoutRes
23 | protected abstract fun getLayoutRes(): Int
24 |
25 | @MenuRes
26 | open val menuRes = Constants.NO_RES
27 |
28 | @IdRes
29 | open val toolbarRes = Constants.NO_RES
30 |
31 | @StringRes
32 | open val titleRes = Constants.NO_RES
33 |
34 | open val uiNavigation = UiNavigation.NONE
35 |
36 | override fun onAttach(context: Context?) {
37 | if (activity is HasSupportFragmentInjector) {
38 | AndroidSupportInjection.inject(this)
39 | onInjected()
40 | }
41 | super.onAttach(context)
42 | }
43 |
44 | override fun onCreate(savedInstanceState: Bundle?) {
45 | super.onCreate(savedInstanceState)
46 | setHasOptionsMenu(menuRes != Constants.NO_RES)
47 | }
48 |
49 | override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {
50 | super.onCreateOptionsMenu(menu, inflater)
51 | if (menuRes != Constants.NO_RES) {
52 | inflater?.inflate(menuRes, menu)
53 | }
54 | }
55 |
56 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
57 | return inflater.inflate(getLayoutRes(), null, false)
58 | }
59 |
60 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
61 | super.onViewCreated(view, savedInstanceState)
62 | if (toolbarRes != Constants.NO_RES) {
63 | val toolbar: Toolbar = view.findViewById(toolbarRes)
64 | (activity as? BaseActivity)?.setToolbar(toolbar)
65 | }
66 | if (uiNavigation != UiNavigation.NONE) {
67 | (activity as? BaseActivity)?.setNavigation(uiNavigation)
68 | }
69 | initView()
70 | }
71 |
72 | override fun onActivityCreated(savedInstanceState: Bundle?) {
73 | super.onActivityCreated(savedInstanceState)
74 | if (titleRes != Constants.NO_RES) {
75 | setActivityTitle(getString(titleRes))
76 | }
77 | }
78 |
79 | override fun onError(e: Error) {
80 | val message = when (e) {
81 | is Error.UnknownError -> getString(R.string.error_message_unknowon_error)
82 | is Error.BusinessError -> e.message
83 | is Error.ApiError -> e.message
84 | }
85 | Toast.makeText(context, message, Toast.LENGTH_LONG).show()
86 | }
87 |
88 | protected fun setActivityTitle(title: String) {
89 | if (activity is BaseActivity) {
90 | (activity as BaseActivity).setScreenTitle(title)
91 | }
92 | }
93 |
94 | fun getApplication() = activity?.application
95 |
96 | fun getApplicationContext() = getApplication()?.applicationContext
97 |
98 | open fun initView() {
99 | // can be overridden
100 | }
101 |
102 | open fun onInjected() {
103 | // can be overridden
104 | }
105 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
10 |
12 |
14 |
16 |
18 |
20 |
22 |
24 |
26 |
28 |
30 |
32 |
34 |
36 |
38 |
40 |
42 |
44 |
46 |
48 |
50 |
52 |
54 |
56 |
58 |
60 |
62 |
64 |
66 |
68 |
70 |
72 |
74 |
75 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/shows/shows_details_presentation/src/main/res/layout/fragment_show_details.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
19 |
20 |
26 |
27 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
47 |
48 |
52 |
53 |
65 |
66 |
70 |
71 |
75 |
76 |
86 |
87 |
88 |
89 |
90 |
94 |
95 |
96 |
97 |
109 |
110 |
114 |
115 |
119 |
120 |
130 |
131 |
137 |
138 |
139 |
140 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
160 |
161 |
169 |
170 |
175 |
176 |
181 |
182 |
193 |
194 |
195 |
196 |
201 |
202 |
207 |
208 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
--------------------------------------------------------------------------------