├── data ├── consumer-rules.pro ├── src │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── farhan │ │ │ └── tanvir │ │ │ └── data │ │ │ ├── repository │ │ │ ├── dataSource │ │ │ │ ├── MovieLocalDataSource.kt │ │ │ │ └── MovieRemoteDataSource.kt │ │ │ ├── dataSourceImpl │ │ │ │ ├── MovieLocalDataSourceImpl.kt │ │ │ │ └── MovieRemoteDataSourceImpl.kt │ │ │ └── MovieRepositoryImpl.kt │ │ │ ├── api │ │ │ └── MovieApi.kt │ │ │ ├── db │ │ │ ├── MovieDB.kt │ │ │ ├── MovieRemoteKeysDao.kt │ │ │ └── MovieDao.kt │ │ │ └── paging │ │ │ └── MovieRemoteMediator.kt │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── farhan │ │ │ └── tanvir │ │ │ └── data │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── com │ │ └── farhan │ │ └── tanvir │ │ └── data │ │ └── ExampleInstrumentedTest.kt ├── .gitignore ├── proguard-rules.pro └── build.gradle ├── domain ├── consumer-rules.pro ├── src │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── farhan │ │ │ └── tanvir │ │ │ └── domain │ │ │ ├── useCase │ │ │ ├── MovieUseCases.kt │ │ │ ├── GetPopularMoviesUseCase.kt │ │ │ └── GetMoviesFromDBUseCase.kt │ │ │ ├── model │ │ │ ├── MovieList.kt │ │ │ ├── MovieRemoteKeys.kt │ │ │ └── Movie.kt │ │ │ └── repository │ │ │ └── MovieRepository.kt │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── farhan │ │ │ └── tanvir │ │ │ └── domain │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── com │ │ └── farhan │ │ └── tanvir │ │ └── domain │ │ └── ExampleInstrumentedTest.kt ├── .gitignore ├── proguard-rules.pro └── build.gradle ├── .idea ├── .gitignore ├── compiler.xml ├── vcs.xml ├── misc.xml └── gradle.xml ├── app ├── src │ ├── main │ │ ├── res │ │ │ ├── font │ │ │ │ ├── roboto.ttf │ │ │ │ └── roboto_bold.ttf │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_round.png │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_round.png │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_round.png │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_round.png │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_round.png │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── values │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ ├── strings.xml │ │ │ │ ├── themes.xml │ │ │ │ └── colors.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── drawable │ │ │ │ ├── ic_baseline_star_rate.xml │ │ │ │ └── ic_baseline_date_range_24.xml │ │ │ └── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ ├── ic_launcher-playstore.png │ │ ├── java │ │ │ └── com │ │ │ │ └── farhan │ │ │ │ └── tanvir │ │ │ │ └── androidcleanarchitecture │ │ │ │ ├── util │ │ │ │ └── Constant.kt │ │ │ │ ├── AndroidCleanArchitecture.kt │ │ │ │ ├── presentation │ │ │ │ ├── navigation │ │ │ │ │ ├── Screen.kt │ │ │ │ │ └── NavGraph.kt │ │ │ │ ├── screen │ │ │ │ │ ├── home │ │ │ │ │ │ ├── HomeViewModel.kt │ │ │ │ │ │ ├── HomeScreen.kt │ │ │ │ │ │ ├── HomeTopBar.kt │ │ │ │ │ │ └── MovieList.kt │ │ │ │ │ └── details │ │ │ │ │ │ ├── MovieDetailsViewModel.kt │ │ │ │ │ │ ├── MovieDetailsScreen.kt │ │ │ │ │ │ ├── MovieDetailsTopBar.kt │ │ │ │ │ │ └── MovieDetailsContent.kt │ │ │ │ └── components │ │ │ │ │ ├── RatingComponent.kt │ │ │ │ │ └── ReleaseDateComponent.kt │ │ │ │ ├── ui │ │ │ │ └── theme │ │ │ │ │ ├── Shape.kt │ │ │ │ │ ├── Color.kt │ │ │ │ │ ├── Type.kt │ │ │ │ │ └── Theme.kt │ │ │ │ ├── di │ │ │ │ ├── LocalDataModule.kt │ │ │ │ ├── RemoteDataModule.kt │ │ │ │ ├── UseCaseModule.kt │ │ │ │ ├── RepositoryModule.kt │ │ │ │ ├── DatabaseModule.kt │ │ │ │ └── NetworkModule.kt │ │ │ │ └── MainActivity.kt │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── farhan │ │ │ └── tanvir │ │ │ └── androidcleanarchitecture │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── com │ │ └── farhan │ │ └── tanvir │ │ └── androidcleanarchitecture │ │ └── ExampleInstrumentedTest.kt ├── debug │ └── output-metadata.json ├── .gitignore ├── proguard-rules.pro └── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── settings.gradle ├── gradle.properties ├── README.md ├── gradlew.bat └── gradlew /data/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /app/src/main/res/font/roboto.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Farhandroid/AndroidCleanArchitecture/HEAD/app/src/main/res/font/roboto.ttf -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Farhandroid/AndroidCleanArchitecture/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Farhandroid/AndroidCleanArchitecture/HEAD/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/res/font/roboto_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Farhandroid/AndroidCleanArchitecture/HEAD/app/src/main/res/font/roboto_bold.ttf -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Farhandroid/AndroidCleanArchitecture/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Farhandroid/AndroidCleanArchitecture/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Farhandroid/AndroidCleanArchitecture/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Farhandroid/AndroidCleanArchitecture/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Farhandroid/AndroidCleanArchitecture/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Farhandroid/AndroidCleanArchitecture/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Farhandroid/AndroidCleanArchitecture/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Farhandroid/AndroidCleanArchitecture/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Farhandroid/AndroidCleanArchitecture/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Farhandroid/AndroidCleanArchitecture/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Farhandroid/AndroidCleanArchitecture/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Farhandroid/AndroidCleanArchitecture/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Farhandroid/AndroidCleanArchitecture/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Farhandroid/AndroidCleanArchitecture/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Farhandroid/AndroidCleanArchitecture/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /data/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /domain/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/java/com/farhan/tanvir/androidcleanarchitecture/util/Constant.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.androidcleanarchitecture.util 2 | 3 | 4 | object Constant { 5 | const val MOVIE_DETAILS_ARGUMENT_KEY = "movieId" 6 | } -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | MovieMania 3 | Something happened . Please try again . 4 | Details 5 | -------------------------------------------------------------------------------- /domain/src/main/java/com/farhan/tanvir/domain/useCase/MovieUseCases.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.domain.useCase 2 | 3 | 4 | data class MovieUseCases( 5 | val getPopularMoviesUseCase: GetPopularMoviesUseCase, 6 | val getMoviesFromDBUseCase: GetMoviesFromDBUseCase, 7 | ) 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/farhan/tanvir/androidcleanarchitecture/AndroidCleanArchitecture.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.androidcleanarchitecture 2 | import android.app.Application 3 | import dagger.hilt.android.HiltAndroidApp 4 | 5 | @HiltAndroidApp 6 | class AndroidCleanArchitecture : Application() -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jan 28 18:20:57 JST 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | gradle.properties 17 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /domain/src/main/java/com/farhan/tanvir/domain/useCase/GetPopularMoviesUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.domain.useCase 2 | 3 | import com.farhan.tanvir.domain.repository.MovieRepository 4 | 5 | class GetPopularMoviesUseCase(private val movieRepository: MovieRepository) { 6 | operator fun invoke() = movieRepository.getPopularMovies() 7 | } -------------------------------------------------------------------------------- /data/src/main/java/com/farhan/tanvir/data/repository/dataSource/MovieLocalDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.data.repository.dataSource 2 | import androidx.paging.PagingSource 3 | import com.farhan.tanvir.domain.model.Movie 4 | import kotlinx.coroutines.flow.Flow 5 | 6 | interface MovieLocalDataSource { 7 | fun getMoviesFromDB(movieId : Int): Flow 8 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/farhan/tanvir/domain/useCase/GetMoviesFromDBUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.domain.useCase 2 | 3 | import com.farhan.tanvir.domain.repository.MovieRepository 4 | 5 | 6 | class GetMoviesFromDBUseCase(private val movieRepository: MovieRepository) { 7 | operator fun invoke(movieID: Int) = movieRepository.getMoviesFromDB(movieID) 8 | } -------------------------------------------------------------------------------- /data/src/main/java/com/farhan/tanvir/data/repository/dataSource/MovieRemoteDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.data.repository.dataSource 2 | 3 | import androidx.paging.PagingData 4 | import com.farhan.tanvir.domain.model.Movie 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | interface MovieRemoteDataSource { 8 | fun getPopularMovies(): Flow> 9 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/farhan/tanvir/domain/model/MovieList.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.domain.model 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import java.io.Serializable 5 | 6 | data class MovieList( 7 | @SerializedName("page") 8 | val page: Int = 1, 9 | @SerializedName("results") 10 | val movies: List, 11 | ) : Serializable -------------------------------------------------------------------------------- /domain/src/main/java/com/farhan/tanvir/domain/repository/MovieRepository.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.domain.repository 2 | 3 | import androidx.paging.PagingData 4 | import com.farhan.tanvir.domain.model.Movie 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | 8 | interface MovieRepository { 9 | fun getPopularMovies(): Flow> 10 | fun getMoviesFromDB(movieId: Int): Flow 11 | } -------------------------------------------------------------------------------- /app/src/main/java/com/farhan/tanvir/androidcleanarchitecture/presentation/navigation/Screen.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.androidcleanarchitecture.presentation.navigation 2 | 3 | sealed class Screen(val route: String) { 4 | object Home : Screen("home_screen") 5 | object MovieDetails : Screen("movie_details_screen/{movieId}") { 6 | fun passMovieId(movieId: String) = "movie_details_screen/$movieId" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /domain/src/main/java/com/farhan/tanvir/domain/model/MovieRemoteKeys.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.domain.model 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | 6 | 7 | @Entity(tableName = "movie_remote_keys") 8 | data class MovieRemoteKeys( 9 | @PrimaryKey(autoGenerate = false) 10 | val id: Int, 11 | val prevPage: Int?, 12 | val nextPage: Int?, 13 | val lastUpdated: Long?, 14 | ) 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_star_rate.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/farhan/tanvir/androidcleanarchitecture/ui/theme/Shape.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.androidcleanarchitecture.ui.theme 2 | 3 | import androidx.compose.foundation.shape.RoundedCornerShape 4 | import androidx.compose.material.Shapes 5 | import androidx.compose.ui.unit.dp 6 | 7 | val Shapes = Shapes( 8 | small = RoundedCornerShape(4.dp), 9 | medium = RoundedCornerShape(4.dp), 10 | large = RoundedCornerShape(0.dp) 11 | ) -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | } 14 | } 15 | rootProject.name = "AndroidCleanArchitecture" 16 | include ':app' 17 | include ':data' 18 | include ':domain' 19 | -------------------------------------------------------------------------------- /data/src/test/java/com/farhan/tanvir/data/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.data 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /domain/src/test/java/com/farhan/tanvir/domain/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.domain 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | #FF9529 11 | -------------------------------------------------------------------------------- /data/src/main/java/com/farhan/tanvir/data/api/MovieApi.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.data.api 2 | 3 | import com.farhan.tanvir.domain.model.MovieList 4 | import retrofit2.Response 5 | import retrofit2.http.GET 6 | import retrofit2.http.Query 7 | 8 | interface MovieApi { 9 | @GET("movie/popular") 10 | suspend fun getPopularMovies( 11 | @Query( 12 | "api_key" 13 | ) apiKey: String, 14 | @Query("page") page: Int = 1, 15 | ): Response 16 | } -------------------------------------------------------------------------------- /app/debug/output-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "artifactType": { 4 | "type": "APK", 5 | "kind": "Directory" 6 | }, 7 | "applicationId": "com.farhan.tanvir.androidcleanarchitecture", 8 | "variantName": "debug", 9 | "elements": [ 10 | { 11 | "type": "SINGLE", 12 | "filters": [], 13 | "attributes": [], 14 | "versionCode": 1, 15 | "versionName": "1.0", 16 | "outputFile": "app-debug.apk" 17 | } 18 | ], 19 | "elementType": "File" 20 | } -------------------------------------------------------------------------------- /app/src/test/java/com/farhan/tanvir/androidcleanarchitecture/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.androidcleanarchitecture 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | # Gradle files 2 | .gradle/ 3 | build/ 4 | 5 | # Local configuration file (sdk path, etc) 6 | local.properties 7 | 8 | # Log/OS Files 9 | *.log 10 | 11 | # Android Studio generated files and folders 12 | captures/ 13 | .externalNativeBuild/ 14 | .cxx/ 15 | *.apk 16 | output.json 17 | 18 | # IntelliJ 19 | *.iml 20 | .idea/ 21 | 22 | # Keystore files 23 | *.jks 24 | *.keystore 25 | 26 | # Google Services (e.g. APIs or Firebase) 27 | google-services.json 28 | 29 | # Android Profiling 30 | *.hprof 31 | 32 | gradle.properties -------------------------------------------------------------------------------- /data/.gitignore: -------------------------------------------------------------------------------- 1 | # Gradle files 2 | .gradle/ 3 | build/ 4 | 5 | # Local configuration file (sdk path, etc) 6 | local.properties 7 | 8 | # Log/OS Files 9 | *.log 10 | 11 | # Android Studio generated files and folders 12 | captures/ 13 | .externalNativeBuild/ 14 | .cxx/ 15 | *.apk 16 | output.json 17 | 18 | # IntelliJ 19 | *.iml 20 | .idea/ 21 | 22 | # Keystore files 23 | *.jks 24 | *.keystore 25 | 26 | # Google Services (e.g. APIs or Firebase) 27 | google-services.json 28 | 29 | # Android Profiling 30 | *.hprof 31 | 32 | gradle.properties -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_date_range_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /domain/.gitignore: -------------------------------------------------------------------------------- 1 | # Gradle files 2 | .gradle/ 3 | build/ 4 | 5 | # Local configuration file (sdk path, etc) 6 | local.properties 7 | 8 | # Log/OS Files 9 | *.log 10 | 11 | # Android Studio generated files and folders 12 | captures/ 13 | .externalNativeBuild/ 14 | .cxx/ 15 | *.apk 16 | output.json 17 | 18 | # IntelliJ 19 | *.iml 20 | .idea/ 21 | 22 | # Keystore files 23 | *.jks 24 | *.keystore 25 | 26 | # Google Services (e.g. APIs or Firebase) 27 | google-services.json 28 | 29 | # Android Profiling 30 | *.hprof 31 | 32 | gradle.properties -------------------------------------------------------------------------------- /data/src/main/java/com/farhan/tanvir/data/db/MovieDB.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.data.db 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | import com.farhan.tanvir.domain.model.Movie 6 | import com.farhan.tanvir.domain.model.MovieRemoteKeys 7 | 8 | @Database( 9 | entities = [Movie::class, MovieRemoteKeys::class], 10 | version = 1, 11 | exportSchema = false 12 | ) 13 | abstract class MovieDB : RoomDatabase() { 14 | abstract fun movieDao(): MovieDao 15 | abstract fun movieRemoteKeysDao(): MovieRemoteKeysDao 16 | } -------------------------------------------------------------------------------- /data/src/main/java/com/farhan/tanvir/data/repository/dataSourceImpl/MovieLocalDataSourceImpl.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.data.repository.dataSourceImpl 2 | 3 | import com.farhan.tanvir.data.db.MovieDao 4 | import com.farhan.tanvir.data.repository.dataSource.MovieLocalDataSource 5 | import com.farhan.tanvir.domain.model.Movie 6 | import kotlinx.coroutines.flow.Flow 7 | 8 | 9 | class MovieLocalDataSourceImpl(private val movieDao: MovieDao) : MovieLocalDataSource { 10 | override fun getMoviesFromDB(movieId: Int): Flow = movieDao.getMovie(movieId) 11 | } -------------------------------------------------------------------------------- /app/src/main/java/com/farhan/tanvir/androidcleanarchitecture/presentation/screen/home/HomeViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.androidcleanarchitecture.presentation.screen.home 2 | 3 | import androidx.lifecycle.ViewModel 4 | import com.farhan.tanvir.domain.useCase.MovieUseCases 5 | import dagger.hilt.android.lifecycle.HiltViewModel 6 | import javax.inject.Inject 7 | 8 | @HiltViewModel 9 | class HomeViewModel @Inject constructor( 10 | movieUseCases: MovieUseCases, 11 | ) : ViewModel() { 12 | val getAllPopularMovies = movieUseCases.getPopularMoviesUseCase() 13 | 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/farhan/tanvir/androidcleanarchitecture/di/LocalDataModule.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.androidcleanarchitecture.di 2 | 3 | import com.farhan.tanvir.data.db.MovieDao 4 | import com.farhan.tanvir.data.repository.dataSource.MovieLocalDataSource 5 | import com.farhan.tanvir.data.repository.dataSourceImpl.MovieLocalDataSourceImpl 6 | import dagger.Module 7 | import dagger.Provides 8 | import dagger.hilt.InstallIn 9 | import dagger.hilt.components.SingletonComponent 10 | 11 | 12 | @Module 13 | @InstallIn(SingletonComponent::class) 14 | object LocalDataModule { 15 | @Provides 16 | fun provideLocalDataSource(movieDao: MovieDao): MovieLocalDataSource = 17 | MovieLocalDataSourceImpl(movieDao = movieDao) 18 | } -------------------------------------------------------------------------------- /data/src/main/java/com/farhan/tanvir/data/db/MovieRemoteKeysDao.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.data.db 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.OnConflictStrategy 6 | import androidx.room.Query 7 | import com.farhan.tanvir.domain.model.MovieRemoteKeys 8 | 9 | @Dao 10 | interface MovieRemoteKeysDao { 11 | 12 | @Query("SELECT * FROM movie_remote_keys WHERE id = :movieId") 13 | suspend fun getMovieRemoteKeys(movieId: Int): MovieRemoteKeys? 14 | 15 | @Insert(onConflict = OnConflictStrategy.REPLACE) 16 | suspend fun addAllMovieRemoteKeys(movieRemoteKeys : List) 17 | 18 | @Query("DELETE FROM movie_remote_keys") 19 | suspend fun deleteAllMovieRemoteKeys() 20 | } -------------------------------------------------------------------------------- /data/src/main/java/com/farhan/tanvir/data/db/MovieDao.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.data.db 2 | 3 | import androidx.paging.PagingSource 4 | import androidx.room.Dao 5 | import androidx.room.Insert 6 | import androidx.room.OnConflictStrategy 7 | import androidx.room.Query 8 | import com.farhan.tanvir.domain.model.Movie 9 | import kotlinx.coroutines.flow.Flow 10 | 11 | @Dao 12 | interface MovieDao { 13 | @Insert(onConflict = OnConflictStrategy.REPLACE) 14 | suspend fun addMovies(movies: List) 15 | 16 | @Query("SELECT * FROM movies") 17 | fun getAllMovies(): PagingSource 18 | 19 | @Query("SELECT * FROM movies WHERE movieId = :movieId") 20 | fun getMovie(movieId: Int): Flow 21 | 22 | @Query("DELETE FROM movies") 23 | suspend fun deleteAllMovies() 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/farhan/tanvir/androidcleanarchitecture/di/RemoteDataModule.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.androidcleanarchitecture.di 2 | 3 | import com.farhan.tanvir.data.api.MovieApi 4 | import com.farhan.tanvir.data.db.MovieDB 5 | import com.farhan.tanvir.data.repository.dataSource.MovieRemoteDataSource 6 | import com.farhan.tanvir.data.repository.dataSourceImpl.MovieRemoteDataSourceImpl 7 | import dagger.Module 8 | import dagger.Provides 9 | import dagger.hilt.InstallIn 10 | import dagger.hilt.components.SingletonComponent 11 | 12 | 13 | @Module 14 | @InstallIn(SingletonComponent::class) 15 | object RemoteDataModule { 16 | @Provides 17 | fun provideMoviesRemoteDataSource(movieApi: MovieApi, movieDB: MovieDB) : MovieRemoteDataSource = 18 | MovieRemoteDataSourceImpl(movieApi, movieDB = movieDB) 19 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/farhan/tanvir/domain/model/Movie.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.domain.model 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | import com.google.gson.annotations.SerializedName 6 | import java.io.Serializable 7 | 8 | @Entity(tableName = "movies") 9 | data class Movie( 10 | @PrimaryKey(autoGenerate = true) 11 | var pk: Long = 0, 12 | @SerializedName("id") 13 | val movieId: Int, 14 | @SerializedName("overview") 15 | val overview: String?, 16 | @SerializedName("poster_path") 17 | val posterPath: String?, 18 | @SerializedName("title") 19 | val title: String?, 20 | @SerializedName("vote_average") 21 | val rating: String?, 22 | @SerializedName("release_date") 23 | val releaseDate: String?, 24 | ) : Serializable -------------------------------------------------------------------------------- /data/src/androidTest/java/com/farhan/tanvir/data/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.data 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.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.getInstrumentation().targetContext 22 | assertEquals("com.farhan.tanvir.data.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /data/src/main/java/com/farhan/tanvir/data/repository/MovieRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.data.repository 2 | 3 | import com.farhan.tanvir.data.repository.dataSource.MovieLocalDataSource 4 | import com.farhan.tanvir.data.repository.dataSource.MovieRemoteDataSource 5 | import com.farhan.tanvir.domain.model.Movie 6 | import com.farhan.tanvir.domain.repository.MovieRepository 7 | import kotlinx.coroutines.flow.Flow 8 | 9 | class MovieRepositoryImpl( 10 | private val movieRemoteDataSource: MovieRemoteDataSource, 11 | private val movieLocalDataSource: MovieLocalDataSource, 12 | ) : 13 | MovieRepository { 14 | override fun getPopularMovies() = 15 | movieRemoteDataSource.getPopularMovies() 16 | 17 | override fun getMoviesFromDB(movieId: Int): Flow = 18 | movieLocalDataSource.getMoviesFromDB(movieId) 19 | } -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /domain/src/androidTest/java/com/farhan/tanvir/domain/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.domain 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.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.getInstrumentation().targetContext 22 | assertEquals("com.farhan.tanvir.domain.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/farhan/tanvir/androidcleanarchitecture/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.androidcleanarchitecture 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.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.getInstrumentation().targetContext 22 | assertEquals("com.farhan.tanvir.androidcleanarchitecture", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/farhan/tanvir/androidcleanarchitecture/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.androidcleanarchitecture.ui.theme 2 | 3 | import androidx.compose.material.Colors 4 | import androidx.compose.ui.graphics.Color 5 | import androidx.compose.ui.graphics.Color.Companion.Black 6 | import androidx.compose.ui.graphics.Color.Companion.White 7 | 8 | val Purple200 = Color(0xFFBB86FC) 9 | val Purple500 = Color(0xFF6200EE) 10 | val Purple700 = Color(0xFF3700B3) 11 | val Teal200 = Color(0xFF03DAC5) 12 | val BlueGrey900 = Color(0xFF263238) 13 | val Grey100 = Color(0xFFF5F5F5) 14 | 15 | val Colors.AppThemeColor 16 | get() = if (isLight) White else Black 17 | 18 | val Colors.AppContentColor 19 | get() = if (isLight) Black else White 20 | 21 | val Colors.TitleColor 22 | get() = if (isLight) Black else White 23 | 24 | val Colors.ItemBackgroundColor get() =if (isLight) Grey100 else BlueGrey900 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/farhan/tanvir/androidcleanarchitecture/di/UseCaseModule.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.androidcleanarchitecture.di 2 | 3 | import com.farhan.tanvir.domain.repository.MovieRepository 4 | import com.farhan.tanvir.domain.useCase.GetMoviesFromDBUseCase 5 | import com.farhan.tanvir.domain.useCase.GetPopularMoviesUseCase 6 | import com.farhan.tanvir.domain.useCase.MovieUseCases 7 | import dagger.Module 8 | import dagger.Provides 9 | import dagger.hilt.InstallIn 10 | import dagger.hilt.components.SingletonComponent 11 | 12 | @Module 13 | @InstallIn(SingletonComponent::class) 14 | object UseCaseModule { 15 | 16 | @Provides 17 | fun provideMovieUseCases(movieRepository: MovieRepository) = MovieUseCases( 18 | getPopularMoviesUseCase = GetPopularMoviesUseCase(movieRepository = movieRepository), 19 | getMoviesFromDBUseCase = GetMoviesFromDBUseCase(movieRepository = movieRepository) 20 | ) 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/farhan/tanvir/androidcleanarchitecture/di/RepositoryModule.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.androidcleanarchitecture.di 2 | 3 | import com.farhan.tanvir.data.repository.MovieRepositoryImpl 4 | import com.farhan.tanvir.data.repository.dataSource.MovieLocalDataSource 5 | import com.farhan.tanvir.data.repository.dataSource.MovieRemoteDataSource 6 | import com.farhan.tanvir.domain.repository.MovieRepository 7 | import dagger.Module 8 | import dagger.Provides 9 | import dagger.hilt.InstallIn 10 | import dagger.hilt.components.SingletonComponent 11 | 12 | 13 | @Module 14 | @InstallIn(SingletonComponent::class) 15 | object RepositoryModule { 16 | 17 | @Provides 18 | fun provideMoviesRepository( 19 | movieRemoteDataSource: MovieRemoteDataSource, 20 | movieLocalDataSource: MovieLocalDataSource 21 | ): MovieRepository = 22 | MovieRepositoryImpl(movieRemoteDataSource, movieLocalDataSource = movieLocalDataSource) 23 | } -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/farhan/tanvir/androidcleanarchitecture/di/DatabaseModule.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.androidcleanarchitecture.di 2 | 3 | import android.app.Application 4 | import androidx.room.Database 5 | import androidx.room.Room 6 | import com.farhan.tanvir.data.db.MovieDB 7 | import com.farhan.tanvir.data.db.MovieDao 8 | import com.farhan.tanvir.data.db.MovieRemoteKeysDao 9 | import dagger.Module 10 | import dagger.Provides 11 | import dagger.hilt.InstallIn 12 | import dagger.hilt.components.SingletonComponent 13 | 14 | @Module 15 | @InstallIn(SingletonComponent::class) 16 | object DatabaseModule { 17 | 18 | @Provides 19 | fun provideDatabase(app: Application): MovieDB = 20 | Room.databaseBuilder(app, MovieDB::class.java, "movie_db").fallbackToDestructiveMigration() 21 | .build() 22 | 23 | @Provides 24 | fun provideMovieDao(movieDB: MovieDB) : MovieDao= movieDB.movieDao() 25 | 26 | @Provides 27 | fun provideMovieRemoteKeysDao(movieDB: MovieDB) : MovieRemoteKeysDao = movieDB.movieRemoteKeysDao() 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/farhan/tanvir/androidcleanarchitecture/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.androidcleanarchitecture 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | import androidx.navigation.NavHostController 7 | import androidx.navigation.compose.rememberNavController 8 | import com.farhan.tanvir.androidcleanarchitecture.presentation.navigation.NavGraph 9 | import com.farhan.tanvir.androidcleanarchitecture.ui.theme.AndroidCleanArchitectureTheme 10 | import dagger.hilt.android.AndroidEntryPoint 11 | 12 | @AndroidEntryPoint 13 | class MainActivity : ComponentActivity() { 14 | private lateinit var navController: NavHostController 15 | 16 | override fun onCreate(savedInstanceState: Bundle?) { 17 | super.onCreate(savedInstanceState) 18 | setContent { 19 | AndroidCleanArchitectureTheme { 20 | navController = rememberNavController() 21 | NavGraph(navController = navController) 22 | } 23 | } 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/farhan/tanvir/androidcleanarchitecture/presentation/screen/details/MovieDetailsViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.androidcleanarchitecture.presentation.screen.details 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.farhan.tanvir.domain.model.Movie 6 | import com.farhan.tanvir.domain.useCase.MovieUseCases 7 | import dagger.hilt.android.lifecycle.HiltViewModel 8 | import kotlinx.coroutines.flow.MutableStateFlow 9 | import kotlinx.coroutines.flow.StateFlow 10 | import kotlinx.coroutines.launch 11 | import javax.inject.Inject 12 | 13 | @HiltViewModel 14 | class MovieDetailsViewModel @Inject constructor( 15 | private val movieUseCases: MovieUseCases 16 | ) : ViewModel() { 17 | private val _selectedMovie: MutableStateFlow = MutableStateFlow(null) 18 | val selectedMovie: StateFlow = _selectedMovie 19 | 20 | fun getMovieDetails(movieID: Int) { 21 | viewModelScope.launch { 22 | movieUseCases.getMoviesFromDBUseCase.invoke(movieID = movieID).collect { 23 | _selectedMovie.value = it 24 | } 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 15 | 16 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/farhan/tanvir/androidcleanarchitecture/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.androidcleanarchitecture.ui.theme 2 | 3 | import androidx.compose.material.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.Font 6 | import androidx.compose.ui.text.font.FontFamily 7 | import androidx.compose.ui.text.font.FontWeight 8 | import androidx.compose.ui.unit.sp 9 | import com.farhan.tanvir.androidcleanarchitecture.R 10 | 11 | val Roboto = FontFamily( 12 | Font(R.font.roboto), 13 | Font(R.font.roboto_bold, FontWeight.Bold) 14 | ) 15 | 16 | // Set of Material typography styles to start with 17 | val Typography = Typography( 18 | defaultFontFamily= Roboto, 19 | body1 = TextStyle( 20 | fontWeight = FontWeight.Bold, 21 | fontSize = 18.sp, 22 | letterSpacing = 0.15.sp, 23 | ), 24 | 25 | 26 | /* Other default text styles to override 27 | button = TextStyle( 28 | fontFamily = FontFamily.Default, 29 | fontWeight = FontWeight.W500, 30 | fontSize = 14.sp 31 | ), 32 | caption = TextStyle( 33 | fontFamily = FontFamily.Default, 34 | fontWeight = FontWeight.Normal, 35 | fontSize = 12.sp 36 | ) 37 | */ 38 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/farhan/tanvir/androidcleanarchitecture/presentation/components/RatingComponent.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.androidcleanarchitecture.presentation.components 2 | 3 | import androidx.compose.foundation.layout.Row 4 | import androidx.compose.foundation.layout.fillMaxWidth 5 | import androidx.compose.foundation.layout.padding 6 | import androidx.compose.material.Icon 7 | import androidx.compose.material.MaterialTheme 8 | import androidx.compose.material.Text 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Alignment 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.res.painterResource 13 | import androidx.compose.ui.unit.dp 14 | import com.farhan.tanvir.androidcleanarchitecture.R 15 | 16 | @Composable 17 | fun RatingComponent(rating: String) { 18 | Row( 19 | modifier = Modifier.fillMaxWidth(), 20 | verticalAlignment = Alignment.CenterVertically 21 | ) { 22 | Icon( 23 | painter = painterResource(id = R.drawable.ic_baseline_star_rate), 24 | contentDescription = null, 25 | modifier = Modifier 26 | .padding( 27 | end = 2.dp, 28 | ) 29 | ) 30 | Text(text = rating, style = MaterialTheme.typography.body2) 31 | } 32 | } -------------------------------------------------------------------------------- /data/src/main/java/com/farhan/tanvir/data/repository/dataSourceImpl/MovieRemoteDataSourceImpl.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.data.repository.dataSourceImpl 2 | 3 | import androidx.paging.ExperimentalPagingApi 4 | import androidx.paging.Pager 5 | import androidx.paging.PagingConfig 6 | import androidx.paging.PagingData 7 | import com.farhan.tanvir.data.api.MovieApi 8 | import com.farhan.tanvir.data.db.MovieDB 9 | import com.farhan.tanvir.data.paging.MovieRemoteMediator 10 | import com.farhan.tanvir.data.repository.dataSource.MovieRemoteDataSource 11 | import com.farhan.tanvir.domain.model.Movie 12 | import kotlinx.coroutines.flow.Flow 13 | 14 | 15 | class MovieRemoteDataSourceImpl(private val movieApi: MovieApi, private val movieDB: MovieDB) : 16 | MovieRemoteDataSource { 17 | private val movieDao = movieDB.movieDao() 18 | 19 | @OptIn(ExperimentalPagingApi::class) 20 | override fun getPopularMovies() : Flow> { 21 | val pagingSourceFactory = { movieDao.getAllMovies() } 22 | return Pager( 23 | config = PagingConfig(pageSize = 20), 24 | remoteMediator = MovieRemoteMediator( 25 | movieApi, 26 | movieDB 27 | ), 28 | pagingSourceFactory = pagingSourceFactory, 29 | ).flow 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/java/com/farhan/tanvir/androidcleanarchitecture/presentation/components/ReleaseDateComponent.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.androidcleanarchitecture.presentation.components 2 | 3 | import androidx.compose.foundation.layout.Row 4 | import androidx.compose.foundation.layout.fillMaxWidth 5 | import androidx.compose.foundation.layout.padding 6 | import androidx.compose.material.Icon 7 | import androidx.compose.material.MaterialTheme 8 | import androidx.compose.material.Text 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Alignment 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.res.painterResource 13 | import androidx.compose.ui.unit.dp 14 | import com.farhan.tanvir.androidcleanarchitecture.R 15 | import java.util.* 16 | 17 | 18 | @Composable 19 | fun ReleaseDateComponent(releaseDate: String) { 20 | Row( 21 | modifier = Modifier.fillMaxWidth(), 22 | verticalAlignment = Alignment.CenterVertically 23 | ) { 24 | Icon( 25 | painter = painterResource(id = R.drawable.ic_baseline_date_range_24), 26 | contentDescription = null, 27 | modifier = Modifier.padding( 28 | end = 2.dp, 29 | ) 30 | ) 31 | Text(text = releaseDate, style = MaterialTheme.typography.body2) 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/farhan/tanvir/androidcleanarchitecture/presentation/screen/details/MovieDetailsScreen.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.androidcleanarchitecture.presentation.screen.details 2 | 3 | import androidx.compose.material.MaterialTheme 4 | import androidx.compose.material.Scaffold 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.runtime.collectAsState 7 | import androidx.compose.runtime.getValue 8 | import androidx.hilt.navigation.compose.hiltViewModel 9 | import androidx.navigation.NavController 10 | import com.farhan.tanvir.androidcleanarchitecture.ui.theme.AppContentColor 11 | import com.farhan.tanvir.androidcleanarchitecture.ui.theme.AppThemeColor 12 | 13 | 14 | @Composable 15 | fun MovieDetailsScreen( 16 | movieId: String, 17 | navController: NavController, 18 | viewModel: MovieDetailsViewModel = hiltViewModel(), 19 | ) { 20 | viewModel.getMovieDetails(movieID = movieId.toInt()) 21 | val movieDetails by viewModel.selectedMovie.collectAsState() 22 | Scaffold( 23 | topBar={ 24 | MovieDetailsTopBar(navController) 25 | }, 26 | contentColor = MaterialTheme.colors.AppContentColor, 27 | backgroundColor = MaterialTheme.colors.AppThemeColor, 28 | content = { 29 | movieDetails?.let { MovieDetailsContent(it) } 30 | }) 31 | } 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/farhan/tanvir/androidcleanarchitecture/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.androidcleanarchitecture.ui.theme 2 | 3 | import androidx.compose.foundation.isSystemInDarkTheme 4 | import androidx.compose.material.MaterialTheme 5 | import androidx.compose.material.darkColors 6 | import androidx.compose.material.lightColors 7 | import androidx.compose.runtime.Composable 8 | 9 | private val DarkColorPalette = darkColors( 10 | primary = Purple200, 11 | primaryVariant = Purple700, 12 | secondary = Teal200 13 | ) 14 | 15 | private val LightColorPalette = lightColors( 16 | primary = Purple500, 17 | primaryVariant = Purple700, 18 | secondary = Teal200 19 | 20 | /* Other default colors to override 21 | background = Color.White, 22 | surface = Color.White, 23 | onPrimary = Color.White, 24 | onSecondary = Color.Black, 25 | onBackground = Color.Black, 26 | onSurface = Color.Black, 27 | */ 28 | ) 29 | 30 | @Composable 31 | fun AndroidCleanArchitectureTheme( 32 | darkTheme: Boolean = isSystemInDarkTheme(), 33 | content: @Composable () -> Unit 34 | ) { 35 | val colors = if (darkTheme) { 36 | DarkColorPalette 37 | } else { 38 | LightColorPalette 39 | } 40 | 41 | MaterialTheme( 42 | colors = colors, 43 | typography = Typography, 44 | shapes = Shapes, 45 | content = content 46 | ) 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/farhan/tanvir/androidcleanarchitecture/presentation/navigation/NavGraph.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.androidcleanarchitecture.presentation.navigation 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.navigation.NavHostController 5 | import androidx.navigation.NavType 6 | import androidx.navigation.compose.NavHost 7 | import androidx.navigation.compose.composable 8 | import androidx.navigation.navArgument 9 | import com.farhan.tanvir.androidcleanarchitecture.presentation.screen.details.MovieDetailsScreen 10 | import com.farhan.tanvir.androidcleanarchitecture.presentation.screen.home.HomeScreen 11 | import com.farhan.tanvir.androidcleanarchitecture.util.Constant 12 | 13 | @Composable 14 | fun NavGraph(navController: NavHostController) { 15 | NavHost( 16 | navController = navController, 17 | startDestination = Screen.Home.route 18 | ) { 19 | composable(route = Screen.Home.route) { 20 | HomeScreen(navController = navController) 21 | } 22 | composable( 23 | route = Screen.MovieDetails.route, 24 | arguments = listOf(navArgument(Constant.MOVIE_DETAILS_ARGUMENT_KEY) { 25 | type = NavType.StringType 26 | }) 27 | ) { backStackEntry -> 28 | backStackEntry.arguments?.getString(Constant.MOVIE_DETAILS_ARGUMENT_KEY) 29 | ?.let { MovieDetailsScreen(it,navController) } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/java/com/farhan/tanvir/androidcleanarchitecture/di/NetworkModule.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.androidcleanarchitecture.di 2 | 3 | import com.farhan.tanvir.androidcleanarchitecture.BuildConfig 4 | import com.farhan.tanvir.data.api.MovieApi 5 | import dagger.Module 6 | import dagger.Provides 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.components.SingletonComponent 9 | import okhttp3.OkHttpClient 10 | import retrofit2.Retrofit 11 | import retrofit2.converter.gson.GsonConverterFactory 12 | import java.util.concurrent.TimeUnit 13 | import javax.inject.Singleton 14 | 15 | @Module 16 | @InstallIn(SingletonComponent::class) 17 | object NetworkModule { 18 | 19 | @Provides 20 | @Singleton 21 | fun provideHttpClient(): OkHttpClient { 22 | return OkHttpClient.Builder() 23 | .readTimeout(20, TimeUnit.SECONDS) 24 | .connectTimeout(20, TimeUnit.SECONDS) 25 | .build() 26 | } 27 | 28 | @Provides 29 | @Singleton 30 | fun provideRetrofitInstance(okHttpClient: OkHttpClient): Retrofit { 31 | return Retrofit.Builder() 32 | .baseUrl(BuildConfig.BASE_URL) 33 | .client(okHttpClient) 34 | .addConverterFactory(GsonConverterFactory.create()) 35 | .build() 36 | } 37 | 38 | @Provides 39 | @Singleton 40 | fun provideMovieApi(retrofit: Retrofit): MovieApi { 41 | return retrofit.create(MovieApi::class.java) 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /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=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true 24 | API_KEY="API_KEY" 25 | BASE_URL="BASE_URL" 26 | POSTER_URL = "POSTER_URL" -------------------------------------------------------------------------------- /app/src/main/java/com/farhan/tanvir/androidcleanarchitecture/presentation/screen/home/HomeScreen.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.androidcleanarchitecture.presentation.screen.home 2 | 3 | 4 | import androidx.compose.material.MaterialTheme 5 | import androidx.compose.material.Scaffold 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.runtime.SideEffect 8 | import androidx.hilt.navigation.compose.hiltViewModel 9 | import androidx.navigation.NavHostController 10 | import androidx.paging.compose.collectAsLazyPagingItems 11 | import com.farhan.tanvir.androidcleanarchitecture.ui.theme.AppContentColor 12 | import com.farhan.tanvir.androidcleanarchitecture.ui.theme.AppThemeColor 13 | import com.google.accompanist.systemuicontroller.rememberSystemUiController 14 | 15 | @Composable 16 | fun HomeScreen(navController: NavHostController, viewModel: HomeViewModel = hiltViewModel()) { 17 | 18 | val systemUiController = rememberSystemUiController() 19 | val systemBarColor = MaterialTheme.colors.AppThemeColor 20 | val allMovies = viewModel.getAllPopularMovies.collectAsLazyPagingItems() 21 | 22 | SideEffect { 23 | systemUiController.setStatusBarColor( 24 | color = systemBarColor 25 | ) 26 | } 27 | 28 | Scaffold( 29 | backgroundColor = MaterialTheme.colors.AppThemeColor, 30 | contentColor = MaterialTheme.colors.AppContentColor, 31 | topBar = { 32 | HomeTopBar() 33 | }, 34 | content = { 35 | MovieListContent(allMovies = allMovies, navController = navController) 36 | } 37 | ) 38 | } 39 | 40 | -------------------------------------------------------------------------------- /domain/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'org.jetbrains.kotlin.android' 4 | } 5 | 6 | android { 7 | compileSdk 32 8 | 9 | defaultConfig { 10 | minSdk 21 11 | targetSdk 32 12 | 13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 14 | consumerProguardFiles "consumer-rules.pro" 15 | } 16 | 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | compileOptions { 24 | sourceCompatibility JavaVersion.VERSION_1_8 25 | targetCompatibility JavaVersion.VERSION_1_8 26 | } 27 | kotlinOptions { 28 | jvmTarget = '1.8' 29 | } 30 | } 31 | 32 | dependencies { 33 | 34 | implementation 'androidx.core:core-ktx:1.7.0' 35 | implementation 'androidx.appcompat:appcompat:1.4.1' 36 | implementation 'com.google.android.material:material:1.5.0' 37 | testImplementation 'junit:junit:4.13.2' 38 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 39 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 40 | 41 | //Coroutines 42 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version" 43 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version" 44 | 45 | //GSON 46 | implementation 'com.google.code.gson:gson:2.8.9' 47 | 48 | //ROOM DB 49 | implementation "androidx.room:room-ktx:$room_version" 50 | 51 | // Paging 3.0 52 | implementation 'androidx.paging:paging-compose:1.0.0-alpha14' 53 | } -------------------------------------------------------------------------------- /app/src/main/java/com/farhan/tanvir/androidcleanarchitecture/presentation/screen/details/MovieDetailsTopBar.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.androidcleanarchitecture.presentation.screen.details 2 | 3 | import androidx.compose.foundation.layout.fillMaxWidth 4 | import androidx.compose.material.* 5 | import androidx.compose.material.icons.Icons 6 | import androidx.compose.material.icons.filled.ArrowBack 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.res.stringResource 10 | import androidx.compose.ui.text.font.FontWeight 11 | import androidx.compose.ui.unit.dp 12 | import androidx.navigation.NavController 13 | import com.farhan.tanvir.androidcleanarchitecture.R 14 | import com.farhan.tanvir.androidcleanarchitecture.ui.theme.AppContentColor 15 | import com.farhan.tanvir.androidcleanarchitecture.ui.theme.AppThemeColor 16 | 17 | @Composable 18 | fun MovieDetailsTopBar( 19 | navController: NavController 20 | ) { 21 | TopAppBar( 22 | backgroundColor = MaterialTheme.colors.AppThemeColor, 23 | navigationIcon = { 24 | IconButton(onClick = { navController.popBackStack() }) { 25 | Icon( 26 | imageVector = Icons.Default.ArrowBack, 27 | contentDescription = "Back Icon", 28 | tint = MaterialTheme.colors.AppContentColor 29 | ) 30 | } 31 | }, 32 | title = { 33 | Text( 34 | text = stringResource(R.string.details), 35 | color = MaterialTheme.colors.AppContentColor, 36 | fontWeight = FontWeight.Bold, 37 | modifier = Modifier.fillMaxWidth(), 38 | style = MaterialTheme.typography.h6 39 | ) 40 | }, 41 | elevation = 0.dp, 42 | ) 43 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /data/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'org.jetbrains.kotlin.android' 4 | id 'kotlin-kapt' 5 | } 6 | 7 | android { 8 | compileSdk 32 9 | 10 | defaultConfig { 11 | minSdk 21 12 | targetSdk 32 13 | 14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 15 | consumerProguardFiles "consumer-rules.pro" 16 | buildConfigField("String","API_KEY",API_KEY) 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | compileOptions { 26 | sourceCompatibility JavaVersion.VERSION_1_8 27 | targetCompatibility JavaVersion.VERSION_1_8 28 | } 29 | kotlinOptions { 30 | jvmTarget = '1.8' 31 | } 32 | } 33 | 34 | dependencies { 35 | 36 | implementation 'androidx.core:core-ktx:1.7.0' 37 | implementation 'androidx.appcompat:appcompat:1.4.1' 38 | implementation 'com.google.android.material:material:1.5.0' 39 | implementation project(path: ':domain') 40 | testImplementation 'junit:junit:4.13.2' 41 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 42 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 43 | 44 | // Retrofit 45 | implementation 'com.squareup.retrofit2:retrofit:2.9.0' 46 | implementation 'com.squareup.retrofit2:converter-gson:2.9.0' 47 | 48 | //Coroutines 49 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0' 50 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0' 51 | 52 | //ROOM DB 53 | implementation "androidx.room:room-runtime:$room_version" 54 | kapt "androidx.room:room-compiler:$room_version" 55 | implementation "androidx.room:room-ktx:$room_version" 56 | implementation "androidx.room:room-paging:$room_version" 57 | 58 | // Paging 3.0 59 | implementation 'androidx.paging:paging-compose:1.0.0-alpha14' 60 | 61 | 62 | } -------------------------------------------------------------------------------- /app/src/main/java/com/farhan/tanvir/androidcleanarchitecture/presentation/screen/home/HomeTopBar.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.androidcleanarchitecture.presentation.screen.home 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.net.Uri 6 | import androidx.compose.foundation.layout.fillMaxWidth 7 | import androidx.compose.material.* 8 | import androidx.compose.material.icons.Icons 9 | import androidx.compose.material.icons.filled.Favorite 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.graphics.Color 13 | import androidx.compose.ui.platform.LocalContext 14 | import androidx.compose.ui.res.stringResource 15 | import androidx.compose.ui.text.font.FontWeight 16 | import androidx.compose.ui.unit.dp 17 | import androidx.core.content.ContextCompat 18 | import com.farhan.tanvir.androidcleanarchitecture.R 19 | import com.farhan.tanvir.androidcleanarchitecture.ui.theme.AppContentColor 20 | import com.farhan.tanvir.androidcleanarchitecture.ui.theme.AppThemeColor 21 | 22 | @Composable 23 | fun HomeTopBar( 24 | ) { 25 | val context = LocalContext.current 26 | TopAppBar( 27 | backgroundColor = MaterialTheme.colors.AppThemeColor, 28 | title = { 29 | Text( 30 | text = stringResource(R.string.app_name), 31 | color = MaterialTheme.colors.AppContentColor, 32 | fontWeight = FontWeight.Bold, 33 | modifier = Modifier.fillMaxWidth(), 34 | style = MaterialTheme.typography.h5 35 | ) 36 | }, 37 | elevation = 0.dp, 38 | actions = { 39 | 40 | IconButton(onClick = { showMessage(context = context) }) { 41 | Icon( 42 | imageVector = Icons.Default.Favorite, 43 | contentDescription = "Favourite Icon", 44 | tint = Color.Red 45 | ) 46 | } 47 | } 48 | ) 49 | } 50 | 51 | fun showMessage(context: Context) { 52 | val browserIntent = Intent( 53 | Intent.ACTION_VIEW, 54 | Uri.parse("https://github.com/Farhandroid/AndroidCleanArchitecture") 55 | ) 56 | ContextCompat.startActivity(context, browserIntent, null) 57 | } 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![MovieMania](https://user-images.githubusercontent.com/32593150/154197538-a9ad9f79-1bb5-44b1-8f03-1f9b287d46ed.jpg) 2 | 3 | # MovieMania 4 | ***A minimalistic Android application Built with Clean architecture and Jetpack component (Jetpack Compose, MVVM , Paging 5 | 3.0, HILT, ROOM DB, Retrofit, and many more …). It was made to describe the latest android development trend. This app will be updated further to adapt to latest development trend*** 6 | 7 | ####

Get the latest MovieMania app apk from below 👇 8 | [![MovieMania](https://img.shields.io/badge/MovieMania-APK-blue)](https://github.com/Farhandroid/AndroidCleanArchitecture/releases/download/Latest/MovieMania-debug.apk) 9 | 10 | ###

This project is described in below articles . Please check it . 11 | #### [ PART1](https://farhan-tanvir.medium.com/clean-architecture-in-android-jetpack-compose-kotlin-mvvm-%E3%83%BCpart-1-f17908b83c0d) 12 | #### [ PART2](https://farhan-tanvir.medium.com/clean-architecture-in-android-jetpack-compose-paging-3-0-kotlin-mvvm-%E3%83%BCpart-2-8d97cee4dffe) 13 | 14 | ##

Screenshort 📸 15 | ![Screenshort](https://user-images.githubusercontent.com/32593150/154197554-9325bc59-1f29-44b3-a85b-d7561ea158df.jpg) 16 | 17 | ##

Built With 🛠 18 | - [Kotlin](https://kotlinlang.org/) 19 | - [Coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html) 20 | - [Compose](https://developer.android.com/jetpack/compose) 21 | - [Navigation](https://developer.android.com/guide/navigation) 22 | - [Android Architecture Components](https://developer.android.com/topic/libraries/architecture) 23 | - [LiveData](https://developer.android.com/topic/libraries/architecture/livedata) 24 | - [ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel) 25 | - [Hilt](https://dagger.dev/hilt/) 26 | - [Paging3.0](https://developer.android.com/topic/libraries/architecture/paging/v3-overview) 27 | - [Retrofit](https://square.github.io/retrofit/) 28 | - [GSON](https://github.com/google/gson) 29 | - [Coil](https://github.com/chrisbanes/accompanist/blob/main/coil/README.md) 30 | - [Accompanist](https://google.github.io/accompanist/) 31 | - [Material Components for Android](https://github.com/material-components/material-components-android) 32 | 33 | ##

How to run this app 34 | - Clone this repository 35 | - Register in [TMDB](https://developers.themoviedb.org/) and get the API_KEY , BASE_URL and POSTER_URL and put it in graddle.properties 36 | - Build the application 37 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /app/src/main/java/com/farhan/tanvir/androidcleanarchitecture/presentation/screen/details/MovieDetailsContent.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.androidcleanarchitecture.presentation.screen.details 2 | 3 | import androidx.compose.foundation.Image 4 | import androidx.compose.foundation.layout.* 5 | import androidx.compose.foundation.rememberScrollState 6 | import androidx.compose.foundation.verticalScroll 7 | import androidx.compose.material.Card 8 | import androidx.compose.material.MaterialTheme 9 | import androidx.compose.material.Text 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.layout.ContentScale 13 | import androidx.compose.ui.text.font.FontWeight 14 | import androidx.compose.ui.unit.dp 15 | import coil.compose.rememberImagePainter 16 | import coil.size.Scale 17 | import com.farhan.tanvir.androidcleanarchitecture.BuildConfig 18 | import com.farhan.tanvir.androidcleanarchitecture.presentation.components.RatingComponent 19 | import com.farhan.tanvir.androidcleanarchitecture.presentation.components.ReleaseDateComponent 20 | import com.farhan.tanvir.androidcleanarchitecture.ui.theme.AppThemeColor 21 | import com.farhan.tanvir.domain.model.Movie 22 | 23 | @Composable 24 | fun MovieDetailsContent(movie: Movie) { 25 | val scrollState = rememberScrollState() 26 | Card( 27 | elevation = 0.dp, 28 | backgroundColor = MaterialTheme.colors.AppThemeColor 29 | ) { 30 | Column( 31 | modifier = Modifier 32 | .fillMaxWidth() 33 | .verticalScroll(scrollState) 34 | ) { 35 | Image( 36 | painter = rememberImagePainter( 37 | data = BuildConfig.POSTER_URL + movie.posterPath, builder = { 38 | crossfade(true) 39 | scale(Scale.FIT) 40 | }), 41 | contentDescription = null, 42 | modifier = Modifier 43 | .fillMaxWidth() 44 | .height(350.dp), 45 | contentScale = ContentScale.FillWidth 46 | ) 47 | Column(modifier = Modifier.padding(8.dp)) { 48 | Spacer(modifier = Modifier.height(16.dp)) 49 | movie.title?.let { 50 | Text( 51 | text = it, 52 | style = MaterialTheme.typography.h5, 53 | fontWeight = FontWeight.Bold 54 | ) 55 | } 56 | Spacer(modifier = Modifier.height(8.dp)) 57 | movie.releaseDate?.let { 58 | ReleaseDateComponent(releaseDate = it) 59 | } 60 | Spacer(modifier = Modifier.height(8.dp)) 61 | movie.rating?.let { RatingComponent(rating = it) } 62 | Spacer(modifier = Modifier.height(16.dp)) 63 | movie.overview?.let { 64 | Text( 65 | text = it, 66 | style = MaterialTheme.typography.body2 67 | ) 68 | } 69 | } 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'org.jetbrains.kotlin.android' 4 | id 'kotlin-kapt' 5 | id "dagger.hilt.android.plugin" 6 | } 7 | 8 | android { 9 | compileSdk 32 10 | 11 | defaultConfig { 12 | applicationId "com.farhan.tanvir.androidcleanarchitecture" 13 | minSdk 21 14 | targetSdk 32 15 | versionCode 1 16 | versionName "1.0" 17 | 18 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 19 | vectorDrawables { 20 | useSupportLibrary true 21 | } 22 | buildConfigField("String","BASE_URL",BASE_URL) 23 | buildConfigField("String","POSTER_URL",POSTER_URL) 24 | 25 | } 26 | 27 | buildTypes { 28 | release { 29 | minifyEnabled false 30 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 31 | } 32 | } 33 | compileOptions { 34 | sourceCompatibility JavaVersion.VERSION_1_8 35 | targetCompatibility JavaVersion.VERSION_1_8 36 | } 37 | kotlinOptions { 38 | jvmTarget = '1.8' 39 | } 40 | buildFeatures { 41 | compose true 42 | } 43 | composeOptions { 44 | kotlinCompilerExtensionVersion compose_version 45 | } 46 | packagingOptions { 47 | resources { 48 | excludes += '/META-INF/{AL2.0,LGPL2.1}' 49 | } 50 | } 51 | } 52 | 53 | dependencies { 54 | 55 | implementation 'androidx.core:core-ktx:1.7.0' 56 | implementation "androidx.compose.ui:ui:$compose_version" 57 | implementation "androidx.compose.material:material:$compose_version" 58 | implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" 59 | implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0' 60 | implementation 'androidx.activity:activity-compose:1.4.0' 61 | implementation project(path: ':data') 62 | implementation project(path: ':domain') 63 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 64 | implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.0' 65 | implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0' 66 | testImplementation 'junit:junit:4.13.2' 67 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 68 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 69 | androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" 70 | debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" 71 | 72 | //hilt 73 | implementation "com.google.dagger:hilt-android:$hilt_version" 74 | kapt "com.google.dagger:hilt-compiler:$hilt_version" 75 | implementation 'androidx.hilt:hilt-navigation-compose:1.0.0' 76 | 77 | // Retrofit 78 | implementation 'com.squareup.retrofit2:retrofit:2.9.0' 79 | implementation 'com.squareup.retrofit2:converter-gson:2.9.0' 80 | implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.8.0") 81 | 82 | //GSON 83 | implementation 'com.google.code.gson:gson:2.8.9' 84 | 85 | // Coil 86 | implementation("io.coil-kt:coil-compose:1.4.0") 87 | 88 | // System UI Controller - Accompanist 89 | implementation "com.google.accompanist:accompanist-systemuicontroller:0.17.0" 90 | 91 | //ROOM DB 92 | implementation "androidx.room:room-runtime:$room_version" 93 | kapt "androidx.room:room-compiler:$room_version" 94 | implementation "androidx.room:room-ktx:$room_version" 95 | implementation "androidx.room:room-paging:$room_version" 96 | 97 | // Paging 3.0 98 | implementation 'androidx.paging:paging-compose:1.0.0-alpha14' 99 | 100 | } 101 | 102 | kapt { 103 | correctErrorTypes true 104 | } -------------------------------------------------------------------------------- /app/src/main/java/com/farhan/tanvir/androidcleanarchitecture/presentation/screen/home/MovieList.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.androidcleanarchitecture.presentation.screen.home 2 | 3 | import androidx.compose.foundation.Image 4 | import androidx.compose.foundation.clickable 5 | import androidx.compose.foundation.layout.* 6 | import androidx.compose.foundation.lazy.LazyColumn 7 | import androidx.compose.material.Card 8 | import androidx.compose.material.MaterialTheme 9 | import androidx.compose.material.Text 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.Alignment 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.layout.ContentScale 14 | import androidx.compose.ui.text.style.TextOverflow 15 | import androidx.compose.ui.unit.dp 16 | import androidx.navigation.NavHostController 17 | import androidx.paging.compose.LazyPagingItems 18 | import androidx.paging.compose.items 19 | import coil.compose.rememberImagePainter 20 | import coil.size.Scale 21 | import com.farhan.tanvir.androidcleanarchitecture.BuildConfig 22 | import com.farhan.tanvir.androidcleanarchitecture.presentation.components.RatingComponent 23 | import com.farhan.tanvir.androidcleanarchitecture.presentation.navigation.Screen 24 | import com.farhan.tanvir.androidcleanarchitecture.ui.theme.ItemBackgroundColor 25 | import com.farhan.tanvir.domain.model.Movie 26 | 27 | @Composable 28 | fun MovieListContent(allMovies: LazyPagingItems, navController: NavHostController) { 29 | LazyColumn( 30 | contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp) 31 | ) { 32 | items( 33 | items = allMovies, 34 | key = { movie -> 35 | movie.pk 36 | } 37 | ) { movie -> 38 | if (movie != null) { 39 | MovieListItem(movie = movie, navController = navController) 40 | } 41 | } 42 | } 43 | } 44 | 45 | @Composable 46 | fun MovieListItem(movie: Movie, navController: NavHostController) { 47 | Card( 48 | modifier = Modifier 49 | .padding(top = 8.dp) 50 | .height(180.dp) 51 | .fillMaxWidth(), 52 | elevation = 4.dp, 53 | backgroundColor = MaterialTheme.colors.ItemBackgroundColor 54 | ) { 55 | Row( 56 | modifier = Modifier 57 | .height(IntrinsicSize.Max) 58 | .fillMaxWidth() 59 | .clickable { 60 | navController.navigate(route = Screen.MovieDetails.passMovieId(movie.movieId.toString())) 61 | }, 62 | verticalAlignment = Alignment.CenterVertically 63 | ) { 64 | movie.posterPath?.let { 65 | Image( 66 | modifier = Modifier 67 | .padding( 68 | end = 4.dp, 69 | ) 70 | .width(120.dp), 71 | painter = rememberImagePainter( 72 | data = BuildConfig.POSTER_URL + movie.posterPath, builder = { 73 | crossfade(true) 74 | scale(Scale.FILL) 75 | }), 76 | contentDescription = null, 77 | contentScale = ContentScale.Fit 78 | ) 79 | } 80 | Column(Modifier 81 | .height(IntrinsicSize.Max) 82 | .padding( 83 | end = 2.dp, 84 | )) { 85 | movie.title?.let { Text(text = it, style = MaterialTheme.typography.body1) } 86 | Spacer(modifier = Modifier.height(4.dp)) 87 | movie.overview?.let { 88 | Text( 89 | text = it, 90 | style = MaterialTheme.typography.body2, 91 | maxLines = 4, 92 | overflow = TextOverflow.Ellipsis 93 | ) 94 | } 95 | Spacer(modifier = Modifier.height(8.dp)) 96 | movie.rating?.let { RatingComponent(rating = it) } 97 | } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /data/src/main/java/com/farhan/tanvir/data/paging/MovieRemoteMediator.kt: -------------------------------------------------------------------------------- 1 | package com.farhan.tanvir.data.paging 2 | 3 | import androidx.paging.ExperimentalPagingApi 4 | import androidx.paging.LoadType 5 | import androidx.paging.PagingState 6 | import androidx.paging.RemoteMediator 7 | import androidx.room.withTransaction 8 | import com.farhan.tanvir.data.BuildConfig 9 | import com.farhan.tanvir.data.api.MovieApi 10 | import com.farhan.tanvir.data.db.MovieDB 11 | import com.farhan.tanvir.domain.model.Movie 12 | import com.farhan.tanvir.domain.model.MovieRemoteKeys 13 | 14 | @OptIn(ExperimentalPagingApi::class) 15 | class MovieRemoteMediator(private val movieApi: MovieApi, private val movieDB: MovieDB) : 16 | RemoteMediator() { 17 | 18 | private val movieDao = movieDB.movieDao() 19 | private val movieRemoteKeysDao = movieDB.movieRemoteKeysDao() 20 | 21 | override suspend fun load(loadType: LoadType, state: PagingState): MediatorResult { 22 | return try { 23 | val page = when (loadType) { 24 | LoadType.REFRESH -> { 25 | val remoteKeys = getRemoteKeyClosestToCurrentPosition(state) 26 | remoteKeys?.nextPage?.minus(1) ?: 1 27 | } 28 | LoadType.PREPEND -> { 29 | val remoteKeys = getRemoteKeyForFirstItem(state) 30 | val prevPage = remoteKeys?.prevPage 31 | ?: return MediatorResult.Success( 32 | endOfPaginationReached = remoteKeys != null 33 | ) 34 | prevPage 35 | } 36 | LoadType.APPEND -> { 37 | val remoteKeys = getRemoteKeyForLastItem(state) 38 | val nextPage = remoteKeys?.nextPage 39 | ?: return MediatorResult.Success( 40 | endOfPaginationReached = remoteKeys != null 41 | ) 42 | nextPage 43 | } 44 | } 45 | val response = movieApi.getPopularMovies(apiKey = BuildConfig.API_KEY, page = page) 46 | var endOfPaginationReached = false 47 | if (response.isSuccessful) { 48 | val responseData = response.body() 49 | endOfPaginationReached = responseData == null 50 | responseData?.let { 51 | movieDB.withTransaction { 52 | if (loadType == LoadType.REFRESH) { 53 | movieDao.deleteAllMovies() 54 | movieRemoteKeysDao.deleteAllMovieRemoteKeys() 55 | } 56 | var prevPage: Int? 57 | var nextPage: Int 58 | 59 | responseData.page.let { pageNumber -> 60 | nextPage = pageNumber + 1 61 | prevPage = if (pageNumber <= 1) null else pageNumber - 1 62 | } 63 | 64 | val keys = responseData.movies.map { movie -> 65 | MovieRemoteKeys( 66 | id = movie.movieId, 67 | prevPage = prevPage, 68 | nextPage = nextPage, 69 | lastUpdated = System.currentTimeMillis() 70 | ) 71 | } 72 | movieRemoteKeysDao.addAllMovieRemoteKeys(movieRemoteKeys = keys) 73 | movieDao.addMovies(movies = responseData.movies) 74 | } 75 | } 76 | 77 | } 78 | MediatorResult.Success(endOfPaginationReached = endOfPaginationReached) 79 | } catch (e: Exception) { 80 | return MediatorResult.Error(e) 81 | } 82 | } 83 | 84 | private suspend fun getRemoteKeyClosestToCurrentPosition( 85 | state: PagingState, 86 | ): MovieRemoteKeys? { 87 | return state.anchorPosition?.let { position -> 88 | state.closestItemToPosition(position)?.movieId?.let { id -> 89 | movieRemoteKeysDao.getMovieRemoteKeys(movieId = id) 90 | } 91 | } 92 | } 93 | 94 | private suspend fun getRemoteKeyForFirstItem( 95 | state: PagingState, 96 | ): MovieRemoteKeys? { 97 | return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull() 98 | ?.let { movie -> 99 | movieRemoteKeysDao.getMovieRemoteKeys(movieId = movie.movieId) 100 | } 101 | } 102 | 103 | private suspend fun getRemoteKeyForLastItem( 104 | state: PagingState, 105 | ): MovieRemoteKeys? { 106 | return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull() 107 | ?.let { movie -> 108 | movieRemoteKeysDao.getMovieRemoteKeys(movieId = movie.movieId) 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | --------------------------------------------------------------------------------