diff --git a/app/build.gradle.kts b/app/build.gradle.kts index bd81a117f..e8bebb5d5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,6 +1,6 @@ +import java.io.FileInputStream import org.apache.commons.io.output.ByteArrayOutputStream import org.jetbrains.kotlin.konan.properties.Properties -import java.io.FileInputStream @Suppress("dsl_scope_violation") plugins { @@ -11,7 +11,6 @@ plugins { alias(libs.plugins.firebase.appdistribution) alias(libs.plugins.firebase.crashlytics) alias(libs.plugins.palantir.git) - alias(libs.plugins.detekt) id("movies-android-hilt") } @@ -148,15 +147,20 @@ android { checkDependencies = true lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") } + + kapt { + correctErrorTypes = true + } } dependencies { implementation(project(":core:analytics")) implementation(project(":core:common")) - implementation(project(":core:domain")) + implementation(project(":core:interactor")) implementation(project(":core:navigation")) implementation(project(":core:notifications")) implementation(project(":core:ui")) + implementation(project(":core:work")) implementation(project(":feature:auth")) implementation(project(":feature:account")) diff --git a/app/src/main/kotlin/org/michaelbel/movies/MainActivity.kt b/app/src/main/kotlin/org/michaelbel/movies/MainActivity.kt index 6e6ab0ce0..ba17e9ae8 100644 --- a/app/src/main/kotlin/org/michaelbel/movies/MainActivity.kt +++ b/app/src/main/kotlin/org/michaelbel/movies/MainActivity.kt @@ -10,8 +10,13 @@ import androidx.core.view.WindowCompat import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavHostController import androidx.navigation.compose.rememberNavController +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.WorkManager +import androidx.work.workDataOf import dagger.hilt.android.AndroidEntryPoint +import org.michaelbel.movies.work.AccountUpdateWorker import org.michaelbel.movies.common.theme.AppTheme +import org.michaelbel.movies.work.MoviesDatabaseWorker import org.michaelbel.movies.navigation.ktx.addOnDestinationChangedListener import org.michaelbel.movies.ui.shortcuts.installShortcuts import org.michaelbel.movies.ui.theme.MoviesTheme @@ -50,5 +55,21 @@ internal class MainActivity: AppCompatActivity() { ) } } + + prepopulateDatabase() + updateAccountDetails() + } + + private fun prepopulateDatabase() { + val request = OneTimeWorkRequestBuilder() + .setInputData(workDataOf(MoviesDatabaseWorker.KEY_FILENAME to MainViewModel.MOVIES_DATA_FILENAME)) + .build() + WorkManager.getInstance(this).enqueue(request) + } + + private fun updateAccountDetails() { + val request = OneTimeWorkRequestBuilder() + .build() + WorkManager.getInstance(this).enqueue(request) } } \ No newline at end of file diff --git a/app/src/main/kotlin/org/michaelbel/movies/MainViewModel.kt b/app/src/main/kotlin/org/michaelbel/movies/MainViewModel.kt index ed107ce1a..0b3c4bda9 100644 --- a/app/src/main/kotlin/org/michaelbel/movies/MainViewModel.kt +++ b/app/src/main/kotlin/org/michaelbel/movies/MainViewModel.kt @@ -3,11 +3,9 @@ package org.michaelbel.movies import android.app.Activity import android.os.Bundle import androidx.navigation.NavDestination -import androidx.work.OneTimeWorkRequestBuilder -import androidx.work.WorkManager -import androidx.work.workDataOf import com.google.firebase.messaging.FirebaseMessaging import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.stateIn @@ -17,16 +15,12 @@ import org.michaelbel.movies.common.inappupdate.di.InAppUpdate import org.michaelbel.movies.common.ktx.printlnDebug import org.michaelbel.movies.common.theme.AppTheme import org.michaelbel.movies.common.viewmodel.BaseViewModel -import org.michaelbel.movies.domain.workers.AccountUpdateWorker -import org.michaelbel.movies.domain.workers.MoviesDatabaseWorker import org.michaelbel.movies.interactor.Interactor -import javax.inject.Inject @HiltViewModel internal class MainViewModel @Inject constructor( private val interactor: Interactor, private val inAppUpdate: InAppUpdate, - private val workManager: WorkManager, private val analytics: MoviesAnalytics, private val firebaseMessaging: FirebaseMessaging ): BaseViewModel() { @@ -48,8 +42,6 @@ internal class MainViewModel @Inject constructor( init { fetchRemoteConfig() fetchFirebaseMessagingToken() - prepopulateDatabase() - updateAccountDetails() } fun analyticsTrackDestination(destination: NavDestination, arguments: Bundle?) { @@ -71,20 +63,7 @@ internal class MainViewModel @Inject constructor( } } - private fun prepopulateDatabase() { - val request = OneTimeWorkRequestBuilder() - .setInputData(workDataOf(MoviesDatabaseWorker.KEY_FILENAME to MOVIES_DATA_FILENAME)) - .build() - workManager.enqueue(request) - } - - private fun updateAccountDetails() { - val request = OneTimeWorkRequestBuilder() - .build() - workManager.enqueue(request) - } - - private companion object { - private const val MOVIES_DATA_FILENAME = "movies.json" + companion object { + const val MOVIES_DATA_FILENAME = "movies.json" } } \ No newline at end of file diff --git a/benchmark/build.gradle.kts b/benchmark/build.gradle.kts index fa9a2ede9..2ec3e47cb 100644 --- a/benchmark/build.gradle.kts +++ b/benchmark/build.gradle.kts @@ -2,7 +2,6 @@ plugins { id("com.android.test") id("org.jetbrains.kotlin.android") id("kotlin-android") - alias(libs.plugins.detekt) } android { diff --git a/build.gradle.kts b/build.gradle.kts index 2ab15ee04..609779c24 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,4 +18,8 @@ plugins { detekt { config.setFrom("$projectDir/config/detekt/detekt.yml") +} + +subprojects { + apply(plugin = "io.gitlab.arturbosch.detekt") } \ No newline at end of file diff --git a/core/analytics/build.gradle.kts b/core/analytics/build.gradle.kts index 249203833..a88836e25 100644 --- a/core/analytics/build.gradle.kts +++ b/core/analytics/build.gradle.kts @@ -2,7 +2,6 @@ plugins { alias(libs.plugins.library) alias(libs.plugins.kotlin) - alias(libs.plugins.detekt) id("movies-android-hilt") } @@ -34,6 +33,10 @@ android { checkDependencies = true lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") } + + kapt { + correctErrorTypes = true + } } dependencies { diff --git a/core/common/build.gradle.kts b/core/common/build.gradle.kts index a04e86799..b29d56b1e 100644 --- a/core/common/build.gradle.kts +++ b/core/common/build.gradle.kts @@ -2,7 +2,6 @@ plugins { alias(libs.plugins.library) alias(libs.plugins.kotlin) - alias(libs.plugins.detekt) id("movies-android-hilt") } @@ -43,11 +42,15 @@ android { checkDependencies = true lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") } + + kapt { + correctErrorTypes = true + } } dependencies { implementation(project(":core:analytics")) - api(project(":core:entities")) + implementation(project(":core:network")) api(libs.bundles.kotlin.coroutines) api(libs.firebase.config.ktx) api(libs.gms.play.services.base) @@ -55,6 +58,8 @@ dependencies { api(libs.androidx.activity.compose) api(libs.androidx.core.ktx) api(libs.androidx.paging.compose) + api(libs.androidx.work.runtime.ktx) + api(libs.androidx.hilt.work) api(libs.bundles.lifecycle) api(libs.timber) implementation(libs.bundles.appcompat) diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/usecase/UseCase.kt b/core/common/src/main/kotlin/org/michaelbel/movies/common/usecase/UseCase.kt index c81f74d79..1e37f4959 100644 --- a/core/common/src/main/kotlin/org/michaelbel/movies/common/usecase/UseCase.kt +++ b/core/common/src/main/kotlin/org/michaelbel/movies/common/usecase/UseCase.kt @@ -2,7 +2,7 @@ package org.michaelbel.movies.common.usecase import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.withContext -import org.michaelbel.movies.entities.Either +import org.michaelbel.movies.network.Either import timber.log.Timber abstract class UseCase( diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/version/AppVersionData.kt b/core/common/src/main/kotlin/org/michaelbel/movies/common/version/AppVersionData.kt index f2061d6cd..16af2550f 100644 --- a/core/common/src/main/kotlin/org/michaelbel/movies/common/version/AppVersionData.kt +++ b/core/common/src/main/kotlin/org/michaelbel/movies/common/version/AppVersionData.kt @@ -6,7 +6,7 @@ data class AppVersionData( val isDebug: Boolean ) { companion object { - val None: AppVersionData = AppVersionData( + val Empty: AppVersionData = AppVersionData( version = "", code = 0L, isDebug = false diff --git a/core/domain/src/main/kotlin/org/michaelbel/movies/domain/workers/di/WorkManagerModule.kt b/core/domain/src/main/kotlin/org/michaelbel/movies/domain/workers/di/WorkManagerModule.kt deleted file mode 100644 index c7b5fb4e3..000000000 --- a/core/domain/src/main/kotlin/org/michaelbel/movies/domain/workers/di/WorkManagerModule.kt +++ /dev/null @@ -1,21 +0,0 @@ -package org.michaelbel.movies.domain.workers.di - -import android.content.Context -import androidx.work.WorkManager -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -internal object WorkManagerModule { - - @Provides - @Singleton - fun provideWorkManager( - @ApplicationContext context: Context - ): WorkManager = WorkManager.getInstance(context) -} \ No newline at end of file diff --git a/core/entities/.gitignore b/core/entities/.gitignore deleted file mode 100644 index 42afabfd2..000000000 --- a/core/entities/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/core/entities/build.gradle.kts b/core/entities/build.gradle.kts deleted file mode 100644 index e6f270f0c..000000000 --- a/core/entities/build.gradle.kts +++ /dev/null @@ -1,50 +0,0 @@ -import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties - -@Suppress("dsl_scope_violation") -plugins { - alias(libs.plugins.library) - alias(libs.plugins.kotlin) - alias(libs.plugins.detekt) - id("movies-android-hilt") -} - -val tmdbApiKey: String by lazy { - gradleLocalProperties(rootDir).getProperty("TMDB_API_KEY").orEmpty().ifEmpty { - System.getenv("TMDB_API_KEY").orEmpty() - } -} - -android { - namespace = "org.michaelbel.movies.entities" - - defaultConfig { - minSdk = libs.versions.min.sdk.get().toInt() - compileSdk = libs.versions.compile.sdk.get().toInt() - buildConfigField("String", "TMDB_API_KEY", "\"$tmdbApiKey\"") - } - - /*buildTypes { - create("benchmark") { - signingConfig = signingConfigs.getByName("debug") - matchingFallbacks += listOf("release") - initWith(getByName("release")) - } - }*/ - - buildFeatures { - buildConfig = true - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - lint { - quiet = true - abortOnError = false - ignoreWarnings = true - checkDependencies = true - lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") - } -} \ No newline at end of file diff --git a/core/entities/src/main/AndroidManifest.xml b/core/entities/src/main/AndroidManifest.xml deleted file mode 100644 index 1d26c87a1..000000000 --- a/core/entities/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/core/interactor-impl/build.gradle.kts b/core/interactor-impl/build.gradle.kts index 5342207f9..ab5f1ecf9 100644 --- a/core/interactor-impl/build.gradle.kts +++ b/core/interactor-impl/build.gradle.kts @@ -2,7 +2,6 @@ plugins { alias(libs.plugins.library) alias(libs.plugins.kotlin) - alias(libs.plugins.detekt) id("movies-android-hilt") } @@ -34,6 +33,10 @@ android { checkDependencies = true lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") } + + kapt { + correctErrorTypes = true + } } dependencies { diff --git a/core/interactor-impl/src/main/kotlin/org/michaelbel/movies/interactor/ImageInteractorImpl.kt b/core/interactor-impl/src/main/kotlin/org/michaelbel/movies/interactor/ImageInteractorImpl.kt new file mode 100644 index 000000000..92982c1b6 --- /dev/null +++ b/core/interactor-impl/src/main/kotlin/org/michaelbel/movies/interactor/ImageInteractorImpl.kt @@ -0,0 +1,26 @@ +package org.michaelbel.movies.interactor + +import javax.inject.Inject +import javax.inject.Singleton +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.withContext +import org.michaelbel.movies.common.dispatchers.MoviesDispatchers +import org.michaelbel.movies.persistence.database.entity.ImageDb +import org.michaelbel.movies.repository.ImageRepository + +@Singleton +internal class ImageInteractorImpl @Inject constructor( + private val dispatchers: MoviesDispatchers, + private val imageRepository: ImageRepository +): ImageInteractor { + + override fun imagesFlow(movieId: Int): Flow> { + return imageRepository.imagesFlow(movieId) + } + + override suspend fun images(movieId: Int) { + return withContext(dispatchers.io) { + imageRepository.images(movieId) + } + } +} \ No newline at end of file diff --git a/core/interactor-impl/src/main/kotlin/org/michaelbel/movies/interactor/MovieInteractorImpl.kt b/core/interactor-impl/src/main/kotlin/org/michaelbel/movies/interactor/MovieInteractorImpl.kt index d1772074d..1c2115c4c 100644 --- a/core/interactor-impl/src/main/kotlin/org/michaelbel/movies/interactor/MovieInteractorImpl.kt +++ b/core/interactor-impl/src/main/kotlin/org/michaelbel/movies/interactor/MovieInteractorImpl.kt @@ -4,12 +4,10 @@ import androidx.paging.PagingSource import javax.inject.Inject import javax.inject.Singleton import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.withContext import org.michaelbel.movies.common.dispatchers.MoviesDispatchers -import org.michaelbel.movies.entities.Either import org.michaelbel.movies.interactor.usecase.DelayUseCase -import org.michaelbel.movies.network.model.ImagesResponse +import org.michaelbel.movies.network.Either import org.michaelbel.movies.network.model.MovieResponse import org.michaelbel.movies.network.model.Result import org.michaelbel.movies.persistence.database.entity.MovieDb @@ -26,8 +24,8 @@ internal class MovieInteractorImpl @Inject constructor( return movieRepository.moviesPagingSource(movieList) } - override fun movieImage(movieId: Int): Flow { - return movieRepository.movieImage(movieId) + override suspend fun backdropPosition(movieId: Int): Int { + return movieRepository.backdropPosition(movieId) } override suspend fun moviesResult(movieList: String, page: Int): Result { @@ -46,12 +44,6 @@ internal class MovieInteractorImpl @Inject constructor( } } - override suspend fun movieImages(movieId: Int): ImagesResponse { - return withContext(dispatchers.io) { - movieRepository.movieImages(movieId) - } - } - override suspend fun removeAllMovies(movieList: String) { return withContext(dispatchers.io) { movieRepository.removeAllMovies(movieList) diff --git a/core/interactor-impl/src/main/kotlin/org/michaelbel/movies/interactor/di/InteractorModule.kt b/core/interactor-impl/src/main/kotlin/org/michaelbel/movies/interactor/di/InteractorModule.kt index bae511499..2308025fd 100644 --- a/core/interactor-impl/src/main/kotlin/org/michaelbel/movies/interactor/di/InteractorModule.kt +++ b/core/interactor-impl/src/main/kotlin/org/michaelbel/movies/interactor/di/InteractorModule.kt @@ -4,15 +4,17 @@ import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton import org.michaelbel.movies.interactor.AccountInteractor import org.michaelbel.movies.interactor.AccountInteractorImpl import org.michaelbel.movies.interactor.AuthenticationInteractor import org.michaelbel.movies.interactor.AuthenticationInteractorImpl +import org.michaelbel.movies.interactor.ImageInteractor +import org.michaelbel.movies.interactor.ImageInteractorImpl import org.michaelbel.movies.interactor.MovieInteractor import org.michaelbel.movies.interactor.MovieInteractorImpl import org.michaelbel.movies.interactor.SettingsInteractor import org.michaelbel.movies.interactor.SettingsInteractorImpl -import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) @@ -24,6 +26,12 @@ internal interface InteractorModule { interactor: MovieInteractorImpl ): MovieInteractor + @Binds + @Singleton + fun provideImageInteractor( + interactor: ImageInteractorImpl + ): ImageInteractor + @Binds @Singleton fun provideAuthenticationInteractor( diff --git a/core/interactor/build.gradle.kts b/core/interactor/build.gradle.kts index e831f8927..afd7817c5 100644 --- a/core/interactor/build.gradle.kts +++ b/core/interactor/build.gradle.kts @@ -2,7 +2,6 @@ plugins { alias(libs.plugins.library) alias(libs.plugins.kotlin) - alias(libs.plugins.detekt) id("movies-android-hilt") } @@ -34,13 +33,16 @@ android { checkDependencies = true lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") } + + kapt { + correctErrorTypes = true + } } dependencies { + implementation(project(":core:network")) api(project(":core:analytics")) api(project(":core:common")) - api(project(":core:network")) api(project(":core:persistence")) api(project(":core:repository")) - implementation(project(":core:entities")) } \ No newline at end of file diff --git a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/ImageInteractor.kt b/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/ImageInteractor.kt new file mode 100644 index 000000000..104a26771 --- /dev/null +++ b/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/ImageInteractor.kt @@ -0,0 +1,11 @@ +package org.michaelbel.movies.interactor + +import kotlinx.coroutines.flow.Flow +import org.michaelbel.movies.persistence.database.entity.ImageDb + +interface ImageInteractor { + + fun imagesFlow(movieId: Int): Flow> + + suspend fun images(movieId: Int) +} \ No newline at end of file diff --git a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/Interactor.kt b/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/Interactor.kt index 12d5b864c..4a629b300 100644 --- a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/Interactor.kt +++ b/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/Interactor.kt @@ -5,9 +5,11 @@ import javax.inject.Inject class Interactor @Inject constructor( accountInteractor: AccountInteractor, authenticationInteractor: AuthenticationInteractor, + imageInteractor: ImageInteractor, movieInteractor: MovieInteractor, settingsInteractor: SettingsInteractor ): AccountInteractor by accountInteractor, AuthenticationInteractor by authenticationInteractor, + ImageInteractor by imageInteractor, MovieInteractor by movieInteractor, SettingsInteractor by settingsInteractor \ No newline at end of file diff --git a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/MovieInteractor.kt b/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/MovieInteractor.kt index 31db08f27..355ddd786 100644 --- a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/MovieInteractor.kt +++ b/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/MovieInteractor.kt @@ -1,9 +1,7 @@ package org.michaelbel.movies.interactor import androidx.paging.PagingSource -import kotlinx.coroutines.flow.Flow -import org.michaelbel.movies.entities.Either -import org.michaelbel.movies.network.model.ImagesResponse +import org.michaelbel.movies.network.Either import org.michaelbel.movies.network.model.MovieResponse import org.michaelbel.movies.network.model.Result import org.michaelbel.movies.persistence.database.entity.MovieDb @@ -12,14 +10,12 @@ interface MovieInteractor { fun moviesPagingSource(movieList: String): PagingSource - fun movieImage(movieId: Int): Flow + suspend fun backdropPosition(movieId: Int): Int suspend fun moviesResult(movieList: String, page: Int): Result suspend fun movieDetails(movieId: Int): Either - suspend fun movieImages(movieId: Int): ImagesResponse - suspend fun removeAllMovies(movieList: String) suspend fun insertAllMovies(movieList: String, movies: List) diff --git a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/usecase/MovieDetailsCase.kt b/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/usecase/MovieDetailsCase.kt index d065e42e1..d5e10f5e2 100644 --- a/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/usecase/MovieDetailsCase.kt +++ b/core/interactor/src/main/kotlin/org/michaelbel/movies/interactor/usecase/MovieDetailsCase.kt @@ -1,15 +1,18 @@ package org.michaelbel.movies.interactor.usecase -import org.michaelbel.movies.entities.handle -import org.michaelbel.movies.entities.lce.ScreenState -import org.michaelbel.movies.interactor.Interactor import javax.inject.Inject +import org.michaelbel.movies.interactor.Interactor +import org.michaelbel.movies.network.Either +import org.michaelbel.movies.network.ScreenState +import org.michaelbel.movies.network.handle +import org.michaelbel.movies.persistence.database.entity.MovieDb class MovieDetailsCase @Inject constructor( private val interactor: Interactor ) { suspend operator fun invoke(movieId: Int): ScreenState { - interactor.movieDetails(movieId).handle( + val movieDetails: Either = interactor.movieDetails(movieId) + movieDetails.handle( success = { movieDetailsData -> return ScreenState.Content(movieDetailsData) }, diff --git a/core/navigation/build.gradle.kts b/core/navigation/build.gradle.kts index 22ae83b00..9a6d6b17a 100644 --- a/core/navigation/build.gradle.kts +++ b/core/navigation/build.gradle.kts @@ -2,7 +2,6 @@ plugins { alias(libs.plugins.library) alias(libs.plugins.kotlin) - alias(libs.plugins.detekt) } android { diff --git a/core/network/build.gradle.kts b/core/network/build.gradle.kts index 085c08be3..a3609143e 100644 --- a/core/network/build.gradle.kts +++ b/core/network/build.gradle.kts @@ -1,18 +1,26 @@ +import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties + @Suppress("dsl_scope_violation") plugins { alias(libs.plugins.library) alias(libs.plugins.kotlin) alias(libs.plugins.kotlin.serialization) - alias(libs.plugins.detekt) id("movies-android-hilt") } +val tmdbApiKey: String by lazy { + gradleLocalProperties(rootDir).getProperty("TMDB_API_KEY").orEmpty().ifEmpty { + System.getenv("TMDB_API_KEY").orEmpty() + } +} + android { namespace = "org.michaelbel.movies.network" defaultConfig { minSdk = libs.versions.min.sdk.get().toInt() compileSdk = libs.versions.compile.sdk.get().toInt() + buildConfigField("String", "TMDB_API_KEY", "\"$tmdbApiKey\"") } /*buildTypes { @@ -39,6 +47,10 @@ android { checkDependencies = true lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") } + + kapt { + correctErrorTypes = true + } } dependencies { diff --git a/core/entities/src/main/kotlin/org/michaelbel/movies/entities/Either.kt b/core/network/src/main/kotlin/org/michaelbel/movies/network/Either.kt similarity index 99% rename from core/entities/src/main/kotlin/org/michaelbel/movies/entities/Either.kt rename to core/network/src/main/kotlin/org/michaelbel/movies/network/Either.kt index 25a51f916..d93051de3 100644 --- a/core/entities/src/main/kotlin/org/michaelbel/movies/entities/Either.kt +++ b/core/network/src/main/kotlin/org/michaelbel/movies/network/Either.kt @@ -1,6 +1,6 @@ @file:Suppress("unused") -package org.michaelbel.movies.entities +package org.michaelbel.movies.network /** * A class that encapsulates a successful result with a value of type T diff --git a/core/entities/src/main/kotlin/org/michaelbel/movies/entities/GravatarConfig.kt b/core/network/src/main/kotlin/org/michaelbel/movies/network/GravatarConfig.kt similarity index 61% rename from core/entities/src/main/kotlin/org/michaelbel/movies/entities/GravatarConfig.kt rename to core/network/src/main/kotlin/org/michaelbel/movies/network/GravatarConfig.kt index c1ec50664..86fff4a24 100644 --- a/core/entities/src/main/kotlin/org/michaelbel/movies/entities/GravatarConfig.kt +++ b/core/network/src/main/kotlin/org/michaelbel/movies/network/GravatarConfig.kt @@ -1,3 +1,3 @@ -package org.michaelbel.movies.entities +package org.michaelbel.movies.network const val GRAVATAR_URL = "https://www.gravatar.com/avatar/%s" \ No newline at end of file diff --git a/core/entities/src/main/kotlin/org/michaelbel/movies/entities/Response.kt b/core/network/src/main/kotlin/org/michaelbel/movies/network/Response.kt similarity index 83% rename from core/entities/src/main/kotlin/org/michaelbel/movies/entities/Response.kt rename to core/network/src/main/kotlin/org/michaelbel/movies/network/Response.kt index ed6a81848..74375ab8a 100644 --- a/core/entities/src/main/kotlin/org/michaelbel/movies/entities/Response.kt +++ b/core/network/src/main/kotlin/org/michaelbel/movies/network/Response.kt @@ -1,4 +1,4 @@ -package org.michaelbel.movies.entities +package org.michaelbel.movies.network suspend fun response( request: suspend () -> T diff --git a/core/entities/src/main/kotlin/org/michaelbel/movies/entities/lce/ScreenState.kt b/core/network/src/main/kotlin/org/michaelbel/movies/network/ScreenState.kt similarity index 81% rename from core/entities/src/main/kotlin/org/michaelbel/movies/entities/lce/ScreenState.kt rename to core/network/src/main/kotlin/org/michaelbel/movies/network/ScreenState.kt index c276cfcc6..e1566d46a 100644 --- a/core/entities/src/main/kotlin/org/michaelbel/movies/entities/lce/ScreenState.kt +++ b/core/network/src/main/kotlin/org/michaelbel/movies/network/ScreenState.kt @@ -1,4 +1,4 @@ -package org.michaelbel.movies.entities.lce +package org.michaelbel.movies.network sealed interface ScreenState { diff --git a/core/entities/src/main/kotlin/org/michaelbel/movies/entities/TmdbConfig.kt b/core/network/src/main/kotlin/org/michaelbel/movies/network/TmdbConfig.kt similarity index 93% rename from core/entities/src/main/kotlin/org/michaelbel/movies/entities/TmdbConfig.kt rename to core/network/src/main/kotlin/org/michaelbel/movies/network/TmdbConfig.kt index 61c9a0388..2476793a6 100644 --- a/core/entities/src/main/kotlin/org/michaelbel/movies/entities/TmdbConfig.kt +++ b/core/network/src/main/kotlin/org/michaelbel/movies/network/TmdbConfig.kt @@ -1,4 +1,4 @@ -package org.michaelbel.movies.entities +package org.michaelbel.movies.network const val TMDB_URL = "https://themoviedb.org" const val TMDB_TERMS_OF_USE = "https://themoviedb.org/documentation/website/terms-of-use" diff --git a/core/entities/src/main/kotlin/org/michaelbel/movies/entities/image/TmdbImage.kt b/core/network/src/main/kotlin/org/michaelbel/movies/network/TmdbImage.kt similarity index 69% rename from core/entities/src/main/kotlin/org/michaelbel/movies/entities/image/TmdbImage.kt rename to core/network/src/main/kotlin/org/michaelbel/movies/network/TmdbImage.kt index ffb16e22c..be830ad9a 100644 --- a/core/entities/src/main/kotlin/org/michaelbel/movies/entities/image/TmdbImage.kt +++ b/core/network/src/main/kotlin/org/michaelbel/movies/network/TmdbImage.kt @@ -1,4 +1,8 @@ -package org.michaelbel.movies.entities.image +package org.michaelbel.movies.network + +import org.michaelbel.movies.network.model.image.BackdropSize +import org.michaelbel.movies.network.model.image.PosterSize +import org.michaelbel.movies.network.model.image.ProfileSize /** * See [TMDB Image Basics](https://developer.themoviedb.org/docs/image-basics) @@ -17,6 +21,7 @@ val String.formatBackdropImage: String val String.formatProfileImage: String get() = String.format(TMDB_IMAGE_BASE_URL, ProfileSize.W185.size, this).ifEmpty { IMAGE_EMPTY_URL } +@Suppress("unused") val String.original: String get() { return when { @@ -26,4 +31,11 @@ val String.original: String } else -> this } - } \ No newline at end of file + } + +val String.isNotOriginal: Boolean + get() = !contains("original".toRegex()) + +fun String.formatImage(size: String): String { + return String.format(TMDB_IMAGE_BASE_URL, size, this).ifEmpty { IMAGE_EMPTY_URL } +} \ No newline at end of file diff --git a/core/entities/src/main/kotlin/org/michaelbel/movies/entities/lce/ktx/ScreenStateKtx.kt b/core/network/src/main/kotlin/org/michaelbel/movies/network/ktx/ScreenStateKtx.kt similarity index 62% rename from core/entities/src/main/kotlin/org/michaelbel/movies/entities/lce/ktx/ScreenStateKtx.kt rename to core/network/src/main/kotlin/org/michaelbel/movies/network/ktx/ScreenStateKtx.kt index e24771331..777937c7c 100644 --- a/core/entities/src/main/kotlin/org/michaelbel/movies/entities/lce/ktx/ScreenStateKtx.kt +++ b/core/network/src/main/kotlin/org/michaelbel/movies/network/ktx/ScreenStateKtx.kt @@ -1,6 +1,6 @@ -package org.michaelbel.movies.entities.lce.ktx +package org.michaelbel.movies.network.ktx -import org.michaelbel.movies.entities.lce.ScreenState +import org.michaelbel.movies.network.ScreenState val ScreenState.isFailure: Boolean get() = this is ScreenState.Failure diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/ImagesResponse.kt b/core/network/src/main/kotlin/org/michaelbel/movies/network/model/ImagesResponse.kt index 760aad0b2..05af803fb 100644 --- a/core/network/src/main/kotlin/org/michaelbel/movies/network/model/ImagesResponse.kt +++ b/core/network/src/main/kotlin/org/michaelbel/movies/network/model/ImagesResponse.kt @@ -7,6 +7,6 @@ import kotlinx.serialization.Serializable data class ImagesResponse( @SerialName("id") val id: Int, @SerialName("backdrops") val backdrops: List, - @SerialName("posters") val crew: List, + @SerialName("posters") val posters: List, @SerialName("logos") val logos: List ) \ No newline at end of file diff --git a/core/entities/src/main/kotlin/org/michaelbel/movies/entities/image/BackdropSize.kt b/core/network/src/main/kotlin/org/michaelbel/movies/network/model/image/BackdropSize.kt similarity index 74% rename from core/entities/src/main/kotlin/org/michaelbel/movies/entities/image/BackdropSize.kt rename to core/network/src/main/kotlin/org/michaelbel/movies/network/model/image/BackdropSize.kt index 417bc2bc7..601819c15 100644 --- a/core/entities/src/main/kotlin/org/michaelbel/movies/entities/image/BackdropSize.kt +++ b/core/network/src/main/kotlin/org/michaelbel/movies/network/model/image/BackdropSize.kt @@ -1,4 +1,4 @@ -package org.michaelbel.movies.entities.image +package org.michaelbel.movies.network.model.image @Suppress("unused") enum class BackdropSize(val size: String) { diff --git a/core/entities/src/main/kotlin/org/michaelbel/movies/entities/image/LogoSize.kt b/core/network/src/main/kotlin/org/michaelbel/movies/network/model/image/LogoSize.kt similarity index 79% rename from core/entities/src/main/kotlin/org/michaelbel/movies/entities/image/LogoSize.kt rename to core/network/src/main/kotlin/org/michaelbel/movies/network/model/image/LogoSize.kt index 08928c4e7..8354951d7 100644 --- a/core/entities/src/main/kotlin/org/michaelbel/movies/entities/image/LogoSize.kt +++ b/core/network/src/main/kotlin/org/michaelbel/movies/network/model/image/LogoSize.kt @@ -1,4 +1,4 @@ -package org.michaelbel.movies.entities.image +package org.michaelbel.movies.network.model.image @Suppress("unused") enum class LogoSize(val size: String) { diff --git a/core/entities/src/main/kotlin/org/michaelbel/movies/entities/image/PosterSize.kt b/core/network/src/main/kotlin/org/michaelbel/movies/network/model/image/PosterSize.kt similarity index 79% rename from core/entities/src/main/kotlin/org/michaelbel/movies/entities/image/PosterSize.kt rename to core/network/src/main/kotlin/org/michaelbel/movies/network/model/image/PosterSize.kt index 39f2dcefe..bd4b9ace1 100644 --- a/core/entities/src/main/kotlin/org/michaelbel/movies/entities/image/PosterSize.kt +++ b/core/network/src/main/kotlin/org/michaelbel/movies/network/model/image/PosterSize.kt @@ -1,4 +1,4 @@ -package org.michaelbel.movies.entities.image +package org.michaelbel.movies.network.model.image @Suppress("unused") enum class PosterSize(val size: String) { diff --git a/core/entities/src/main/kotlin/org/michaelbel/movies/entities/image/ProfileSize.kt b/core/network/src/main/kotlin/org/michaelbel/movies/network/model/image/ProfileSize.kt similarity index 74% rename from core/entities/src/main/kotlin/org/michaelbel/movies/entities/image/ProfileSize.kt rename to core/network/src/main/kotlin/org/michaelbel/movies/network/model/image/ProfileSize.kt index b38fce0ad..0668aea9e 100644 --- a/core/entities/src/main/kotlin/org/michaelbel/movies/entities/image/ProfileSize.kt +++ b/core/network/src/main/kotlin/org/michaelbel/movies/network/model/image/ProfileSize.kt @@ -1,4 +1,4 @@ -package org.michaelbel.movies.entities.image +package org.michaelbel.movies.network.model.image @Suppress("unused") enum class ProfileSize(val size: String) { diff --git a/core/entities/src/main/kotlin/org/michaelbel/movies/entities/image/StillSize.kt b/core/network/src/main/kotlin/org/michaelbel/movies/network/model/image/StillSize.kt similarity index 73% rename from core/entities/src/main/kotlin/org/michaelbel/movies/entities/image/StillSize.kt rename to core/network/src/main/kotlin/org/michaelbel/movies/network/model/image/StillSize.kt index e671832e1..b4b2bffee 100644 --- a/core/entities/src/main/kotlin/org/michaelbel/movies/entities/image/StillSize.kt +++ b/core/network/src/main/kotlin/org/michaelbel/movies/network/model/image/StillSize.kt @@ -1,4 +1,4 @@ -package org.michaelbel.movies.entities.image +package org.michaelbel.movies.network.model.image @Suppress("unused") enum class StillSize(val size: String) { diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/service/di/ServiceModule.kt b/core/network/src/main/kotlin/org/michaelbel/movies/network/service/di/ServiceModule.kt index aabf37b0a..48091e385 100644 --- a/core/network/src/main/kotlin/org/michaelbel/movies/network/service/di/ServiceModule.kt +++ b/core/network/src/main/kotlin/org/michaelbel/movies/network/service/di/ServiceModule.kt @@ -4,12 +4,13 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton import org.michaelbel.movies.network.service.account.AccountService import org.michaelbel.movies.network.service.authentication.AuthenticationService +import org.michaelbel.movies.network.service.image.ImageService import org.michaelbel.movies.network.service.ktx.createService import org.michaelbel.movies.network.service.movie.MovieService import retrofit2.Retrofit -import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) @@ -21,6 +22,12 @@ internal object ServiceModule { retrofit: Retrofit ): MovieService = retrofit.createService() + @Provides + @Singleton + fun provideImageService( + retrofit: Retrofit + ): ImageService = retrofit.createService() + @Provides @Singleton fun provideAuthenticationService( diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/service/image/ImageService.kt b/core/network/src/main/kotlin/org/michaelbel/movies/network/service/image/ImageService.kt new file mode 100644 index 000000000..70eea2b8e --- /dev/null +++ b/core/network/src/main/kotlin/org/michaelbel/movies/network/service/image/ImageService.kt @@ -0,0 +1,15 @@ +package org.michaelbel.movies.network.service.image + +import org.michaelbel.movies.network.model.ImagesResponse +import retrofit2.http.GET +import retrofit2.http.Path +import retrofit2.http.Query + +interface ImageService { + + @GET("movie/{movie_id}/images") + suspend fun images( + @Path("movie_id") id: Int, + @Query("api_key") apiKey: String, + ): ImagesResponse +} \ No newline at end of file diff --git a/core/notifications/build.gradle.kts b/core/notifications/build.gradle.kts index f7c2af955..9b99639b7 100644 --- a/core/notifications/build.gradle.kts +++ b/core/notifications/build.gradle.kts @@ -2,7 +2,6 @@ plugins { alias(libs.plugins.library) alias(libs.plugins.kotlin) - alias(libs.plugins.detekt) id("movies-android-hilt") } @@ -34,6 +33,10 @@ android { checkDependencies = true lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") } + + kapt { + correctErrorTypes = true + } } dependencies { diff --git a/core/persistence/build.gradle.kts b/core/persistence/build.gradle.kts index 17cf3065f..858007a5b 100644 --- a/core/persistence/build.gradle.kts +++ b/core/persistence/build.gradle.kts @@ -3,7 +3,6 @@ plugins { alias(libs.plugins.library) alias(libs.plugins.kotlin) alias(libs.plugins.kotlin.ksp) - alias(libs.plugins.detekt) id("movies-android-hilt") } @@ -23,6 +22,10 @@ android { } }*/ + buildFeatures { + buildConfig = true + } + compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 @@ -35,10 +38,14 @@ android { checkDependencies = true lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") } + + kapt { + correctErrorTypes = true + } } dependencies { - implementation(project(":core:entities")) + implementation(project(":core:network")) implementation(libs.bundles.datastore) implementation(libs.bundles.room) ksp(libs.androidx.room.compiler) diff --git a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/AppDatabase.kt b/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/AppDatabase.kt index c47de46b9..364cf8110 100644 --- a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/AppDatabase.kt +++ b/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/AppDatabase.kt @@ -5,12 +5,14 @@ import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase import androidx.room.TypeConverters -import org.michaelbel.movies.entities.BuildConfig +import org.michaelbel.movies.persistence.BuildConfig import org.michaelbel.movies.persistence.database.converter.CalendarConverter import org.michaelbel.movies.persistence.database.dao.AccountDao +import org.michaelbel.movies.persistence.database.dao.ImageDao import org.michaelbel.movies.persistence.database.dao.MovieDao import org.michaelbel.movies.persistence.database.dao.PagingKeyDao import org.michaelbel.movies.persistence.database.entity.AccountDb +import org.michaelbel.movies.persistence.database.entity.ImageDb import org.michaelbel.movies.persistence.database.entity.MovieDb import org.michaelbel.movies.persistence.database.entity.PagingKeyDb @@ -20,6 +22,7 @@ import org.michaelbel.movies.persistence.database.entity.PagingKeyDb @Database( entities = [ MovieDb::class, + ImageDb::class, AccountDb::class, PagingKeyDb::class ], @@ -30,12 +33,13 @@ import org.michaelbel.movies.persistence.database.entity.PagingKeyDb internal abstract class AppDatabase: RoomDatabase() { abstract fun movieDao(): MovieDao + abstract fun imageDao(): ImageDao abstract fun accountDao(): AccountDao abstract fun pagingKeyDao(): PagingKeyDao companion object { private val DATABASE_NAME: String = if (BuildConfig.DEBUG) "movies-db-debug" else "movies-db" - const val DATABASE_VERSION = 15 + const val DATABASE_VERSION = 16 @Volatile private var instance: AppDatabase? = null diff --git a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/dao/ImageDao.kt b/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/dao/ImageDao.kt new file mode 100644 index 000000000..6c144242a --- /dev/null +++ b/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/dao/ImageDao.kt @@ -0,0 +1,21 @@ +package org.michaelbel.movies.persistence.database.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import kotlinx.coroutines.flow.Flow +import org.michaelbel.movies.persistence.database.entity.ImageDb + +/** + * The Data Access Object for the [ImageDb] class. + */ +@Dao +interface ImageDao { + + @Query("SELECT * FROM images WHERE movieId = :movieId") + fun imagesFlow(movieId: Int): Flow> + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(images: List) +} \ No newline at end of file diff --git a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/dao/MovieDao.kt b/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/dao/MovieDao.kt index 96de4866c..3c36d17c7 100644 --- a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/dao/MovieDao.kt +++ b/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/dao/MovieDao.kt @@ -5,7 +5,6 @@ import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query -import kotlinx.coroutines.flow.Flow import org.michaelbel.movies.persistence.database.entity.MovieDb /** @@ -18,7 +17,7 @@ interface MovieDao { fun pagingSource(movieList: String): PagingSource @Query("SELECT backdropPath FROM movies WHERE id = :movieId") - fun movieImage(movieId: Int): Flow + suspend fun movieBackdropPath(movieId: Int): String @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertAllMovies(movies: List) diff --git a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/di/DatabaseModule.kt b/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/di/DatabaseModule.kt index 18a011faa..634e60634 100644 --- a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/di/DatabaseModule.kt +++ b/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/di/DatabaseModule.kt @@ -6,11 +6,12 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton import org.michaelbel.movies.persistence.database.AppDatabase import org.michaelbel.movies.persistence.database.dao.AccountDao +import org.michaelbel.movies.persistence.database.dao.ImageDao import org.michaelbel.movies.persistence.database.dao.MovieDao import org.michaelbel.movies.persistence.database.dao.PagingKeyDao -import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) @@ -25,6 +26,9 @@ internal object DatabaseModule { @Provides fun provideMovieDao(appDatabase: AppDatabase): MovieDao = appDatabase.movieDao() + @Provides + fun provideImagesDao(appDatabase: AppDatabase): ImageDao = appDatabase.imageDao() + @Provides fun provideAccountDao(appDatabase: AppDatabase): AccountDao = appDatabase.accountDao() diff --git a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/entity/ImageDb.kt b/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/entity/ImageDb.kt new file mode 100644 index 000000000..d272fb4ba --- /dev/null +++ b/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/entity/ImageDb.kt @@ -0,0 +1,38 @@ +package org.michaelbel.movies.persistence.database.entity + +import androidx.room.Entity +import org.jetbrains.annotations.NotNull +import org.jetbrains.annotations.Nullable + +@Entity(tableName = "images", primaryKeys = ["movieId", "filePath"]) +data class ImageDb( + @NotNull val movieId: Int, + @NotNull val filePath: String, + @NotNull val type: Type, + @NotNull val width: Int, + @NotNull val height: Int, + @NotNull val aspectRatio: Float, + @NotNull val voteAverage: Float, + @NotNull val voteCount: Int, + @Nullable val lang: String? +) { + companion object { + val Empty: ImageDb = ImageDb( + movieId = 0, + filePath = "", + type = Type.BACKDROP, + width = 0, + height = 0, + aspectRatio = 0F, + voteAverage = 0F, + voteCount = 0, + lang = null + ) + } + + enum class Type { + BACKDROP, + POSTER, + LOGO + } +} \ No newline at end of file diff --git a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/entity/PagingKeyDb.kt b/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/entity/PagingKeyDb.kt index b54f402bb..15fb3b1b5 100644 --- a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/entity/PagingKeyDb.kt +++ b/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/entity/PagingKeyDb.kt @@ -2,9 +2,10 @@ package org.michaelbel.movies.persistence.database.entity import androidx.room.Entity import org.jetbrains.annotations.NotNull +import org.jetbrains.annotations.Nullable @Entity(tableName = "pagingkeys", primaryKeys = ["movieList"]) data class PagingKeyDb( @NotNull val movieList: String, - val page: Int? = null + @Nullable val page: Int? = null ) \ No newline at end of file diff --git a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/ktx/ImageDbKtx.kt b/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/ktx/ImageDbKtx.kt new file mode 100644 index 000000000..4f84f036b --- /dev/null +++ b/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/ktx/ImageDbKtx.kt @@ -0,0 +1,21 @@ +package org.michaelbel.movies.persistence.database.ktx + +import org.michaelbel.movies.network.formatImage +import org.michaelbel.movies.network.model.image.BackdropSize +import org.michaelbel.movies.network.model.image.LogoSize +import org.michaelbel.movies.network.model.image.PosterSize +import org.michaelbel.movies.persistence.database.entity.ImageDb + +val ImageDb.image: String + get() = when (type) { + ImageDb.Type.BACKDROP -> filePath.formatImage(BackdropSize.W300.size) + ImageDb.Type.POSTER -> filePath.formatImage(PosterSize.W92.size) + ImageDb.Type.LOGO -> filePath.formatImage(LogoSize.W45.size) + } + +val ImageDb.original: String + get() = when (type) { + ImageDb.Type.BACKDROP -> filePath.formatImage(BackdropSize.ORIGINAL.size) + ImageDb.Type.POSTER -> filePath.formatImage(PosterSize.ORIGINAL.size) + ImageDb.Type.LOGO -> filePath.formatImage(LogoSize.ORIGINAL.size) + } \ No newline at end of file diff --git a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/ktx/ImageKtx.kt b/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/ktx/ImageKtx.kt new file mode 100644 index 000000000..b9f8dc382 --- /dev/null +++ b/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/ktx/ImageKtx.kt @@ -0,0 +1,18 @@ +package org.michaelbel.movies.persistence.database.ktx + +import org.michaelbel.movies.network.model.Image +import org.michaelbel.movies.persistence.database.entity.ImageDb + +fun Image.imageDb(movieId: Int, type: ImageDb.Type): ImageDb { + return ImageDb( + movieId = movieId, + filePath = filePath, + type = type, + width = width, + height = height, + aspectRatio = aspectRatio, + voteAverage = voteAverage, + voteCount = voteCount, + lang = lang + ) +} \ No newline at end of file diff --git a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/ktx/MovieDbKtx.kt b/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/ktx/MovieDbKtx.kt index 9a5f3398a..263fa173d 100644 --- a/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/ktx/MovieDbKtx.kt +++ b/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/ktx/MovieDbKtx.kt @@ -1,6 +1,6 @@ package org.michaelbel.movies.persistence.database.ktx -import org.michaelbel.movies.entities.TMDB_MOVIE_URL +import org.michaelbel.movies.network.TMDB_MOVIE_URL import org.michaelbel.movies.persistence.database.entity.MovieDb import java.util.Locale diff --git a/core/domain/src/main/kotlin/org/michaelbel/movies/domain/ktx/MovieResponseKtx.kt b/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/ktx/MovieResponseKtx.kt similarity index 52% rename from core/domain/src/main/kotlin/org/michaelbel/movies/domain/ktx/MovieResponseKtx.kt rename to core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/ktx/MovieResponseKtx.kt index 2d901c76e..7f9545d2b 100644 --- a/core/domain/src/main/kotlin/org/michaelbel/movies/domain/ktx/MovieResponseKtx.kt +++ b/core/persistence/src/main/kotlin/org/michaelbel/movies/persistence/database/ktx/MovieResponseKtx.kt @@ -1,19 +1,17 @@ -package org.michaelbel.movies.domain.ktx +package org.michaelbel.movies.persistence.database.ktx -import org.michaelbel.movies.entities.image.formatBackdropImage -import org.michaelbel.movies.entities.image.formatPosterImage import org.michaelbel.movies.network.model.MovieResponse import org.michaelbel.movies.persistence.database.entity.MovieDb -internal fun MovieResponse.mapToMovieDb(movieList: String, position: Int): MovieDb { +fun MovieResponse.movieDb(movieList: String, position: Int): MovieDb { return MovieDb( movieList = movieList, dateAdded = System.currentTimeMillis(), position = position, movieId = id, overview = overview.orEmpty(), - posterPath = posterPath.orEmpty().formatPosterImage, - backdropPath = backdropPath.orEmpty().formatBackdropImage, + posterPath = posterPath.orEmpty(), + backdropPath = backdropPath.orEmpty(), releaseDate = releaseDate, title = title, voteAverage = voteAverage diff --git a/core/repository-impl/build.gradle.kts b/core/repository-impl/build.gradle.kts index c7a8c7731..ed8016444 100644 --- a/core/repository-impl/build.gradle.kts +++ b/core/repository-impl/build.gradle.kts @@ -2,7 +2,6 @@ plugins { alias(libs.plugins.library) alias(libs.plugins.kotlin) - alias(libs.plugins.detekt) id("movies-android-hilt") } @@ -40,8 +39,14 @@ android { checkDependencies = true lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") } + + kapt { + correctErrorTypes = true + } } dependencies { api(project(":core:repository")) + testImplementation(libs.kotlin.coroutines.test) + testImplementation(libs.junit) } \ No newline at end of file diff --git a/core/repository-impl/src/main/kotlin/org/michaelbel/movies/repository/AccountRepositoryImpl.kt b/core/repository-impl/src/main/kotlin/org/michaelbel/movies/repository/AccountRepositoryImpl.kt index 3daa61e8e..94cc947e9 100644 --- a/core/repository-impl/src/main/kotlin/org/michaelbel/movies/repository/AccountRepositoryImpl.kt +++ b/core/repository-impl/src/main/kotlin/org/michaelbel/movies/repository/AccountRepositoryImpl.kt @@ -4,7 +4,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import org.michaelbel.movies.common.exceptions.AccountDetailsException -import org.michaelbel.movies.entities.tmdbApiKey +import org.michaelbel.movies.network.tmdbApiKey import org.michaelbel.movies.network.model.Account import org.michaelbel.movies.network.service.account.AccountService import org.michaelbel.movies.persistence.database.dao.AccountDao diff --git a/core/repository-impl/src/main/kotlin/org/michaelbel/movies/repository/AuthenticationRepositoryImpl.kt b/core/repository-impl/src/main/kotlin/org/michaelbel/movies/repository/AuthenticationRepositoryImpl.kt index bdc89411a..861430c65 100644 --- a/core/repository-impl/src/main/kotlin/org/michaelbel/movies/repository/AuthenticationRepositoryImpl.kt +++ b/core/repository-impl/src/main/kotlin/org/michaelbel/movies/repository/AuthenticationRepositoryImpl.kt @@ -4,7 +4,7 @@ import org.michaelbel.movies.common.exceptions.CreateRequestTokenException import org.michaelbel.movies.common.exceptions.CreateSessionException import org.michaelbel.movies.common.exceptions.CreateSessionWithLoginException import org.michaelbel.movies.common.exceptions.DeleteSessionException -import org.michaelbel.movies.entities.tmdbApiKey +import org.michaelbel.movies.network.tmdbApiKey import org.michaelbel.movies.network.model.DeletedSession import org.michaelbel.movies.network.model.RequestToken import org.michaelbel.movies.network.model.Session diff --git a/core/repository-impl/src/main/kotlin/org/michaelbel/movies/repository/ImageRepositoryImpl.kt b/core/repository-impl/src/main/kotlin/org/michaelbel/movies/repository/ImageRepositoryImpl.kt new file mode 100644 index 000000000..ce22c4e9d --- /dev/null +++ b/core/repository-impl/src/main/kotlin/org/michaelbel/movies/repository/ImageRepositoryImpl.kt @@ -0,0 +1,39 @@ +package org.michaelbel.movies.repository + +import javax.inject.Inject +import javax.inject.Singleton +import kotlinx.coroutines.flow.Flow +import org.michaelbel.movies.network.tmdbApiKey +import org.michaelbel.movies.network.model.ImagesResponse +import org.michaelbel.movies.network.service.image.ImageService +import org.michaelbel.movies.persistence.database.dao.ImageDao +import org.michaelbel.movies.persistence.database.entity.ImageDb +import org.michaelbel.movies.persistence.database.ktx.imageDb + +@Singleton +internal class ImageRepositoryImpl @Inject constructor( + private val imageService: ImageService, + private val imageDao: ImageDao +): ImageRepository { + + override fun imagesFlow(movieId: Int): Flow> { + return imageDao.imagesFlow(movieId) + } + + override suspend fun images(movieId: Int) { + val imageResponse: ImagesResponse = imageService.images( + id = movieId, + apiKey = tmdbApiKey + ) + val backdrops: List = imageResponse.backdrops.map { image -> + image.imageDb(movieId, ImageDb.Type.BACKDROP) + } + val posters: List = imageResponse.posters.map { image -> + image.imageDb(movieId, ImageDb.Type.POSTER) + } + val logos: List = imageResponse.logos.map { image -> + image.imageDb(movieId, ImageDb.Type.LOGO) + } + imageDao.insert(backdrops + posters + logos) + } +} \ No newline at end of file diff --git a/core/repository-impl/src/main/kotlin/org/michaelbel/movies/repository/MovieRepositoryImpl.kt b/core/repository-impl/src/main/kotlin/org/michaelbel/movies/repository/MovieRepositoryImpl.kt index a10bc86ad..342b9a47c 100644 --- a/core/repository-impl/src/main/kotlin/org/michaelbel/movies/repository/MovieRepositoryImpl.kt +++ b/core/repository-impl/src/main/kotlin/org/michaelbel/movies/repository/MovieRepositoryImpl.kt @@ -3,17 +3,15 @@ package org.michaelbel.movies.repository import androidx.paging.PagingSource import javax.inject.Inject import javax.inject.Singleton -import kotlinx.coroutines.flow.Flow import org.michaelbel.movies.common.localization.LocaleController -import org.michaelbel.movies.entities.Either -import org.michaelbel.movies.entities.isTmdbApiKeyEmpty -import org.michaelbel.movies.entities.response -import org.michaelbel.movies.entities.tmdbApiKey -import org.michaelbel.movies.network.model.ImagesResponse +import org.michaelbel.movies.network.Either +import org.michaelbel.movies.network.isTmdbApiKeyEmpty import org.michaelbel.movies.network.model.Movie import org.michaelbel.movies.network.model.MovieResponse import org.michaelbel.movies.network.model.Result +import org.michaelbel.movies.network.response import org.michaelbel.movies.network.service.movie.MovieService +import org.michaelbel.movies.network.tmdbApiKey import org.michaelbel.movies.persistence.database.dao.MovieDao import org.michaelbel.movies.persistence.database.dao.PagingKeyDao import org.michaelbel.movies.persistence.database.dao.ktx.isEmpty @@ -34,8 +32,9 @@ internal class MovieRepositoryImpl @Inject constructor( return movieDao.pagingSource(movieList) } - override fun movieImage(movieId: Int): Flow { - return movieDao.movieImage(movieId) + override suspend fun backdropPosition(movieId: Int): Int { + val backdropPath: String = movieDao.movieBackdropPath(movieId) + return 0 } override suspend fun moviesResult(movieList: String, page: Int): Result { @@ -68,13 +67,6 @@ internal class MovieRepositoryImpl @Inject constructor( } } - override suspend fun movieImages(movieId: Int): ImagesResponse { - return movieService.images( - id = movieId, - apiKey = tmdbApiKey - ) - } - override suspend fun removeAllMovies(movieList: String) { movieDao.removeAllMovies(movieList) } diff --git a/core/repository-impl/src/main/kotlin/org/michaelbel/movies/repository/di/RepositoryModule.kt b/core/repository-impl/src/main/kotlin/org/michaelbel/movies/repository/di/RepositoryModule.kt index bf3dbf1aa..0fa4b2366 100644 --- a/core/repository-impl/src/main/kotlin/org/michaelbel/movies/repository/di/RepositoryModule.kt +++ b/core/repository-impl/src/main/kotlin/org/michaelbel/movies/repository/di/RepositoryModule.kt @@ -4,15 +4,17 @@ import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton import org.michaelbel.movies.repository.AccountRepository import org.michaelbel.movies.repository.AccountRepositoryImpl import org.michaelbel.movies.repository.AuthenticationRepository import org.michaelbel.movies.repository.AuthenticationRepositoryImpl +import org.michaelbel.movies.repository.ImageRepository +import org.michaelbel.movies.repository.ImageRepositoryImpl import org.michaelbel.movies.repository.MovieRepository import org.michaelbel.movies.repository.MovieRepositoryImpl import org.michaelbel.movies.repository.SettingsRepository import org.michaelbel.movies.repository.SettingsRepositoryImpl -import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) @@ -24,6 +26,12 @@ internal interface RepositoryModule { repository: MovieRepositoryImpl ): MovieRepository + @Binds + @Singleton + fun provideImageRepository( + repository: ImageRepositoryImpl + ): ImageRepository + @Binds @Singleton fun provideAuthenticationRepository( diff --git a/core/repository-impl/src/main/kotlin/org/michaelbel/movies/repository/ktx/AccountKtx.kt b/core/repository-impl/src/main/kotlin/org/michaelbel/movies/repository/ktx/AccountKtx.kt index 4f1b96081..52a0afc9f 100644 --- a/core/repository-impl/src/main/kotlin/org/michaelbel/movies/repository/ktx/AccountKtx.kt +++ b/core/repository-impl/src/main/kotlin/org/michaelbel/movies/repository/ktx/AccountKtx.kt @@ -1,7 +1,7 @@ package org.michaelbel.movies.repository.ktx -import org.michaelbel.movies.entities.GRAVATAR_URL -import org.michaelbel.movies.entities.image.formatProfileImage +import org.michaelbel.movies.network.GRAVATAR_URL +import org.michaelbel.movies.network.formatProfileImage import org.michaelbel.movies.network.model.Account import org.michaelbel.movies.persistence.database.entity.AccountDb import java.util.Locale diff --git a/core/repository-impl/src/main/kotlin/org/michaelbel/movies/repository/ktx/ExceptionKtx.kt b/core/repository-impl/src/main/kotlin/org/michaelbel/movies/repository/ktx/ExceptionKtx.kt index 66ae7be18..e79dc5063 100644 --- a/core/repository-impl/src/main/kotlin/org/michaelbel/movies/repository/ktx/ExceptionKtx.kt +++ b/core/repository-impl/src/main/kotlin/org/michaelbel/movies/repository/ktx/ExceptionKtx.kt @@ -1,7 +1,7 @@ package org.michaelbel.movies.repository.ktx import org.michaelbel.movies.common.exceptions.ApiKeyNotNullException -import org.michaelbel.movies.entities.isTmdbApiKeyEmpty +import org.michaelbel.movies.network.isTmdbApiKeyEmpty internal fun checkApiKeyNotNullException() { if (isTmdbApiKeyEmpty) throw ApiKeyNotNullException diff --git a/core/repository-impl/src/main/kotlin/org/michaelbel/movies/repository/ktx/MovieKtx.kt b/core/repository-impl/src/main/kotlin/org/michaelbel/movies/repository/ktx/MovieKtx.kt index f9d9dda16..e551f9e81 100644 --- a/core/repository-impl/src/main/kotlin/org/michaelbel/movies/repository/ktx/MovieKtx.kt +++ b/core/repository-impl/src/main/kotlin/org/michaelbel/movies/repository/ktx/MovieKtx.kt @@ -1,7 +1,5 @@ package org.michaelbel.movies.repository.ktx -import org.michaelbel.movies.entities.image.formatBackdropImage -import org.michaelbel.movies.entities.image.formatPosterImage import org.michaelbel.movies.network.model.Movie import org.michaelbel.movies.persistence.database.entity.MovieDb @@ -12,8 +10,8 @@ internal val Movie.mapToMovieDb: MovieDb position = 0, movieId = id, overview = overview.orEmpty(), - posterPath = posterPath.orEmpty().formatPosterImage, - backdropPath = backdropPath.orEmpty().formatBackdropImage, + posterPath = posterPath.orEmpty(), + backdropPath = backdropPath.orEmpty(), releaseDate = releaseDate.orEmpty(), title = title.orEmpty(), voteAverage = voteAverage diff --git a/core/repository-impl/src/main/kotlin/org/michaelbel/movies/repository/ktx/MovieResponseKtx.kt b/core/repository-impl/src/main/kotlin/org/michaelbel/movies/repository/ktx/MovieResponseKtx.kt index c8fd79fd5..df28664ab 100644 --- a/core/repository-impl/src/main/kotlin/org/michaelbel/movies/repository/ktx/MovieResponseKtx.kt +++ b/core/repository-impl/src/main/kotlin/org/michaelbel/movies/repository/ktx/MovieResponseKtx.kt @@ -1,7 +1,5 @@ package org.michaelbel.movies.repository.ktx -import org.michaelbel.movies.entities.image.formatBackdropImage -import org.michaelbel.movies.entities.image.formatPosterImage import org.michaelbel.movies.network.model.MovieResponse import org.michaelbel.movies.persistence.database.entity.MovieDb @@ -12,8 +10,8 @@ internal fun MovieResponse.mapToMovieDb(movieList: String, position: Int): Movie position = position, movieId = id, overview = overview.orEmpty(), - posterPath = posterPath.orEmpty().formatPosterImage, - backdropPath = backdropPath.orEmpty().formatBackdropImage, + posterPath = posterPath.orEmpty(), + backdropPath = backdropPath.orEmpty(), releaseDate = releaseDate, title = title, voteAverage = voteAverage diff --git a/core/repository-impl/src/test/kotlin/org/michaelbel/movies/repository/AccountRepositoryTest.kt b/core/repository-impl/src/test/kotlin/org/michaelbel/movies/repository/AccountRepositoryTest.kt new file mode 100644 index 000000000..d5e39cbc4 --- /dev/null +++ b/core/repository-impl/src/test/kotlin/org/michaelbel/movies/repository/AccountRepositoryTest.kt @@ -0,0 +1,21 @@ +package org.michaelbel.movies.repository + +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import org.junit.Before + +class AccountRepositoryTest { + + private val testScope = TestScope(UnconfinedTestDispatcher()) + + private lateinit var subject: AccountRepositoryImpl + + @Before + fun setup() { + /*subject = AccountRepositoryImpl( + accountService = , + accountDao = , + preferences = + )*/ + } +} \ No newline at end of file diff --git a/core/repository/build.gradle.kts b/core/repository/build.gradle.kts index e874ffcfe..45c4cb35e 100644 --- a/core/repository/build.gradle.kts +++ b/core/repository/build.gradle.kts @@ -2,7 +2,6 @@ plugins { alias(libs.plugins.library) alias(libs.plugins.kotlin) - alias(libs.plugins.detekt) id("movies-android-hilt") } @@ -34,11 +33,14 @@ android { checkDependencies = true lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") } + + kapt { + correctErrorTypes = true + } } dependencies { api(project(":core:common")) - api(project(":core:entities")) api(project(":core:network")) api(project(":core:persistence")) } \ No newline at end of file diff --git a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/ImageRepository.kt b/core/repository/src/main/kotlin/org/michaelbel/movies/repository/ImageRepository.kt new file mode 100644 index 000000000..f80a81280 --- /dev/null +++ b/core/repository/src/main/kotlin/org/michaelbel/movies/repository/ImageRepository.kt @@ -0,0 +1,11 @@ +package org.michaelbel.movies.repository + +import kotlinx.coroutines.flow.Flow +import org.michaelbel.movies.persistence.database.entity.ImageDb + +interface ImageRepository { + + fun imagesFlow(movieId: Int): Flow> + + suspend fun images(movieId: Int) +} \ No newline at end of file diff --git a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/MovieRepository.kt b/core/repository/src/main/kotlin/org/michaelbel/movies/repository/MovieRepository.kt index 6d31c18c7..3f85f7dcd 100644 --- a/core/repository/src/main/kotlin/org/michaelbel/movies/repository/MovieRepository.kt +++ b/core/repository/src/main/kotlin/org/michaelbel/movies/repository/MovieRepository.kt @@ -1,9 +1,7 @@ package org.michaelbel.movies.repository import androidx.paging.PagingSource -import kotlinx.coroutines.flow.Flow -import org.michaelbel.movies.entities.Either -import org.michaelbel.movies.network.model.ImagesResponse +import org.michaelbel.movies.network.Either import org.michaelbel.movies.network.model.MovieResponse import org.michaelbel.movies.network.model.Result import org.michaelbel.movies.persistence.database.entity.MovieDb @@ -12,14 +10,12 @@ interface MovieRepository { fun moviesPagingSource(movieList: String): PagingSource - fun movieImage(movieId: Int): Flow + suspend fun backdropPosition(movieId: Int): Int suspend fun moviesResult(movieList: String, page: Int): Result suspend fun movieDetails(movieId: Int): Either - suspend fun movieImages(movieId: Int): ImagesResponse - suspend fun removeAllMovies(movieList: String) suspend fun insertAllMovies(movieList: String, movies: List) diff --git a/core/src/main/kotlin/org/michaelbel/movies/core/config/di/RemoteConfigModule.kt b/core/src/main/kotlin/org/michaelbel/movies/core/config/di/RemoteConfigModule.kt index 786d2def2..209d5b0ed 100644 --- a/core/src/main/kotlin/org/michaelbel/movies/core/config/di/RemoteConfigModule.kt +++ b/core/src/main/kotlin/org/michaelbel/movies/core/config/di/RemoteConfigModule.kt @@ -8,7 +8,6 @@ import com.google.firebase.remoteconfig.ktx.remoteConfigSettings import dagger.Module import dagger.Provides import dagger.hilt.InstallIn -import dagger.hilt.android.components.ViewModelComponent import dagger.hilt.components.SingletonComponent import org.michaelbel.movies.core.config.RemoteParams diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index 8076d4719..60a4b5c04 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -2,7 +2,6 @@ plugins { alias(libs.plugins.library) alias(libs.plugins.kotlin) - alias(libs.plugins.detekt) } android { @@ -45,9 +44,9 @@ android { dependencies { implementation(project(":core:common")) - implementation(project(":core:domain")) implementation(project(":core:network")) implementation(project(":core:notifications")) + implementation(project(":core:persistence")) api(libs.androidx.core.splashscreen) api(libs.androidx.constraintlayout.compose) api(libs.coil.compose) diff --git a/core/domain/.gitignore b/core/work/.gitignore similarity index 100% rename from core/domain/.gitignore rename to core/work/.gitignore diff --git a/core/domain/build.gradle.kts b/core/work/build.gradle.kts similarity index 86% rename from core/domain/build.gradle.kts rename to core/work/build.gradle.kts index 996979829..59f7e79bd 100644 --- a/core/domain/build.gradle.kts +++ b/core/work/build.gradle.kts @@ -7,7 +7,7 @@ plugins { } android { - namespace = "org.michaelbel.movies.domain" + namespace = "org.michaelbel.movies.work" defaultConfig { minSdk = libs.versions.min.sdk.get().toInt() @@ -24,8 +24,7 @@ android { kotlinOptions { freeCompilerArgs = freeCompilerArgs + listOf( - "-opt-in=kotlinx.serialization.ExperimentalSerializationApi", - "-opt-in=androidx.paging.ExperimentalPagingApi" + "-opt-in=kotlinx.serialization.ExperimentalSerializationApi" ) } @@ -46,9 +45,7 @@ android { dependencies { api(project(":core:interactor-impl")) api(project(":core:persistence")) - implementation(project(":core:analytics")) implementation(project(":core:common")) - implementation(project(":core:entities")) implementation(project(":core:network")) implementation(project(":core:repository-impl")) api(libs.androidx.hilt.work) diff --git a/core/domain/src/main/AndroidManifest.xml b/core/work/src/main/AndroidManifest.xml similarity index 100% rename from core/domain/src/main/AndroidManifest.xml rename to core/work/src/main/AndroidManifest.xml diff --git a/core/domain/src/main/assets/movies.json b/core/work/src/main/assets/movies.json similarity index 100% rename from core/domain/src/main/assets/movies.json rename to core/work/src/main/assets/movies.json diff --git a/core/domain/src/main/kotlin/org/michaelbel/movies/domain/workers/AccountUpdateWorker.kt b/core/work/src/main/kotlin/org/michaelbel/movies/work/AccountUpdateWorker.kt similarity index 93% rename from core/domain/src/main/kotlin/org/michaelbel/movies/domain/workers/AccountUpdateWorker.kt rename to core/work/src/main/kotlin/org/michaelbel/movies/work/AccountUpdateWorker.kt index 5718ccead..51e0fae59 100644 --- a/core/domain/src/main/kotlin/org/michaelbel/movies/domain/workers/AccountUpdateWorker.kt +++ b/core/work/src/main/kotlin/org/michaelbel/movies/work/AccountUpdateWorker.kt @@ -1,4 +1,4 @@ -package org.michaelbel.movies.domain.workers +package org.michaelbel.movies.work import android.content.Context import androidx.hilt.work.HiltWorker @@ -6,11 +6,11 @@ import androidx.work.CoroutineWorker import androidx.work.WorkerParameters import dagger.assisted.Assisted import dagger.assisted.AssistedInject +import java.util.concurrent.TimeUnit import org.michaelbel.movies.common.ktx.isTimePasses -import org.michaelbel.movies.entities.isTmdbApiKeyEmpty +import org.michaelbel.movies.network.isTmdbApiKeyEmpty import org.michaelbel.movies.interactor.Interactor import org.michaelbel.movies.persistence.datastore.MoviesPreferences -import java.util.concurrent.TimeUnit @HiltWorker class AccountUpdateWorker @AssistedInject constructor( diff --git a/core/domain/src/main/kotlin/org/michaelbel/movies/domain/workers/MoviesDatabaseWorker.kt b/core/work/src/main/kotlin/org/michaelbel/movies/work/MoviesDatabaseWorker.kt similarity index 93% rename from core/domain/src/main/kotlin/org/michaelbel/movies/domain/workers/MoviesDatabaseWorker.kt rename to core/work/src/main/kotlin/org/michaelbel/movies/work/MoviesDatabaseWorker.kt index 07b6b782f..47421311e 100644 --- a/core/domain/src/main/kotlin/org/michaelbel/movies/domain/workers/MoviesDatabaseWorker.kt +++ b/core/work/src/main/kotlin/org/michaelbel/movies/work/MoviesDatabaseWorker.kt @@ -1,4 +1,4 @@ -package org.michaelbel.movies.domain.workers +package org.michaelbel.movies.work import android.content.Context import androidx.hilt.work.HiltWorker @@ -10,11 +10,11 @@ import kotlinx.coroutines.withContext import kotlinx.serialization.json.Json import kotlinx.serialization.json.decodeFromStream import org.michaelbel.movies.common.dispatchers.MoviesDispatchers -import org.michaelbel.movies.domain.ktx.mapToMovieDb import org.michaelbel.movies.network.model.MovieResponse import org.michaelbel.movies.persistence.database.dao.MovieDao import org.michaelbel.movies.persistence.database.dao.ktx.isEmpty import org.michaelbel.movies.persistence.database.entity.MovieDb +import org.michaelbel.movies.persistence.database.ktx.movieDb @HiltWorker class MoviesDatabaseWorker @AssistedInject constructor( @@ -33,7 +33,7 @@ class MoviesDatabaseWorker @AssistedInject constructor( val format = Json { ignoreUnknownKeys = true } val moviesJsonData: List = format.decodeFromStream(inputStream) val moviesDb: List = moviesJsonData.mapIndexed { index, movieResponse -> - movieResponse.mapToMovieDb( + movieResponse.movieDb( movieList = MovieDb.MOVIES_LOCAL_LIST, position = index.plus(1) ) diff --git a/feature/account-impl/build.gradle.kts b/feature/account-impl/build.gradle.kts index 789ac7e66..952fe5d68 100644 --- a/feature/account-impl/build.gradle.kts +++ b/feature/account-impl/build.gradle.kts @@ -2,7 +2,6 @@ plugins { alias(libs.plugins.library) alias(libs.plugins.kotlin) - alias(libs.plugins.detekt) id("movies-android-hilt") } @@ -49,14 +48,20 @@ android { checkDependencies = true lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") } + + kapt { + correctErrorTypes = true + } } dependencies { api(project(":core:navigation")) api(project(":core:ui")) implementation(project(":core:common")) - implementation(project(":core:domain")) + implementation(project(":core:interactor")) implementation(project(":core:network")) + implementation(libs.androidx.work.runtime.ktx) + implementation(libs.androidx.hilt.work) testImplementation(libs.junit) androidTestImplementation(libs.androidx.test.ext.junit.ktx) diff --git a/feature/account/build.gradle.kts b/feature/account/build.gradle.kts index 9aed5f2b0..87497dd6a 100644 --- a/feature/account/build.gradle.kts +++ b/feature/account/build.gradle.kts @@ -2,7 +2,6 @@ plugins { alias(libs.plugins.library) alias(libs.plugins.kotlin) - alias(libs.plugins.detekt) } android { diff --git a/feature/auth-impl/build.gradle.kts b/feature/auth-impl/build.gradle.kts index 9cfa27b61..7accdb4d0 100644 --- a/feature/auth-impl/build.gradle.kts +++ b/feature/auth-impl/build.gradle.kts @@ -2,7 +2,6 @@ plugins { alias(libs.plugins.library) alias(libs.plugins.kotlin) - alias(libs.plugins.detekt) id("movies-android-hilt") } @@ -49,13 +48,17 @@ android { checkDependencies = true lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") } + + kapt { + correctErrorTypes = true + } } dependencies { - api(project(":core:domain")) api(project(":core:navigation")) api(project(":core:ui")) implementation(project(":core:common")) + implementation(project(":core:interactor")) implementation(project(":core:network")) implementation(libs.androidx.autofill) diff --git a/feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AuthScreenContent.kt b/feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AuthScreenContent.kt index 6783e2efa..b058503dc 100644 --- a/feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AuthScreenContent.kt +++ b/feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AuthScreenContent.kt @@ -46,11 +46,11 @@ import org.michaelbel.movies.auth.ktx.text import org.michaelbel.movies.auth_impl.R import org.michaelbel.movies.common.browser.openUrl import org.michaelbel.movies.common.exceptions.CreateSessionWithLoginException -import org.michaelbel.movies.entities.TMDB_PRIVACY_POLICY -import org.michaelbel.movies.entities.TMDB_REGISTER -import org.michaelbel.movies.entities.TMDB_RESET_PASSWORD -import org.michaelbel.movies.entities.TMDB_TERMS_OF_USE -import org.michaelbel.movies.entities.TMDB_URL +import org.michaelbel.movies.network.TMDB_PRIVACY_POLICY +import org.michaelbel.movies.network.TMDB_REGISTER +import org.michaelbel.movies.network.TMDB_RESET_PASSWORD +import org.michaelbel.movies.network.TMDB_TERMS_OF_USE +import org.michaelbel.movies.network.TMDB_URL import org.michaelbel.movies.ui.icons.MoviesIcons import org.michaelbel.movies.ui.ktx.clickableWithoutRipple diff --git a/feature/auth/build.gradle.kts b/feature/auth/build.gradle.kts index 9efd6ed6b..f4ce1a863 100644 --- a/feature/auth/build.gradle.kts +++ b/feature/auth/build.gradle.kts @@ -2,7 +2,6 @@ plugins { alias(libs.plugins.library) alias(libs.plugins.kotlin) - alias(libs.plugins.detekt) } android { diff --git a/feature/details-impl/build.gradle.kts b/feature/details-impl/build.gradle.kts index db9f37e77..66324b5eb 100644 --- a/feature/details-impl/build.gradle.kts +++ b/feature/details-impl/build.gradle.kts @@ -2,7 +2,6 @@ plugins { alias(libs.plugins.library) alias(libs.plugins.kotlin) - alias(libs.plugins.detekt) id("movies-android-hilt") } @@ -49,13 +48,17 @@ android { checkDependencies = true lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") } + + kapt { + correctErrorTypes = true + } } dependencies { api(project(":core:navigation")) api(project(":core:ui")) implementation(project(":core:common")) - implementation(project(":core:domain")) + implementation(project(":core:interactor")) implementation(project(":core:network")) testImplementation(libs.junit) diff --git a/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/DetailsViewModel.kt b/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/DetailsViewModel.kt index ce4c0c200..20ccf697a 100644 --- a/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/DetailsViewModel.kt +++ b/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/DetailsViewModel.kt @@ -2,6 +2,7 @@ package org.michaelbel.movies.details import androidx.lifecycle.SavedStateHandle import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -10,11 +11,10 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import org.michaelbel.movies.common.ktx.require import org.michaelbel.movies.common.viewmodel.BaseViewModel -import org.michaelbel.movies.entities.lce.ScreenState import org.michaelbel.movies.interactor.usecase.MovieDetailsCase +import org.michaelbel.movies.network.ScreenState import org.michaelbel.movies.network.connectivity.NetworkManager import org.michaelbel.movies.network.connectivity.NetworkStatus -import javax.inject.Inject @HiltViewModel class DetailsViewModel @Inject constructor( diff --git a/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ktx/ScreenStateKtx.kt b/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ktx/ScreenStateKtx.kt index a339e8f2a..9634ba38a 100644 --- a/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ktx/ScreenStateKtx.kt +++ b/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ktx/ScreenStateKtx.kt @@ -3,7 +3,7 @@ package org.michaelbel.movies.details.ktx import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import org.michaelbel.movies.details_impl.R -import org.michaelbel.movies.entities.lce.ScreenState +import org.michaelbel.movies.network.ScreenState import org.michaelbel.movies.persistence.database.entity.MovieDb import org.michaelbel.movies.persistence.database.ktx.url diff --git a/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsContent.kt b/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsContent.kt index 096acbf19..970bf7ddb 100644 --- a/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsContent.kt +++ b/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsContent.kt @@ -27,6 +27,7 @@ import androidx.constraintlayout.compose.Dimension import coil.compose.AsyncImage import coil.request.ImageRequest import org.michaelbel.movies.details_impl.R +import org.michaelbel.movies.network.formatBackdropImage import org.michaelbel.movies.persistence.database.entity.MovieDb import org.michaelbel.movies.persistence.database.ktx.isNotEmpty import org.michaelbel.movies.ui.ktx.context @@ -57,7 +58,7 @@ fun DetailsContent( null } else { ImageRequest.Builder(context) - .data(movie.backdropPath) + .data(movie.backdropPath.formatBackdropImage) .crossfade(true) .build() } diff --git a/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsScreenContent.kt b/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsScreenContent.kt index b4c5c6f3f..8bd0ca43a 100644 --- a/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsScreenContent.kt +++ b/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsScreenContent.kt @@ -10,16 +10,16 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import java.net.UnknownHostException import org.michaelbel.movies.details.DetailsViewModel import org.michaelbel.movies.details.ktx.movie import org.michaelbel.movies.details.ktx.movieUrl import org.michaelbel.movies.details.ktx.toolbarTitle -import org.michaelbel.movies.entities.lce.ScreenState -import org.michaelbel.movies.entities.lce.ktx.isFailure -import org.michaelbel.movies.entities.lce.ktx.throwable +import org.michaelbel.movies.network.ScreenState import org.michaelbel.movies.network.connectivity.NetworkStatus import org.michaelbel.movies.network.connectivity.ktx.isAvailable -import java.net.UnknownHostException +import org.michaelbel.movies.network.ktx.isFailure +import org.michaelbel.movies.network.ktx.throwable @Composable fun DetailsRoute( diff --git a/feature/details/build.gradle.kts b/feature/details/build.gradle.kts index b17a1b2f4..e7e56781b 100644 --- a/feature/details/build.gradle.kts +++ b/feature/details/build.gradle.kts @@ -2,7 +2,6 @@ plugins { alias(libs.plugins.library) alias(libs.plugins.kotlin) - alias(libs.plugins.detekt) } android { diff --git a/feature/feed-impl/build.gradle.kts b/feature/feed-impl/build.gradle.kts index 13935c106..b38430ee2 100644 --- a/feature/feed-impl/build.gradle.kts +++ b/feature/feed-impl/build.gradle.kts @@ -2,7 +2,6 @@ plugins { alias(libs.plugins.library) alias(libs.plugins.kotlin) - alias(libs.plugins.detekt) id("movies-android-hilt") } @@ -26,6 +25,7 @@ android { kotlinOptions { freeCompilerArgs = freeCompilerArgs + listOf( "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", + "-opt-in=kotlinx.serialization.ExperimentalSerializationApi", "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api", "-opt-in=androidx.paging.ExperimentalPagingApi" ) @@ -51,15 +51,20 @@ android { checkDependencies = true lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") } + + kapt { + correctErrorTypes = true + } } dependencies { api(project(":core:navigation")) api(project(":core:ui")) implementation(project(":core:common")) - implementation(project(":core:domain")) + implementation(project(":core:interactor")) implementation(project(":core:network")) implementation(project(":core:notifications")) + implementation(project(":core:persistence")) testImplementation(libs.junit) androidTestImplementation(libs.androidx.test.ext.junit.ktx) diff --git a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/FeedViewModel.kt b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/FeedViewModel.kt index 168601103..d76353c31 100644 --- a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/FeedViewModel.kt +++ b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/FeedViewModel.kt @@ -8,6 +8,7 @@ import androidx.paging.PagingConfig import androidx.paging.PagingData import androidx.paging.cachedIn import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -22,8 +23,8 @@ import org.michaelbel.movies.common.appearance.FeedView import org.michaelbel.movies.common.inappupdate.di.InAppUpdate import org.michaelbel.movies.common.list.MovieList import org.michaelbel.movies.common.viewmodel.BaseViewModel -import org.michaelbel.movies.domain.mediator.MoviesRemoteMediator import org.michaelbel.movies.feed.ktx.nameOrLocalList +import org.michaelbel.movies.feed.remote.MoviesRemoteMediator import org.michaelbel.movies.interactor.Interactor import org.michaelbel.movies.network.connectivity.NetworkManager import org.michaelbel.movies.network.connectivity.NetworkStatus @@ -31,7 +32,6 @@ import org.michaelbel.movies.network.model.MovieResponse import org.michaelbel.movies.notifications.NotificationClient import org.michaelbel.movies.persistence.database.entity.AccountDb import org.michaelbel.movies.persistence.database.entity.MovieDb -import javax.inject.Inject @HiltViewModel class FeedViewModel @Inject constructor( diff --git a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ktx/MovieListKtx.kt b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ktx/MovieListKtx.kt index dafb6fa52..e9fda4ef9 100644 --- a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ktx/MovieListKtx.kt +++ b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ktx/MovieListKtx.kt @@ -3,7 +3,7 @@ package org.michaelbel.movies.feed.ktx import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import org.michaelbel.movies.common.list.MovieList -import org.michaelbel.movies.entities.isTmdbApiKeyEmpty +import org.michaelbel.movies.network.isTmdbApiKeyEmpty import org.michaelbel.movies.feed_impl.R import org.michaelbel.movies.persistence.database.entity.MovieDb diff --git a/core/domain/src/main/kotlin/org/michaelbel/movies/domain/mediator/MoviesRemoteMediator.kt b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/remote/MoviesRemoteMediator.kt similarity index 97% rename from core/domain/src/main/kotlin/org/michaelbel/movies/domain/mediator/MoviesRemoteMediator.kt rename to feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/remote/MoviesRemoteMediator.kt index 0db10b3f4..b4bcae766 100644 --- a/core/domain/src/main/kotlin/org/michaelbel/movies/domain/mediator/MoviesRemoteMediator.kt +++ b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/remote/MoviesRemoteMediator.kt @@ -1,4 +1,4 @@ -package org.michaelbel.movies.domain.mediator +package org.michaelbel.movies.feed.remote import androidx.paging.LoadType import androidx.paging.PagingState diff --git a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedCellMovieBox.kt b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedCellMovieBox.kt index 642c18805..152878c35 100644 --- a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedCellMovieBox.kt +++ b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedCellMovieBox.kt @@ -23,6 +23,7 @@ import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension import coil.compose.AsyncImage import coil.request.ImageRequest +import org.michaelbel.movies.network.formatBackdropImage import org.michaelbel.movies.feed_impl.R import org.michaelbel.movies.persistence.database.entity.MovieDb import org.michaelbel.movies.ui.ktx.context @@ -45,7 +46,7 @@ fun FeedCellMovieBox( AsyncImage( model = ImageRequest.Builder(context) - .data(movie.backdropPath) + .data(movie.backdropPath.formatBackdropImage) .crossfade(true) .build(), contentDescription = null, diff --git a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedContent.kt b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedContent.kt index 210de08bc..42ac7cb3c 100644 --- a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedContent.kt +++ b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedContent.kt @@ -21,7 +21,7 @@ import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.itemContentType import androidx.paging.compose.itemKey import org.michaelbel.movies.common.appearance.FeedView -import org.michaelbel.movies.entities.isTmdbApiKeyEmpty +import org.michaelbel.movies.network.isTmdbApiKeyEmpty import org.michaelbel.movies.persistence.database.entity.MovieDb import org.michaelbel.movies.ui.ktx.isNotEmpty import org.michaelbel.movies.ui.ktx.isPagingFailure diff --git a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedGridMovieBox.kt b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedGridMovieBox.kt index 3e2031125..73c21824f 100644 --- a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedGridMovieBox.kt +++ b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedGridMovieBox.kt @@ -23,6 +23,7 @@ import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension import coil.compose.AsyncImage import coil.request.ImageRequest +import org.michaelbel.movies.network.formatPosterImage import org.michaelbel.movies.feed_impl.R import org.michaelbel.movies.persistence.database.entity.MovieDb import org.michaelbel.movies.ui.ktx.context @@ -45,7 +46,7 @@ fun FeedGridMovieBox( AsyncImage( model = ImageRequest.Builder(context) - .data(movie.posterPath) + .data(movie.posterPath.formatPosterImage) .crossfade(true) .build(), contentDescription = null, diff --git a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedScreenContent.kt b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedScreenContent.kt index 32a9564b6..519b6c059 100644 --- a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedScreenContent.kt +++ b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedScreenContent.kt @@ -44,7 +44,7 @@ import kotlinx.coroutines.launch import org.michaelbel.movies.common.appearance.FeedView import org.michaelbel.movies.common.exceptions.ApiKeyNotNullException import org.michaelbel.movies.common.list.MovieList -import org.michaelbel.movies.entities.isTmdbApiKeyEmpty +import org.michaelbel.movies.network.isTmdbApiKeyEmpty import org.michaelbel.movies.feed.FeedViewModel import org.michaelbel.movies.feed.ktx.titleText import org.michaelbel.movies.feed_impl.R diff --git a/feature/feed/build.gradle.kts b/feature/feed/build.gradle.kts index bb26112af..03fd62d3e 100644 --- a/feature/feed/build.gradle.kts +++ b/feature/feed/build.gradle.kts @@ -2,7 +2,6 @@ plugins { alias(libs.plugins.library) alias(libs.plugins.kotlin) - alias(libs.plugins.detekt) } android { diff --git a/feature/gallery-impl/build.gradle.kts b/feature/gallery-impl/build.gradle.kts index 5bc60c3ae..af0b4fc20 100644 --- a/feature/gallery-impl/build.gradle.kts +++ b/feature/gallery-impl/build.gradle.kts @@ -2,7 +2,6 @@ plugins { alias(libs.plugins.library) alias(libs.plugins.kotlin) - alias(libs.plugins.detekt) id("movies-android-hilt") } @@ -52,13 +51,17 @@ android { checkDependencies = true lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") } + + kapt { + correctErrorTypes = true + } } dependencies { api(project(":core:navigation")) api(project(":core:ui")) implementation(project(":core:common")) - implementation(project(":core:domain")) + implementation(project(":core:interactor")) implementation(project(":core:network")) testImplementation(libs.junit) diff --git a/feature/gallery-impl/src/main/kotlin/org/michaelbel/movies/gallery/GalleryViewModel.kt b/feature/gallery-impl/src/main/kotlin/org/michaelbel/movies/gallery/GalleryViewModel.kt index 44aca68ac..5090a0004 100644 --- a/feature/gallery-impl/src/main/kotlin/org/michaelbel/movies/gallery/GalleryViewModel.kt +++ b/feature/gallery-impl/src/main/kotlin/org/michaelbel/movies/gallery/GalleryViewModel.kt @@ -3,14 +3,16 @@ package org.michaelbel.movies.gallery import androidx.lifecycle.SavedStateHandle import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import org.michaelbel.movies.common.ktx.require import org.michaelbel.movies.common.viewmodel.BaseViewModel import org.michaelbel.movies.interactor.Interactor -import org.michaelbel.movies.network.model.ImagesResponse +import org.michaelbel.movies.persistence.database.entity.ImageDb @HiltViewModel class GalleryViewModel @Inject constructor( @@ -20,16 +22,22 @@ class GalleryViewModel @Inject constructor( private val movieId: String = savedStateHandle.require("movieId") - val imageFlow: StateFlow = interactor.movieImage(movieId.toInt()) + val movieImagesFlow: StateFlow> = interactor.imagesFlow(movieId.toInt()) .stateIn( scope = this, started = SharingStarted.Lazily, - initialValue = "" + initialValue = emptyList() ) + private val _backdropPositionFlow: MutableStateFlow = MutableStateFlow(0) + val backdropPositionFlow: StateFlow = _backdropPositionFlow.asStateFlow() + init { - launch { - val imagesResponse: ImagesResponse = interactor.movieImages(movieId.toInt()) - } + loadMovieImages(movieId.toInt()) + } + + private fun loadMovieImages(movieId: Int) = launch { + interactor.images(movieId) + _backdropPositionFlow.value = interactor.backdropPosition(movieId) } } \ No newline at end of file diff --git a/feature/gallery-impl/src/main/kotlin/org/michaelbel/movies/gallery/ui/GalleryScreenContent.kt b/feature/gallery-impl/src/main/kotlin/org/michaelbel/movies/gallery/ui/GalleryScreenContent.kt index df22d84be..97f561f67 100644 --- a/feature/gallery-impl/src/main/kotlin/org/michaelbel/movies/gallery/ui/GalleryScreenContent.kt +++ b/feature/gallery-impl/src/main/kotlin/org/michaelbel/movies/gallery/ui/GalleryScreenContent.kt @@ -1,6 +1,5 @@ package org.michaelbel.movies.gallery.ui -import android.content.Context import androidx.activity.compose.BackHandler import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -9,21 +8,30 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.PagerDefaults import androidx.compose.foundation.pager.PagerScope +import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material3.IconButton +import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalHapticFeedback +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension @@ -33,10 +41,12 @@ import coil.compose.AsyncImage import coil.compose.AsyncImagePainter import coil.request.ImageRequest import kotlinx.coroutines.launch -import org.michaelbel.movies.entities.image.original +import org.michaelbel.movies.network.isNotOriginal import org.michaelbel.movies.gallery.GalleryViewModel import org.michaelbel.movies.gallery.zoomable.rememberZoomState import org.michaelbel.movies.gallery.zoomable.zoomable +import org.michaelbel.movies.persistence.database.entity.ImageDb +import org.michaelbel.movies.persistence.database.ktx.original import org.michaelbel.movies.ui.icons.MoviesIcons @Composable @@ -45,10 +55,12 @@ fun GalleryRoute( modifier: Modifier = Modifier, viewModel: GalleryViewModel = hiltViewModel() ) { - val movieImage: String by viewModel.imageFlow.collectAsStateWithLifecycle() + val movieImages: List by viewModel.movieImagesFlow.collectAsStateWithLifecycle() + val backdropPosition: Int by viewModel.backdropPositionFlow.collectAsStateWithLifecycle() GalleryScreenContent( - movieImage = movieImage, + movieImages = movieImages, + backdropPosition = backdropPosition, onBackClick = onBackClick, modifier = modifier ) @@ -56,26 +68,41 @@ fun GalleryRoute( @Composable private fun GalleryScreenContent( - movieImage: String, + movieImages: List, + backdropPosition: Int, onBackClick: () -> Unit, modifier: Modifier = Modifier ) { - val context: Context = LocalContext.current + val context = LocalContext.current + val hapticFeedback = LocalHapticFeedback.current val coroutineScope = rememberCoroutineScope() - var imageDiskCacheKey: String? by remember { mutableStateOf(null) } - var image: String by remember { mutableStateOf("") } - image = movieImage - ConstraintLayout( modifier = modifier .fillMaxSize() .background(MaterialTheme.colorScheme.primaryContainer) ) { - val (pager, backIcon) = createRefs() + val (pager, backIcon, title) = createRefs() + + val initialPage = 0 + val pagerState = rememberPagerState( + initialPage = initialPage, + initialPageOffsetFraction = 0F, + pageCount = { movieImages.size } + ) + + var currentPage: Int by remember { mutableStateOf(0) } + LaunchedEffect(pagerState) { + snapshotFlow { pagerState.currentPage }.collect { page -> + if (currentPage != page) { + hapticFeedback.performHapticFeedback(hapticFeedbackType = HapticFeedbackType.LongPress) + currentPage = page + } + } + } LoopHorizontalPager( - count = 1, + pagerState = pagerState, modifier = Modifier.constrainAs(pager) { width = Dimension.fillToConstraints height = Dimension.wrapContent @@ -84,7 +111,17 @@ private fun GalleryScreenContent( end.linkTo(parent.end) bottom.linkTo(parent.bottom) } - ) { + ) { page -> + currentPage = page + + val imageDb: ImageDb = movieImages[page] + var imageDiskCacheKey: String? by remember { mutableStateOf(null) } + + var image: String by remember { mutableStateOf("") } + image = imageDb.original + + var loading: Boolean by remember { mutableStateOf(true) } + Box( contentAlignment = Alignment.Center ) { @@ -101,18 +138,27 @@ private fun GalleryScreenContent( .fillMaxSize() .zoomable(zoomState), transform = { state -> + loading = state is AsyncImagePainter.State.Loading + if (state is AsyncImagePainter.State.Success) { zoomState.setContentSize(state.painter.intrinsicSize) imageDiskCacheKey = state.result.diskCacheKey - if (image != image.original) { - image = image.original + if (image.isNotOriginal) { + image = imageDb.original } } + state }, contentScale = ContentScale.Fit ) + if (loading) { + LinearProgressIndicator( + trackColor = MaterialTheme.colorScheme.inversePrimary + ) + } + BackHandler(zoomState.isScaled) { coroutineScope.launch { zoomState.reset() } } @@ -121,12 +167,14 @@ private fun GalleryScreenContent( IconButton( onClick = onBackClick, - modifier = Modifier.constrainAs(backIcon) { - width = Dimension.wrapContent - height = Dimension.wrapContent - start.linkTo(parent.start, 4.dp) - top.linkTo(parent.top, 8.dp) - }.statusBarsPadding() + modifier = Modifier + .constrainAs(backIcon) { + width = Dimension.wrapContent + height = Dimension.wrapContent + start.linkTo(parent.start, 4.dp) + top.linkTo(parent.top, 8.dp) + } + .statusBarsPadding() ) { Image( imageVector = MoviesIcons.ArrowBack, @@ -134,34 +182,42 @@ private fun GalleryScreenContent( colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onPrimaryContainer) ) } + + Text( + text = "", + modifier = Modifier + .constrainAs(title) { + width = Dimension.wrapContent + height = Dimension.wrapContent + start.linkTo(backIcon.end, 4.dp) + top.linkTo(backIcon.top) + bottom.linkTo(backIcon.bottom) + } + .statusBarsPadding(), + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = MaterialTheme.typography.titleLarge.copy( + color = MaterialTheme.colorScheme.onPrimaryContainer + ) + ) } } @Composable private fun LoopHorizontalPager( - count: Int, + pagerState: PagerState, modifier: Modifier = Modifier, contentPadding: PaddingValues = PaddingValues(0.dp), content: @Composable PagerScope.(page: Int) -> Unit, ) { - val startIndex: Int = Int.MAX_VALUE / 2 - val pagerState = rememberPagerState( - initialPage = startIndex, - initialPageOffsetFraction = 0F, - pageCount = { count } - ) HorizontalPager( state = pagerState, modifier = modifier, contentPadding = contentPadding, + pageSpacing = 8.dp, + flingBehavior = PagerDefaults.flingBehavior(state = pagerState), pageContent = { index -> - val page = (index - startIndex).floorMod(count) - content(page) + content(index) } ) -} - -private fun Int.floorMod(other: Int): Int = when (other) { - 0 -> this - else -> this - floorDiv(other) * other } \ No newline at end of file diff --git a/feature/gallery-impl/src/main/kotlin/org/michaelbel/movies/gallery/zoomable/ZoomState.kt b/feature/gallery-impl/src/main/kotlin/org/michaelbel/movies/gallery/zoomable/ZoomState.kt index 8a0901b6a..580a8393f 100644 --- a/feature/gallery-impl/src/main/kotlin/org/michaelbel/movies/gallery/zoomable/ZoomState.kt +++ b/feature/gallery-impl/src/main/kotlin/org/michaelbel/movies/gallery/zoomable/ZoomState.kt @@ -32,12 +32,12 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect import androidx.compose.ui.geometry.Size import androidx.compose.ui.input.pointer.util.VelocityTracker -import java.lang.Float.max -import kotlin.math.abs import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch +import java.lang.Float.max +import kotlin.math.abs /** * A state object that manage scale and offset. @@ -295,7 +295,6 @@ class ZoomState( } suspend fun endGesture() = coroutineScope { - Log.e("2", "endGesture") val velocity = velocityTracker.calculateVelocity() if (velocity.x != 0f) { launch { diff --git a/feature/gallery/build.gradle.kts b/feature/gallery/build.gradle.kts index 88a2c0734..9496764a9 100644 --- a/feature/gallery/build.gradle.kts +++ b/feature/gallery/build.gradle.kts @@ -2,7 +2,6 @@ plugins { alias(libs.plugins.library) alias(libs.plugins.kotlin) - alias(libs.plugins.detekt) } android { diff --git a/feature/settings-impl/build.gradle.kts b/feature/settings-impl/build.gradle.kts index 061a7c399..44e50be35 100644 --- a/feature/settings-impl/build.gradle.kts +++ b/feature/settings-impl/build.gradle.kts @@ -2,7 +2,6 @@ plugins { alias(libs.plugins.library) alias(libs.plugins.kotlin) - alias(libs.plugins.detekt) id("movies-android-hilt") } @@ -50,13 +49,17 @@ android { checkDependencies = true lintConfig = file("${project.rootDir}/config/codestyle/lint.xml") } + + kapt { + correctErrorTypes = true + } } dependencies { api(project(":core:navigation")) api(project(":core:common")) api(project(":core:ui")) - implementation(project(":core:domain")) + implementation(project(":core:interactor")) implementation(project(":core:notifications")) testImplementation(libs.junit) diff --git a/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsVersionBoxTest.kt b/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsVersionBoxTest.kt index 5ca7e95aa..87f7a88d7 100644 --- a/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsVersionBoxTest.kt +++ b/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsVersionBoxTest.kt @@ -53,6 +53,10 @@ internal class SettingsVersionBoxTest { } private companion object { - private val APP_VERSION_DATA: AppVersionData = AppVersionData("1.0.0", 1L) + private val APP_VERSION_DATA: AppVersionData = AppVersionData( + version = "1.0.0", + code = 1L, + isDebug = true + ) } } \ No newline at end of file diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/SettingsViewModel.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/SettingsViewModel.kt index 05248d38a..ce34f5bdf 100644 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/SettingsViewModel.kt +++ b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/SettingsViewModel.kt @@ -98,7 +98,7 @@ class SettingsViewModel @Inject constructor( .stateIn( scope = this, started = SharingStarted.Lazily, - initialValue = AppVersionData.None + initialValue = AppVersionData.Empty ) fun selectLanguage(language: AppLanguage) = launch { diff --git a/feature/settings/build.gradle.kts b/feature/settings/build.gradle.kts index 1f2a4bac4..c11ef7cbf 100644 --- a/feature/settings/build.gradle.kts +++ b/feature/settings/build.gradle.kts @@ -2,7 +2,6 @@ plugins { alias(libs.plugins.library) alias(libs.plugins.kotlin) - alias(libs.plugins.detekt) } android { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index eec81feb8..ddf34a4bb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ # Compose to Kotlin Compatibility Map: # https://d.android.com/jetpack/androidx/releases/compose-kotlin [versions] -min-sdk = "21" +min-sdk = "23" benchmark-min-sdk = "23" compile-sdk = "34" target-sdk = "34" diff --git a/settings.gradle.kts b/settings.gradle.kts index c6dd8945f..3336a6870 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -23,8 +23,6 @@ include( ":core:analytics", ":core:common", - ":core:domain", - ":core:entities", ":core:interactor", ":core:interactor-impl", ":core:navigation", @@ -34,6 +32,7 @@ include( ":core:repository", ":core:repository-impl", ":core:ui", + ":core:work", ":feature:account", ":feature:account-impl",