diff --git a/.github/workflows/check_pr.yml b/.github/workflows/check_pr.yml index 52399736b..268149ec1 100644 --- a/.github/workflows/check_pr.yml +++ b/.github/workflows/check_pr.yml @@ -40,11 +40,11 @@ jobs: - name: Validate Gradle Wrapper uses: gradle/wrapper-validation-action@v1 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: distribution: 'zulu' - java-version: 11 + java-version: 17 - name: Setup Gradle uses: gradle/gradle-build-action@v2 diff --git a/.gitignore b/.gitignore index daeda5d1d..513558f29 100644 --- a/.gitignore +++ b/.gitignore @@ -708,3 +708,7 @@ /.gradle/7.6/checksums/sha1-checksums.bin /buildSrc/.gradle/7.6/checksums/sha1-checksums.bin /config/movies.jks +/.gradle/ +/.idea/ +/buildSrc/build/classes/kotlin/main/ktx/ +/buildSrc/.gradle/ diff --git a/app/build.gradle.kts b/app/build.gradle.kts index dc74f9e80..95d799a33 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,17 +1,18 @@ import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties -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 { - id("movies-android-application") - id("movies-android-application-compose") + alias(libs.plugins.application) + alias(libs.plugins.kotlin) + alias(libs.plugins.androidx.navigation.safeargs) + alias(libs.plugins.google.services) + alias(libs.plugins.firebase.appdistribution) + alias(libs.plugins.firebase.crashlytics) + alias(libs.plugins.palantir.git) id("movies-android-hilt") - id("androidx.navigation.safeargs") - id("com.google.gms.google-services") - id("com.google.firebase.appdistribution") - id("com.google.firebase.crashlytics") - id("com.palantir.git-version") } val gitCommitsCount: Int by lazy { @@ -27,11 +28,11 @@ val currentTime: Long by lazy { System.currentTimeMillis() } -val admobAppId: String by lazy { +val admobAppId: String? by lazy { gradleLocalProperties(rootDir).getProperty("ADMOB_APP_ID") } -val admobBannerId: String by lazy { +val admobBannerId: String? by lazy { gradleLocalProperties(rootDir).getProperty("ADMOB_BANNER_ID") } @@ -129,6 +130,7 @@ android { } buildFeatures { + buildConfig = true compose = true } @@ -136,6 +138,11 @@ android { kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get() } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + lint { quiet = true abortOnError = false @@ -165,4 +172,6 @@ dependencies { androidTestImplementation(libs.androidx.espresso.core) androidTestImplementation(libs.androidx.compose.ui.test.junit4) androidTestImplementation(libs.androidx.benchmark.junit) + + lintChecks(libs.lint.checks) } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index fa5ae7e48..a5ba5e8e8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -95,9 +95,9 @@ - + android:value="@string/admobAppId" />--> () - .setInputData(workDataOf(MoviesDatabaseWorker.KEY_FILENAME to MOVIES_DATA_FILENAME)) - .build() - WorkManager.getInstance(this).enqueue(request) - } - - private companion object { - private const val MOVIES_DATA_FILENAME = "movies.json" } } \ 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 d102e541a..29e61d73c 100644 --- a/app/src/main/kotlin/org/michaelbel/movies/MainViewModel.kt +++ b/app/src/main/kotlin/org/michaelbel/movies/MainViewModel.kt @@ -3,7 +3,11 @@ package org.michaelbel.movies import android.os.Bundle import androidx.compose.ui.unit.LayoutDirection import androidx.navigation.NavDestination +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.WorkManager +import androidx.work.workDataOf 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 @@ -12,11 +16,13 @@ import org.michaelbel.movies.analytics.MoviesAnalytics import org.michaelbel.movies.common.theme.AppTheme import org.michaelbel.movies.common.viewmodel.BaseViewModel import org.michaelbel.movies.domain.interactor.settings.SettingsInteractor -import javax.inject.Inject +import org.michaelbel.movies.domain.workers.AccountUpdateWorker +import org.michaelbel.movies.domain.workers.MoviesDatabaseWorker @HiltViewModel internal class MainViewModel @Inject constructor( private val settingsInteractor: SettingsInteractor, + private val workManager: WorkManager, private val analytics: MoviesAnalytics ): BaseViewModel() { @@ -43,6 +49,8 @@ internal class MainViewModel @Inject constructor( init { fetchRemoteConfig() + prepopulateDatabase() + updateAccountDetails() } fun analyticsTrackDestination(destination: NavDestination, arguments: Bundle?) { @@ -52,4 +60,21 @@ internal class MainViewModel @Inject constructor( private fun fetchRemoteConfig() = launch { settingsInteractor.fetchRemoteConfig() } + + 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" + } } \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 80a98f15d..4a6ea330e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,4 @@ +@Suppress("dsl_scope_violation") plugins { alias(libs.plugins.application) apply false alias(libs.plugins.library) apply false @@ -12,10 +13,9 @@ plugins { alias(libs.plugins.hilt) apply false alias(libs.plugins.spotless) alias(libs.plugins.detekt) - - id("com.github.ben-manes.versions") version "0.46.0" - id("nl.littlerobots.version-catalog-update") version "0.8.0" - id("com.palantir.git-version") version "2.0.0" + alias(libs.plugins.palantir.git) + alias(libs.plugins.ben.manes.versions) + alias(libs.plugins.littlerobots.version.catalog.update) } /** diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 8a68b018b..7a034a208 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -10,22 +10,6 @@ dependencies { gradlePlugin { plugins { - register("androidApplication") { - id = "movies-android-application" - implementationClass = "plugins.AndroidApplicationConventionPlugin" - } - register("androidLibrary") { - id = "movies-android-library" - implementationClass = "plugins.AndroidLibraryConventionPlugin" - } - register("androidComposeApplication") { - id = "movies-android-application-compose" - implementationClass = "plugins.AndroidComposeApplicationConventionPlugin" - } - register("androidComposeLibrary") { - id = "movies-android-library-compose" - implementationClass = "plugins.AndroidComposeLibraryConventionPlugin" - } register("androidHilt") { id = "movies-android-hilt" implementationClass = "plugins.AndroidHiltConventionPlugin" diff --git a/buildSrc/src/main/kotlin/plugins/AndroidApplicationConventionPlugin.kt b/buildSrc/src/main/kotlin/plugins/AndroidApplicationConventionPlugin.kt deleted file mode 100644 index a161e33f6..000000000 --- a/buildSrc/src/main/kotlin/plugins/AndroidApplicationConventionPlugin.kt +++ /dev/null @@ -1,23 +0,0 @@ -package plugins - -import org.gradle.api.Plugin -import org.gradle.api.Project - -internal class AndroidApplicationConventionPlugin: Plugin { - - override fun apply(target: Project) { - target.run { - pluginManager.run { - apply("com.android.application") - apply("org.jetbrains.kotlin.android") - } - - /*val libs: VersionCatalog = extensions.getByType().named("libs") - extensions.configure { - defaultConfig.targetSdk = libs.findVersion("target-sdk").get().requiredVersion.toInt() - configureKotlinAndroid(this) - configureLintCheck(this) - }*/ - } - } -} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/plugins/AndroidComposeApplicationConventionPlugin.kt b/buildSrc/src/main/kotlin/plugins/AndroidComposeApplicationConventionPlugin.kt deleted file mode 100644 index 2f8359cdf..000000000 --- a/buildSrc/src/main/kotlin/plugins/AndroidComposeApplicationConventionPlugin.kt +++ /dev/null @@ -1,18 +0,0 @@ -package plugins - -import org.gradle.api.Plugin -import org.gradle.api.Project - -internal class AndroidComposeApplicationConventionPlugin: Plugin { - - override fun apply(target: Project) { - target.run { - pluginManager.run { - apply("com.android.application") - } - - /*val extension: ApplicationExtension = extensions.getByType() - configureAndroidCompose(extension)*/ - } - } -} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/plugins/AndroidComposeLibraryConventionPlugin.kt b/buildSrc/src/main/kotlin/plugins/AndroidComposeLibraryConventionPlugin.kt deleted file mode 100644 index 60265dd9c..000000000 --- a/buildSrc/src/main/kotlin/plugins/AndroidComposeLibraryConventionPlugin.kt +++ /dev/null @@ -1,18 +0,0 @@ -package plugins - -import org.gradle.api.Plugin -import org.gradle.api.Project - -internal class AndroidComposeLibraryConventionPlugin: Plugin { - - override fun apply(target: Project) { - target.run { - pluginManager.run { - apply("com.android.library") - } - - /*val extension: LibraryExtension = extensions.getByType() - configureAndroidCompose(extension)*/ - } - } -} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/plugins/AndroidLibraryConventionPlugin.kt b/buildSrc/src/main/kotlin/plugins/AndroidLibraryConventionPlugin.kt deleted file mode 100644 index cb53208f0..000000000 --- a/buildSrc/src/main/kotlin/plugins/AndroidLibraryConventionPlugin.kt +++ /dev/null @@ -1,23 +0,0 @@ -package plugins - -import org.gradle.api.Plugin -import org.gradle.api.Project - -internal class AndroidLibraryConventionPlugin: Plugin { - - override fun apply(target: Project) { - target.run { - pluginManager.run { - apply("com.android.library") - apply("org.jetbrains.kotlin.android") - } - - /*val libs: VersionCatalog = extensions.getByType().named("libs") - extensions.configure { - defaultConfig.targetSdk = libs.findVersion("target-sdk").get().requiredVersion.toInt() - configureKotlinAndroid(this) - configureLintCheck(this) - }*/ - } - } -} \ No newline at end of file diff --git a/core/ads/build.gradle.kts b/core/ads/build.gradle.kts index 3ba6212f7..86b545ec5 100644 --- a/core/ads/build.gradle.kts +++ b/core/ads/build.gradle.kts @@ -1,5 +1,7 @@ +@Suppress("dsl_scope_violation") plugins { - id("movies-android-library") + alias(libs.plugins.library) + alias(libs.plugins.kotlin) } android { @@ -18,6 +20,11 @@ android { } } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + lint { quiet = true abortOnError = false @@ -28,5 +35,5 @@ android { } dependencies { - api(libs.play.services.ads) + /*api(libs.play.services.ads)*/ } \ No newline at end of file diff --git a/core/analytics/build.gradle.kts b/core/analytics/build.gradle.kts index c07323583..5f0b79d1f 100644 --- a/core/analytics/build.gradle.kts +++ b/core/analytics/build.gradle.kts @@ -1,5 +1,7 @@ +@Suppress("dsl_scope_violation") plugins { - id("movies-android-library") + alias(libs.plugins.library) + alias(libs.plugins.kotlin) id("movies-android-hilt") } @@ -19,6 +21,11 @@ android { } } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + lint { quiet = true abortOnError = false diff --git a/core/common/build.gradle.kts b/core/common/build.gradle.kts index f94e4f573..2a7c77555 100644 --- a/core/common/build.gradle.kts +++ b/core/common/build.gradle.kts @@ -1,6 +1,7 @@ +@Suppress("dsl_scope_violation") plugins { - id("movies-android-library") - id("movies-android-library-compose") + alias(libs.plugins.library) + alias(libs.plugins.kotlin) id("movies-android-hilt") } @@ -29,6 +30,7 @@ android { } buildFeatures { + buildConfig = true compose = true } @@ -36,6 +38,11 @@ android { kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get() } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + lint { quiet = true abortOnError = false @@ -60,4 +67,6 @@ dependencies { implementation(libs.firebase.crashlytics) implementation(libs.androidx.startup.runtime) implementation(libs.androidx.browser) + + lintChecks(libs.lint.checks) } \ No newline at end of file diff --git a/core/common/src/main/AndroidManifest.xml b/core/common/src/main/AndroidManifest.xml index 1a9db05ab..07a9a6fc2 100644 --- a/core/common/src/main/AndroidManifest.xml +++ b/core/common/src/main/AndroidManifest.xml @@ -11,9 +11,9 @@ android:exported="false" tools:node="merge"> - + android:value="androidx.startup"/>--> { override fun create(context: Context): FirebaseCrashlytics { @@ -18,4 +11,4 @@ internal class FirebaseCrashlyticsInitializer: Initializer } override fun dependencies(): List>> = emptyList() -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/dispatchers/MoviesDispatchers.kt b/core/common/src/main/kotlin/org/michaelbel/movies/common/dispatchers/MoviesDispatchers.kt new file mode 100644 index 000000000..acab66710 --- /dev/null +++ b/core/common/src/main/kotlin/org/michaelbel/movies/common/dispatchers/MoviesDispatchers.kt @@ -0,0 +1,10 @@ +package org.michaelbel.movies.common.dispatchers + +import kotlinx.coroutines.CoroutineDispatcher + +interface MoviesDispatchers { + val default: CoroutineDispatcher + val io: CoroutineDispatcher + val main: CoroutineDispatcher + val immediate: CoroutineDispatcher +} \ No newline at end of file diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/dispatchers/di/DispatchersModule.kt b/core/common/src/main/kotlin/org/michaelbel/movies/common/dispatchers/di/DispatchersModule.kt new file mode 100644 index 000000000..7ebc76d0c --- /dev/null +++ b/core/common/src/main/kotlin/org/michaelbel/movies/common/dispatchers/di/DispatchersModule.kt @@ -0,0 +1,18 @@ +package org.michaelbel.movies.common.dispatchers.di + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import org.michaelbel.movies.common.dispatchers.MoviesDispatchers +import org.michaelbel.movies.common.dispatchers.impl.MoviesDispatchersImpl + +@Module +@InstallIn(SingletonComponent::class) +internal interface DispatchersModule { + + @Binds + fun provideDispatchers( + dispatchers: MoviesDispatchersImpl + ): MoviesDispatchers +} \ No newline at end of file diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/dispatchers/impl/MoviesDispatchersImpl.kt b/core/common/src/main/kotlin/org/michaelbel/movies/common/dispatchers/impl/MoviesDispatchersImpl.kt new file mode 100644 index 000000000..5ac749b12 --- /dev/null +++ b/core/common/src/main/kotlin/org/michaelbel/movies/common/dispatchers/impl/MoviesDispatchersImpl.kt @@ -0,0 +1,21 @@ +package org.michaelbel.movies.common.dispatchers.impl + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import org.michaelbel.movies.common.dispatchers.MoviesDispatchers +import javax.inject.Inject + +internal class MoviesDispatchersImpl @Inject constructor(): MoviesDispatchers { + + override val default: CoroutineDispatcher + get() = Dispatchers.Default + + override val io: CoroutineDispatcher + get() = Dispatchers.IO + + override val main: CoroutineDispatcher + get() = Dispatchers.Main + + override val immediate: CoroutineDispatcher + get() = Dispatchers.Main.immediate +} \ No newline at end of file diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/ktx/TimeKtx.kt b/core/common/src/main/kotlin/org/michaelbel/movies/common/ktx/TimeKtx.kt new file mode 100644 index 000000000..2f07f62ef --- /dev/null +++ b/core/common/src/main/kotlin/org/michaelbel/movies/common/ktx/TimeKtx.kt @@ -0,0 +1,5 @@ +package org.michaelbel.movies.common.ktx + +fun isTimePasses(interval: Long, expireTime: Long, currentTime: Long): Boolean { + return (currentTime - expireTime) >= interval +} \ No newline at end of file diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/localization/impl/LocaleControllerImpl.kt b/core/common/src/main/kotlin/org/michaelbel/movies/common/localization/impl/LocaleControllerImpl.kt index e40612a02..4968d5c4f 100644 --- a/core/common/src/main/kotlin/org/michaelbel/movies/common/localization/impl/LocaleControllerImpl.kt +++ b/core/common/src/main/kotlin/org/michaelbel/movies/common/localization/impl/LocaleControllerImpl.kt @@ -2,20 +2,18 @@ package org.michaelbel.movies.common.localization.impl import androidx.appcompat.app.AppCompatDelegate import androidx.core.os.LocaleListCompat -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.withContext import org.michaelbel.movies.analytics.MoviesAnalytics import org.michaelbel.movies.analytics.event.SelectLanguageEvent -import org.michaelbel.movies.common.coroutines.Dispatcher -import org.michaelbel.movies.common.coroutines.MoviesDispatchers +import org.michaelbel.movies.common.dispatchers.MoviesDispatchers import org.michaelbel.movies.common.localization.LocaleController import org.michaelbel.movies.common.localization.model.AppLanguage import javax.inject.Inject internal class LocaleControllerImpl @Inject constructor( - @Dispatcher(MoviesDispatchers.Main) private val dispatcher: CoroutineDispatcher, + private val dispatchers: MoviesDispatchers, private val analytics: MoviesAnalytics ): LocaleController { @@ -24,7 +22,7 @@ internal class LocaleControllerImpl @Inject constructor( override val appLanguage: Flow = flowOf(AppLanguage.transform(language)) - override suspend fun selectLanguage(language: AppLanguage) = withContext(dispatcher) { + override suspend fun selectLanguage(language: AppLanguage) = withContext(dispatchers.io) { AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(language.code)) analytics.logEvent(SelectLanguageEvent(language.toString())) } diff --git a/core/common/src/main/kotlin/org/michaelbel/movies/common/timber/TimberInitializer.kt b/core/common/src/main/kotlin/org/michaelbel/movies/common/timber/TimberInitializer.kt index e4b71108a..07fdcbcdc 100644 --- a/core/common/src/main/kotlin/org/michaelbel/movies/common/timber/TimberInitializer.kt +++ b/core/common/src/main/kotlin/org/michaelbel/movies/common/timber/TimberInitializer.kt @@ -4,7 +4,6 @@ import android.content.Context import androidx.startup.Initializer import org.michaelbel.movies.common.BuildConfig import org.michaelbel.movies.common.crashlytics.CrashlyticsTree -import org.michaelbel.movies.common.crashlytics.FirebaseCrashlyticsInitializer import timber.log.Timber @Suppress("unused") @@ -15,6 +14,6 @@ internal class TimberInitializer: Initializer { } override fun dependencies(): List>> { - return listOf(FirebaseCrashlyticsInitializer::class.java) + return emptyList() /*listOf(FirebaseCrashlyticsInitializer::class.java)*/ } } \ No newline at end of file diff --git a/core/domain/build.gradle.kts b/core/domain/build.gradle.kts index 0655170e6..ad78eb065 100644 --- a/core/domain/build.gradle.kts +++ b/core/domain/build.gradle.kts @@ -1,7 +1,9 @@ +@Suppress("dsl_scope_violation") plugins { - id("movies-android-library") + alias(libs.plugins.library) + alias(libs.plugins.kotlin) + alias(libs.plugins.kotlin.ksp) id("movies-android-hilt") - id("com.google.devtools.ksp") } android { @@ -28,6 +30,11 @@ android { ) } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + lint { quiet = true abortOnError = false @@ -45,7 +52,6 @@ dependencies { implementation(libs.bundles.datastore) implementation(libs.bundles.room) api(libs.androidx.hilt.work) - kapt(libs.androidx.hilt.compiler) api(libs.androidx.work.runtime.ktx) ksp(libs.androidx.room.compiler) } \ No newline at end of file diff --git a/core/domain/src/main/kotlin/org/michaelbel/movies/domain/data/AppDatabase.kt b/core/domain/src/main/kotlin/org/michaelbel/movies/domain/data/AppDatabase.kt index bb4702c9e..82fcb9be1 100644 --- a/core/domain/src/main/kotlin/org/michaelbel/movies/domain/data/AppDatabase.kt +++ b/core/domain/src/main/kotlin/org/michaelbel/movies/domain/data/AppDatabase.kt @@ -26,7 +26,7 @@ import org.michaelbel.movies.domain.data.entity.PagingKeyDb exportSchema = false ) @TypeConverters(CalendarConverter::class) -abstract class AppDatabase: RoomDatabase() { +internal abstract class AppDatabase: RoomDatabase() { abstract fun movieDao(): MovieDao abstract fun accountDao(): AccountDao diff --git a/core/domain/src/main/kotlin/org/michaelbel/movies/domain/data/dao/AccountDao.kt b/core/domain/src/main/kotlin/org/michaelbel/movies/domain/data/dao/AccountDao.kt index 34f27b533..42266d15a 100644 --- a/core/domain/src/main/kotlin/org/michaelbel/movies/domain/data/dao/AccountDao.kt +++ b/core/domain/src/main/kotlin/org/michaelbel/movies/domain/data/dao/AccountDao.kt @@ -11,7 +11,7 @@ import org.michaelbel.movies.domain.data.entity.AccountDb * The Data Access Object for the [AccountDb] class. */ @Dao -interface AccountDao { +internal interface AccountDao { @Query("SELECT * FROM accounts WHERE id = :accountId") fun accountById(accountId: Int): Flow diff --git a/core/domain/src/main/kotlin/org/michaelbel/movies/domain/data/dao/MovieDao.kt b/core/domain/src/main/kotlin/org/michaelbel/movies/domain/data/dao/MovieDao.kt index 033ac26a0..398fa5752 100644 --- a/core/domain/src/main/kotlin/org/michaelbel/movies/domain/data/dao/MovieDao.kt +++ b/core/domain/src/main/kotlin/org/michaelbel/movies/domain/data/dao/MovieDao.kt @@ -25,7 +25,7 @@ interface MovieDao { @Query("SELECT * FROM movies WHERE id = :movieId") suspend fun movieById(movieId: Int): MovieDb? - @Query("SELECT MAX(position) from movies WHERE movieList = :movieList") + @Query("SELECT MAX(position) FROM movies WHERE movieList = :movieList") suspend fun maxPosition(movieList: String): Int? @Query("SELECT COUNT(*) FROM movies WHERE movieList = :movieList") diff --git a/core/domain/src/main/kotlin/org/michaelbel/movies/domain/data/dao/PagingKeyDao.kt b/core/domain/src/main/kotlin/org/michaelbel/movies/domain/data/dao/PagingKeyDao.kt index 3ee023d4f..7ba0c5392 100644 --- a/core/domain/src/main/kotlin/org/michaelbel/movies/domain/data/dao/PagingKeyDao.kt +++ b/core/domain/src/main/kotlin/org/michaelbel/movies/domain/data/dao/PagingKeyDao.kt @@ -10,7 +10,7 @@ import org.michaelbel.movies.domain.data.entity.PagingKeyDb * The Data Access Object for the [PagingKeyDb] class. */ @Dao -interface PagingKeyDao { +internal interface PagingKeyDao { @Query("SELECT * FROM pagingkeys WHERE movieList = :movieList") suspend fun pagingKey(movieList: String): PagingKeyDb? diff --git a/core/domain/src/main/kotlin/org/michaelbel/movies/domain/interactor/account/impl/AccountInteractorImpl.kt b/core/domain/src/main/kotlin/org/michaelbel/movies/domain/interactor/account/impl/AccountInteractorImpl.kt index 42e5e731e..d1d60174b 100644 --- a/core/domain/src/main/kotlin/org/michaelbel/movies/domain/interactor/account/impl/AccountInteractorImpl.kt +++ b/core/domain/src/main/kotlin/org/michaelbel/movies/domain/interactor/account/impl/AccountInteractorImpl.kt @@ -1,21 +1,19 @@ package org.michaelbel.movies.domain.interactor.account.impl -import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.withContext -import org.michaelbel.movies.common.coroutines.Dispatcher -import org.michaelbel.movies.common.coroutines.MoviesDispatchers +import org.michaelbel.movies.common.dispatchers.MoviesDispatchers import org.michaelbel.movies.domain.data.entity.AccountDb import org.michaelbel.movies.domain.interactor.account.AccountInteractor import org.michaelbel.movies.domain.repository.account.AccountRepository +import org.michaelbel.movies.domain.usecase.DelayUseCase import javax.inject.Inject import javax.inject.Singleton -import kotlinx.coroutines.delay -import org.michaelbel.movies.domain.usecase.DelayUseCase @Singleton internal class AccountInteractorImpl @Inject constructor( - @Dispatcher(MoviesDispatchers.IO) private val dispatcher: CoroutineDispatcher, + private val dispatchers: MoviesDispatchers, private val accountRepository: AccountRepository, private val delayUseCase: DelayUseCase ): AccountInteractor { @@ -25,6 +23,6 @@ internal class AccountInteractorImpl @Inject constructor( override suspend fun accountDetails() { delay(delayUseCase.networkRequestDelay()) - return withContext(dispatcher) { accountRepository.accountDetails() } + return withContext(dispatchers.io) { accountRepository.accountDetails() } } } \ No newline at end of file diff --git a/core/domain/src/main/kotlin/org/michaelbel/movies/domain/interactor/authentication/impl/AuthenticationInteractorImpl.kt b/core/domain/src/main/kotlin/org/michaelbel/movies/domain/interactor/authentication/impl/AuthenticationInteractorImpl.kt index 0ddf186cb..c52e8c803 100644 --- a/core/domain/src/main/kotlin/org/michaelbel/movies/domain/interactor/authentication/impl/AuthenticationInteractorImpl.kt +++ b/core/domain/src/main/kotlin/org/michaelbel/movies/domain/interactor/authentication/impl/AuthenticationInteractorImpl.kt @@ -1,27 +1,25 @@ package org.michaelbel.movies.domain.interactor.authentication.impl -import javax.inject.Inject -import javax.inject.Singleton -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.delay import kotlinx.coroutines.withContext -import org.michaelbel.movies.common.coroutines.Dispatcher -import org.michaelbel.movies.common.coroutines.MoviesDispatchers +import org.michaelbel.movies.common.dispatchers.MoviesDispatchers import org.michaelbel.movies.domain.interactor.authentication.AuthenticationInteractor import org.michaelbel.movies.domain.repository.authentication.AuthenticationRepository import org.michaelbel.movies.domain.usecase.DelayUseCase import org.michaelbel.movies.network.model.Session import org.michaelbel.movies.network.model.Token +import javax.inject.Inject +import javax.inject.Singleton @Singleton internal class AuthenticationInteractorImpl @Inject constructor( - @Dispatcher(MoviesDispatchers.IO) private val dispatcher: CoroutineDispatcher, + private val dispatchers: MoviesDispatchers, private val authenticationRepository: AuthenticationRepository, private val delayUseCase: DelayUseCase ): AuthenticationInteractor { override suspend fun createRequestToken(): Token { - return withContext(dispatcher) { authenticationRepository.createRequestToken() } + return withContext(dispatchers.io) { authenticationRepository.createRequestToken() } } override suspend fun createSessionWithLogin( @@ -29,13 +27,13 @@ internal class AuthenticationInteractorImpl @Inject constructor( password: String, requestToken: String ): Token { - return withContext(dispatcher) { + return withContext(dispatchers.io) { authenticationRepository.createSessionWithLogin(username, password, requestToken) } } override suspend fun createSession(token: String): Session { - return withContext(dispatcher) { + return withContext(dispatchers.io) { authenticationRepository.createSession(token) } } @@ -43,7 +41,7 @@ internal class AuthenticationInteractorImpl @Inject constructor( override suspend fun deleteSession() { delay(delayUseCase.networkRequestDelay()) - return withContext(dispatcher) { + return withContext(dispatchers.io) { authenticationRepository.deleteSession() } } diff --git a/core/domain/src/main/kotlin/org/michaelbel/movies/domain/interactor/movie/impl/MovieInteractorImpl.kt b/core/domain/src/main/kotlin/org/michaelbel/movies/domain/interactor/movie/impl/MovieInteractorImpl.kt index 6e7c0c074..5771b2351 100644 --- a/core/domain/src/main/kotlin/org/michaelbel/movies/domain/interactor/movie/impl/MovieInteractorImpl.kt +++ b/core/domain/src/main/kotlin/org/michaelbel/movies/domain/interactor/movie/impl/MovieInteractorImpl.kt @@ -1,13 +1,9 @@ package org.michaelbel.movies.domain.interactor.movie.impl import androidx.paging.PagingSource -import javax.inject.Inject -import javax.inject.Singleton -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.delay import kotlinx.coroutines.withContext -import org.michaelbel.movies.common.coroutines.Dispatcher -import org.michaelbel.movies.common.coroutines.MoviesDispatchers +import org.michaelbel.movies.common.dispatchers.MoviesDispatchers import org.michaelbel.movies.domain.data.entity.MovieDb import org.michaelbel.movies.domain.interactor.movie.MovieInteractor import org.michaelbel.movies.domain.repository.movie.MovieRepository @@ -15,10 +11,12 @@ import org.michaelbel.movies.domain.usecase.DelayUseCase import org.michaelbel.movies.entities.Either import org.michaelbel.movies.network.model.MovieResponse import org.michaelbel.movies.network.model.Result +import javax.inject.Inject +import javax.inject.Singleton @Singleton internal class MovieInteractorImpl @Inject constructor( - @Dispatcher(MoviesDispatchers.IO) private val dispatcher: CoroutineDispatcher, + private val dispatchers: MoviesDispatchers, private val movieRepository: MovieRepository, private val delayUseCase: DelayUseCase ): MovieInteractor { @@ -30,7 +28,7 @@ internal class MovieInteractorImpl @Inject constructor( override suspend fun moviesResult(movieList: String, page: Int): Result { delay(delayUseCase.networkRequestDelay()) - return withContext(dispatcher) { + return withContext(dispatchers.io) { movieRepository.moviesResult(movieList, page) } } @@ -38,37 +36,37 @@ internal class MovieInteractorImpl @Inject constructor( override suspend fun movieDetails(movieId: Int): Either { delay(delayUseCase.networkRequestDelay()) - return withContext(dispatcher) { + return withContext(dispatchers.io) { movieRepository.movieDetails(movieId) } } override suspend fun removeAllMovies(movieList: String) { - return withContext(dispatcher) { + return withContext(dispatchers.io) { movieRepository.removeAllMovies(movieList) } } override suspend fun insertAllMovies(movieList: String, movies: List) { - return withContext(dispatcher) { + return withContext(dispatchers.io) { movieRepository.insertAllMovies(movieList, movies) } } override suspend fun page(movieList: String): Int? { - return withContext(dispatcher) { + return withContext(dispatchers.io) { movieRepository.page(movieList) } } override suspend fun removePagingKey(movieList: String) { - return withContext(dispatcher) { + return withContext(dispatchers.io) { movieRepository.removePagingKey(movieList) } } override suspend fun insertPagingKey(movieList: String, page: Int) { - return withContext(dispatcher) { + return withContext(dispatchers.io) { movieRepository.insertPagingKey(movieList, page) } } diff --git a/core/domain/src/main/kotlin/org/michaelbel/movies/domain/interactor/settings/impl/SettingsInteractorImpl.kt b/core/domain/src/main/kotlin/org/michaelbel/movies/domain/interactor/settings/impl/SettingsInteractorImpl.kt index f133b9d7c..4f6585f93 100644 --- a/core/domain/src/main/kotlin/org/michaelbel/movies/domain/interactor/settings/impl/SettingsInteractorImpl.kt +++ b/core/domain/src/main/kotlin/org/michaelbel/movies/domain/interactor/settings/impl/SettingsInteractorImpl.kt @@ -2,7 +2,6 @@ package org.michaelbel.movies.domain.interactor.settings.impl import androidx.compose.ui.unit.LayoutDirection import com.google.firebase.remoteconfig.FirebaseRemoteConfig -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.withContext @@ -11,8 +10,7 @@ import org.michaelbel.movies.analytics.event.ChangeDynamicColorsEvent import org.michaelbel.movies.analytics.event.ChangeRtlEnabledEvent import org.michaelbel.movies.analytics.event.SelectThemeEvent import org.michaelbel.movies.common.config.RemoteParams -import org.michaelbel.movies.common.coroutines.Dispatcher -import org.michaelbel.movies.common.coroutines.MoviesDispatchers +import org.michaelbel.movies.common.dispatchers.MoviesDispatchers import org.michaelbel.movies.common.googleapi.GoogleApi import org.michaelbel.movies.common.theme.AppTheme import org.michaelbel.movies.common.version.AppVersionData @@ -24,7 +22,7 @@ import javax.inject.Singleton @Singleton internal class SettingsInteractorImpl @Inject constructor( - @Dispatcher(MoviesDispatchers.Main) private val dispatcher: CoroutineDispatcher, + private val dispatchers: MoviesDispatchers, private val settingsRepository: SettingsRepository, private val firebaseRemoteConfig: FirebaseRemoteConfig, googleApi: GoogleApi, @@ -47,23 +45,23 @@ internal class SettingsInteractorImpl @Inject constructor( override val appVersionData: Flow = settingsRepository.appVersionData - override suspend fun selectTheme(theme: AppTheme) = withContext(dispatcher) { + override suspend fun selectTheme(theme: AppTheme) = withContext(dispatchers.main) { settingsRepository.selectTheme(theme) analytics.logEvent(SelectThemeEvent(theme.toString())) } - override suspend fun setDynamicColors(value: Boolean) = withContext(dispatcher) { + override suspend fun setDynamicColors(value: Boolean) = withContext(dispatchers.main) { settingsRepository.setDynamicColors(value) analytics.logEvent(ChangeDynamicColorsEvent(value)) } - override suspend fun setRtlEnabled(value: Boolean) = withContext(dispatcher) { + override suspend fun setRtlEnabled(value: Boolean) = withContext(dispatchers.main) { settingsRepository.setRtlEnabled(value) analytics.logEvent(ChangeRtlEnabledEvent(value)) } override suspend fun fetchRemoteConfig() { - withContext(dispatcher) { + withContext(dispatchers.main) { firebaseRemoteConfig .fetchAndActivate() .addOnFailureListener(Timber::e) diff --git a/core/domain/src/main/kotlin/org/michaelbel/movies/domain/preferences/constants/Constants.kt b/core/domain/src/main/kotlin/org/michaelbel/movies/domain/preferences/constants/Constants.kt index 9da94aa91..705cf49a4 100644 --- a/core/domain/src/main/kotlin/org/michaelbel/movies/domain/preferences/constants/Constants.kt +++ b/core/domain/src/main/kotlin/org/michaelbel/movies/domain/preferences/constants/Constants.kt @@ -3,6 +3,7 @@ package org.michaelbel.movies.domain.preferences.constants import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.intPreferencesKey +import androidx.datastore.preferences.core.longPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey internal const val USER_PREFERENCES_NAME = "user_preferences" @@ -12,4 +13,5 @@ internal val PREFERENCE_DYNAMIC_COLORS_KEY: Preferences.Key = booleanPr internal val PREFERENCE_RTL_ENABLED_KEY: Preferences.Key = booleanPreferencesKey("rtl_enabled") internal val PREFERENCE_NETWORK_REQUEST_DELAY_KEY: Preferences.Key = intPreferencesKey("network_request_delay") internal val PREFERENCE_SESSION_ID_KEY: Preferences.Key = stringPreferencesKey("session_id") -internal val PREFERENCE_ACCOUNT_ID_KEY: Preferences.Key = intPreferencesKey("account_id") \ No newline at end of file +internal val PREFERENCE_ACCOUNT_ID_KEY: Preferences.Key = intPreferencesKey("account_id") +internal val PREFERENCE_ACCOUNT_EXPIRE_TIME_KEY: Preferences.Key = longPreferencesKey("account_expire_time") \ No newline at end of file diff --git a/core/domain/src/main/kotlin/org/michaelbel/movies/domain/repository/account/impl/AccountRepositoryImpl.kt b/core/domain/src/main/kotlin/org/michaelbel/movies/domain/repository/account/impl/AccountRepositoryImpl.kt index ebcefa75d..114c52bfa 100644 --- a/core/domain/src/main/kotlin/org/michaelbel/movies/domain/repository/account/impl/AccountRepositoryImpl.kt +++ b/core/domain/src/main/kotlin/org/michaelbel/movies/domain/repository/account/impl/AccountRepositoryImpl.kt @@ -3,6 +3,8 @@ package org.michaelbel.movies.domain.repository.account.impl import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.edit +import javax.inject.Inject +import javax.inject.Singleton import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest @@ -11,14 +13,13 @@ import org.michaelbel.movies.domain.data.dao.AccountDao import org.michaelbel.movies.domain.data.entity.AccountDb import org.michaelbel.movies.domain.exceptions.AccountDetailsException import org.michaelbel.movies.domain.ktx.mapToAccountDb +import org.michaelbel.movies.domain.preferences.constants.PREFERENCE_ACCOUNT_EXPIRE_TIME_KEY import org.michaelbel.movies.domain.preferences.constants.PREFERENCE_ACCOUNT_ID_KEY import org.michaelbel.movies.domain.preferences.constants.PREFERENCE_SESSION_ID_KEY import org.michaelbel.movies.domain.repository.account.AccountRepository import org.michaelbel.movies.domain.service.account.AccountService import org.michaelbel.movies.entities.tmdbApiKey import org.michaelbel.movies.network.model.Account -import javax.inject.Inject -import javax.inject.Singleton @Singleton internal class AccountRepositoryImpl @Inject constructor( @@ -40,6 +41,7 @@ internal class AccountRepositoryImpl @Inject constructor( ) dataStore.edit { preferences -> preferences[PREFERENCE_ACCOUNT_ID_KEY] = account.id + preferences[PREFERENCE_ACCOUNT_EXPIRE_TIME_KEY] = System.currentTimeMillis() } accountDao.insert(account.mapToAccountDb) } catch (e: Exception) { diff --git a/core/domain/src/main/kotlin/org/michaelbel/movies/domain/usecase/DelayUseCase.kt b/core/domain/src/main/kotlin/org/michaelbel/movies/domain/usecase/DelayUseCase.kt index d2d6e1094..a09b0e033 100644 --- a/core/domain/src/main/kotlin/org/michaelbel/movies/domain/usecase/DelayUseCase.kt +++ b/core/domain/src/main/kotlin/org/michaelbel/movies/domain/usecase/DelayUseCase.kt @@ -3,20 +3,18 @@ package org.michaelbel.movies.domain.usecase import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.edit -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map -import org.michaelbel.movies.common.coroutines.Dispatcher -import org.michaelbel.movies.common.coroutines.MoviesDispatchers +import org.michaelbel.movies.common.dispatchers.MoviesDispatchers import org.michaelbel.movies.common.usecase.UseCase import org.michaelbel.movies.domain.preferences.constants.PREFERENCE_NETWORK_REQUEST_DELAY_KEY import javax.inject.Inject class DelayUseCase @Inject constructor( - @Dispatcher(MoviesDispatchers.IO) private val dispatcher: CoroutineDispatcher, + dispatchers: MoviesDispatchers, private val dataStore: DataStore -): UseCase(dispatcher) { +): UseCase(dispatchers.io) { val networkRequestDelay: Flow = dataStore.data.map { preferences -> preferences[PREFERENCE_NETWORK_REQUEST_DELAY_KEY] ?: 0 diff --git a/core/domain/src/main/kotlin/org/michaelbel/movies/domain/workers/AccountUpdateWorker.kt b/core/domain/src/main/kotlin/org/michaelbel/movies/domain/workers/AccountUpdateWorker.kt new file mode 100644 index 000000000..8ecc86a07 --- /dev/null +++ b/core/domain/src/main/kotlin/org/michaelbel/movies/domain/workers/AccountUpdateWorker.kt @@ -0,0 +1,48 @@ +package org.michaelbel.movies.domain.workers + +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.hilt.work.HiltWorker +import androidx.work.CoroutineWorker +import androidx.work.WorkerParameters +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import java.util.concurrent.TimeUnit +import kotlinx.coroutines.flow.first +import org.michaelbel.movies.common.ktx.isTimePasses +import org.michaelbel.movies.domain.interactor.account.AccountInteractor +import org.michaelbel.movies.domain.preferences.constants.PREFERENCE_ACCOUNT_EXPIRE_TIME_KEY +import org.michaelbel.movies.domain.preferences.constants.PREFERENCE_ACCOUNT_ID_KEY +import org.michaelbel.movies.entities.isTmdbApiKeyEmpty + +@HiltWorker +class AccountUpdateWorker @AssistedInject constructor( + @Assisted context: Context, + @Assisted workerParams: WorkerParameters, + private val dataStore: DataStore, + private val accountInteractor: AccountInteractor +): CoroutineWorker(context, workerParams) { + + override suspend fun doWork(): Result { + return try { + val accountId: Int? = dataStore.data.first()[PREFERENCE_ACCOUNT_ID_KEY] + if (isTmdbApiKeyEmpty || accountId == null) { + return Result.success() + } + + val expireTime: Long = dataStore.data.first()[PREFERENCE_ACCOUNT_EXPIRE_TIME_KEY] ?: 0L + val currentTime: Long = System.currentTimeMillis() + if (isTimePasses(ONE_DAY_MILLS, expireTime, currentTime)) { + accountInteractor.accountDetails() + } + Result.success() + } catch (e: Exception) { + Result.failure() + } + } + + private companion object { + private val ONE_DAY_MILLS: Long = TimeUnit.DAYS.toMillis(1) + } +} \ No newline at end of file diff --git a/core/domain/src/main/kotlin/org/michaelbel/movies/domain/workers/MoviesDatabaseWorker.kt b/core/domain/src/main/kotlin/org/michaelbel/movies/domain/workers/MoviesDatabaseWorker.kt index 727c4aebe..891576bc8 100644 --- a/core/domain/src/main/kotlin/org/michaelbel/movies/domain/workers/MoviesDatabaseWorker.kt +++ b/core/domain/src/main/kotlin/org/michaelbel/movies/domain/workers/MoviesDatabaseWorker.kt @@ -6,13 +6,12 @@ import androidx.work.CoroutineWorker import androidx.work.WorkerParameters import dagger.assisted.Assisted import dagger.assisted.AssistedInject -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.withContext import kotlinx.serialization.json.Json import kotlinx.serialization.json.decodeFromStream -import org.michaelbel.movies.common.coroutines.Dispatcher -import org.michaelbel.movies.common.coroutines.MoviesDispatchers +import org.michaelbel.movies.common.dispatchers.MoviesDispatchers import org.michaelbel.movies.domain.data.dao.MovieDao +import org.michaelbel.movies.domain.data.dao.ktx.isEmpty import org.michaelbel.movies.domain.data.entity.MovieDb import org.michaelbel.movies.domain.ktx.mapToMovieDb import org.michaelbel.movies.network.model.MovieResponse @@ -21,15 +20,15 @@ import org.michaelbel.movies.network.model.MovieResponse class MoviesDatabaseWorker @AssistedInject constructor( @Assisted context: Context, @Assisted workerParams: WorkerParameters, - @Dispatcher(MoviesDispatchers.IO) private val dispatcher: CoroutineDispatcher, + private val dispatchers: MoviesDispatchers, private val movieDao: MovieDao ): CoroutineWorker(context, workerParams) { override suspend fun doWork(): Result { - return withContext(dispatcher) { + return withContext(dispatchers.io) { try { val filename: String? = inputData.getString(KEY_FILENAME) - if (filename != null) { + if (filename != null && movieDao.isEmpty(MovieDb.MOVIES_LOCAL_LIST)) { applicationContext.assets.open(filename).use { inputStream -> val format = Json { ignoreUnknownKeys = true } val moviesJsonData: List = format.decodeFromStream(inputStream) @@ -40,11 +39,9 @@ class MoviesDatabaseWorker @AssistedInject constructor( ) } movieDao.insertAllMovies(moviesDb) - Result.success() } - } else { - Result.failure() } + Result.success() } catch (e: Exception) { Result.failure() } 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 new file mode 100644 index 000000000..c7b5fb4e3 --- /dev/null +++ b/core/domain/src/main/kotlin/org/michaelbel/movies/domain/workers/di/WorkManagerModule.kt @@ -0,0 +1,21 @@ +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/build.gradle.kts b/core/entities/build.gradle.kts index f323c8708..6ba521759 100644 --- a/core/entities/build.gradle.kts +++ b/core/entities/build.gradle.kts @@ -1,7 +1,9 @@ import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties +@Suppress("dsl_scope_violation") plugins { - id("movies-android-library") + alias(libs.plugins.library) + alias(libs.plugins.kotlin) id("movies-android-hilt") } @@ -28,6 +30,15 @@ android { } } + buildFeatures { + buildConfig = true + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + lint { quiet = true abortOnError = false diff --git a/core/navigation/build.gradle.kts b/core/navigation/build.gradle.kts index 6ff42b9bb..9e87661fb 100644 --- a/core/navigation/build.gradle.kts +++ b/core/navigation/build.gradle.kts @@ -1,5 +1,7 @@ +@Suppress("dsl_scope_violation") plugins { - id("movies-android-library") + alias(libs.plugins.library) + alias(libs.plugins.kotlin) } android { @@ -18,6 +20,11 @@ android { } } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + lint { quiet = true abortOnError = false diff --git a/core/network/build.gradle.kts b/core/network/build.gradle.kts index b05fb442e..4c6e4ede1 100644 --- a/core/network/build.gradle.kts +++ b/core/network/build.gradle.kts @@ -1,6 +1,8 @@ +@Suppress("dsl_scope_violation") plugins { - id("movies-android-library") - id("kotlinx-serialization") + alias(libs.plugins.library) + alias(libs.plugins.kotlin) + alias(libs.plugins.kotlin.serialization) id("movies-android-hilt") } @@ -20,12 +22,21 @@ android { } } + buildFeatures { + buildConfig = true + } + kotlinOptions { freeCompilerArgs = freeCompilerArgs + listOf( "-opt-in=kotlinx.serialization.ExperimentalSerializationApi" ) } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + lint { quiet = true abortOnError = false diff --git a/core/network/src/main/kotlin/org/michaelbel/movies/network/okhttp/OkhttpModule.kt b/core/network/src/main/kotlin/org/michaelbel/movies/network/okhttp/OkhttpModule.kt index 8f0b9e24e..bd8d1fc0a 100644 --- a/core/network/src/main/kotlin/org/michaelbel/movies/network/okhttp/OkhttpModule.kt +++ b/core/network/src/main/kotlin/org/michaelbel/movies/network/okhttp/OkhttpModule.kt @@ -1,18 +1,25 @@ package org.michaelbel.movies.network.okhttp +import android.content.Context import com.chuckerteam.chucker.api.ChuckerInterceptor import dagger.Module import dagger.Provides import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent +import okhttp3.Cache +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import org.michaelbel.movies.network.BuildConfig import java.util.concurrent.TimeUnit import javax.inject.Singleton -import okhttp3.OkHttpClient @Module @InstallIn(SingletonComponent::class) internal object OkhttpModule { + private const val HTTP_CACHE_SIZE_BYTES = 1024 * 1024 * 50L + /** * Суммарное время на выполнение запроса (0 - нет ограничений). */ @@ -33,17 +40,40 @@ internal object OkhttpModule { */ private const val WRITE_TIMEOUT_SECONDS = 10L + @Provides + @Singleton + fun httpCache( + @ApplicationContext context: Context + ): Cache { + return Cache(context.cacheDir, HTTP_CACHE_SIZE_BYTES) + } + + @Provides + @Singleton + fun provideLoggingInterceptor(): HttpLoggingInterceptor { + return HttpLoggingInterceptor().apply { + level = if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY else level + } + } + @Provides @Singleton fun provideOkHttp( - chuckerInterceptor: ChuckerInterceptor + chuckerInterceptor: ChuckerInterceptor, + httpLoggingInterceptor: HttpLoggingInterceptor, + cache: Cache ): OkHttpClient { val builder = OkHttpClient.Builder().apply { addInterceptor(chuckerInterceptor) + addInterceptor(httpLoggingInterceptor) callTimeout(CALL_TIMEOUT_SECONDS, TimeUnit.SECONDS) connectTimeout(CONNECT_TIMEOUT_SECONDS, TimeUnit.SECONDS) readTimeout(READ_TIMEOUT_SECONDS, TimeUnit.SECONDS) writeTimeout(WRITE_TIMEOUT_SECONDS, TimeUnit.SECONDS) + retryOnConnectionFailure(true) + followRedirects(true) + followSslRedirects(true) + cache(cache) } return builder.build() } diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index cf89ac623..f81c8e75c 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -1,6 +1,7 @@ +@Suppress("dsl_scope_violation") plugins { - id("movies-android-library") - id("movies-android-library-compose") + alias(libs.plugins.library) + alias(libs.plugins.kotlin) } android { @@ -27,6 +28,11 @@ android { kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get() } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + lint { quiet = true abortOnError = false @@ -47,4 +53,6 @@ dependencies { api(libs.bundles.accompanist) api(libs.bundles.compose) api(libs.androidx.paging.compose) + + lintChecks(libs.lint.checks) } \ No newline at end of file diff --git a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/compose/AccountAvatar.kt b/core/ui/src/main/kotlin/org/michaelbel/movies/ui/compose/AccountAvatar.kt index 66e8ab98a..ce963f38e 100644 --- a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/compose/AccountAvatar.kt +++ b/core/ui/src/main/kotlin/org/michaelbel/movies/ui/compose/AccountAvatar.kt @@ -26,9 +26,9 @@ import org.michaelbel.movies.ui.theme.MoviesTheme @Composable fun AccountAvatar( - modifier: Modifier = Modifier, account: AccountDb, - fontSize: TextUnit + fontSize: TextUnit, + modifier: Modifier = Modifier ) { if (account.avatarUrl.isNotEmpty()) { AsyncImage( @@ -68,10 +68,9 @@ private fun AccountAvatarPreview( ) { MoviesTheme { AccountAvatar( - modifier = Modifier - .size(32.dp), account = account, - fontSize = account.lettersTextFontSizeSmall + fontSize = account.lettersTextFontSizeSmall, + modifier = Modifier.size(32.dp), ) } } \ No newline at end of file diff --git a/feature/account-impl/build.gradle.kts b/feature/account-impl/build.gradle.kts index 98a476107..316ec71b9 100644 --- a/feature/account-impl/build.gradle.kts +++ b/feature/account-impl/build.gradle.kts @@ -1,6 +1,7 @@ +@Suppress("dsl_scope_violation") plugins { - id("movies-android-library") - id("movies-android-library-compose") + alias(libs.plugins.library) + alias(libs.plugins.kotlin) id("movies-android-hilt") } @@ -10,6 +11,7 @@ android { defaultConfig { minSdk = libs.versions.min.sdk.get().toInt() compileSdk = libs.versions.compile.sdk.get().toInt() + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } buildTypes { @@ -37,6 +39,11 @@ android { kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get() } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + lint { quiet = true abortOnError = false @@ -52,4 +59,13 @@ dependencies { implementation(project(":core:common")) implementation(project(":core:domain")) implementation(project(":core:network")) + + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.test.ext.junit.ktx) + androidTestImplementation(libs.androidx.espresso.core) + androidTestImplementation(libs.androidx.compose.ui.test.junit4) + androidTestImplementation(libs.androidx.benchmark.junit) + debugImplementation(libs.androidx.compose.ui.test.manifest) + + lintChecks(libs.lint.checks) } \ No newline at end of file diff --git a/feature/account-impl/src/main/kotlin/org/michaelbel/movies/auth/AccountViewModel.kt b/feature/account-impl/src/main/kotlin/org/michaelbel/movies/account/AccountViewModel.kt similarity index 75% rename from feature/account-impl/src/main/kotlin/org/michaelbel/movies/auth/AccountViewModel.kt rename to feature/account-impl/src/main/kotlin/org/michaelbel/movies/account/AccountViewModel.kt index c50582226..1488b427a 100644 --- a/feature/account-impl/src/main/kotlin/org/michaelbel/movies/auth/AccountViewModel.kt +++ b/feature/account-impl/src/main/kotlin/org/michaelbel/movies/account/AccountViewModel.kt @@ -1,11 +1,11 @@ -package org.michaelbel.movies.auth +package org.michaelbel.movies.account +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.delay -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.viewmodel.BaseViewModel @@ -20,8 +20,7 @@ class AccountViewModel @Inject constructor( accountInteractor: AccountInteractor, ): BaseViewModel() { - private val _loading: MutableStateFlow = MutableStateFlow(false) - val loading: StateFlow = _loading.asStateFlow() + var loading: Boolean by mutableStateOf(false) val account: StateFlow = accountInteractor.account .stateIn( @@ -31,14 +30,13 @@ class AccountViewModel @Inject constructor( ) override fun handleError(throwable: Throwable) { - _loading.value = false + loading = false super.handleError(throwable) } fun onLogoutClick(onResult: () -> Unit) = launch { - _loading.value = true + loading = true - delay(5000) authenticationInteractor.deleteSession() onResult() } diff --git a/feature/account-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AccountCountryBox.kt b/feature/account-impl/src/main/kotlin/org/michaelbel/movies/account/ui/AccountCountryBox.kt similarity index 76% rename from feature/account-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AccountCountryBox.kt rename to feature/account-impl/src/main/kotlin/org/michaelbel/movies/account/ui/AccountCountryBox.kt index 4d14d13ed..02ac0af48 100644 --- a/feature/account-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AccountCountryBox.kt +++ b/feature/account-impl/src/main/kotlin/org/michaelbel/movies/account/ui/AccountCountryBox.kt @@ -1,4 +1,4 @@ -package org.michaelbel.movies.auth.ui +package org.michaelbel.movies.account.ui import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -19,9 +19,9 @@ import org.michaelbel.movies.ui.preview.DevicePreviews import org.michaelbel.movies.ui.theme.MoviesTheme @Composable -internal fun AccountCountryBox( - modifier: Modifier = Modifier, - country: String +fun AccountCountryBox( + country: String, + modifier: Modifier = Modifier ) { Row( modifier = modifier, @@ -31,21 +31,18 @@ internal fun AccountCountryBox( Icon( imageVector = MoviesIcons.LocationOn, contentDescription = null, - modifier = Modifier - .size(24.dp), + modifier = Modifier.size(24.dp), tint = MaterialTheme.colorScheme.onPrimaryContainer ) Text( text = country, - modifier = Modifier - .padding( - start = 4.dp - ), - color = MaterialTheme.colorScheme.onPrimaryContainer, + modifier = Modifier.padding(start = 4.dp), overflow = TextOverflow.Ellipsis, maxLines = 1, - style = MaterialTheme.typography.bodySmall + style = MaterialTheme.typography.bodySmall.copy( + color = MaterialTheme.colorScheme.onPrimaryContainer + ) ) } } @@ -55,10 +52,10 @@ internal fun AccountCountryBox( private fun AccountCountryBoxPreview() { MoviesTheme { AccountCountryBox( + country = "Finland", modifier = Modifier .fillMaxWidth() - .background(MaterialTheme.colorScheme.primaryContainer), - country = "Finland" + .background(MaterialTheme.colorScheme.primaryContainer) ) } } \ No newline at end of file diff --git a/feature/account-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AccountScreenContent.kt b/feature/account-impl/src/main/kotlin/org/michaelbel/movies/account/ui/AccountScreenContent.kt similarity index 88% rename from feature/account-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AccountScreenContent.kt rename to feature/account-impl/src/main/kotlin/org/michaelbel/movies/account/ui/AccountScreenContent.kt index cedf60ee9..320125e33 100644 --- a/feature/account-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AccountScreenContent.kt +++ b/feature/account-impl/src/main/kotlin/org/michaelbel/movies/account/ui/AccountScreenContent.kt @@ -1,4 +1,4 @@ -package org.michaelbel.movies.auth.ui +package org.michaelbel.movies.account.ui import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -23,8 +23,8 @@ import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import org.michaelbel.movies.account.AccountViewModel import org.michaelbel.movies.account_impl.R -import org.michaelbel.movies.auth.AccountViewModel import org.michaelbel.movies.domain.data.entity.AccountDb import org.michaelbel.movies.domain.data.ktx.orEmpty import org.michaelbel.movies.ui.compose.AccountAvatar @@ -37,34 +37,31 @@ fun AccountRoute( viewModel: AccountViewModel = hiltViewModel() ) { val account: AccountDb? by viewModel.account.collectAsStateWithLifecycle() - val loading: Boolean by viewModel.loading.collectAsStateWithLifecycle() AccountScreenContent( - modifier = modifier, account = account.orEmpty, - loading = loading, + loading = viewModel.loading, onBackClick = onBackClick, onLogoutClick = { viewModel.onLogoutClick { onBackClick() } - } + }, + modifier = modifier ) } @Composable internal fun AccountScreenContent( - modifier: Modifier = Modifier, account: AccountDb, loading: Boolean, onBackClick: () -> Unit, - onLogoutClick: () -> Unit + onLogoutClick: () -> Unit, + modifier: Modifier = Modifier ) { ConstraintLayout( modifier - .padding( - horizontal = 16.dp - ) + .padding(horizontal = 16.dp) .fillMaxWidth() .background( color = MaterialTheme.colorScheme.primaryContainer, @@ -95,15 +92,15 @@ internal fun AccountScreenContent( ) AccountAvatar( + account = account, + fontSize = account.lettersTextFontSizeLarge, modifier = Modifier .constrainAs(accountAvatar) { width = Dimension.value(64.dp) height = Dimension.value(64.dp) start.linkTo(parent.start, 16.dp) top.linkTo(toolbar.bottom) - }, - account = account, - fontSize = account.lettersTextFontSizeLarge + } ) Column( @@ -122,26 +119,29 @@ internal fun AccountScreenContent( if (account.name.isNotEmpty()) { Text( text = account.name, - color = MaterialTheme.colorScheme.primary, overflow = TextOverflow.Ellipsis, maxLines = 1, - style = MaterialTheme.typography.bodyLarge + style = MaterialTheme.typography.bodyLarge.copy( + color = MaterialTheme.colorScheme.primary + ) ) } if (account.username.isNotEmpty()) { Text( text = account.username, - color = MaterialTheme.colorScheme.secondary, overflow = TextOverflow.Ellipsis, maxLines = 1, - style = MaterialTheme.typography.bodyMedium + style = MaterialTheme.typography.bodyMedium.copy( + color = MaterialTheme.colorScheme.secondary + ) ) } } if (account.country.isNotEmpty()) { AccountCountryBox( + country = account.country, modifier = Modifier .constrainAs(countryBox) { width = Dimension.fillToConstraints @@ -149,8 +149,7 @@ internal fun AccountScreenContent( start.linkTo(parent.start, 16.dp) top.linkTo(accountAvatar.bottom, 8.dp) end.linkTo(parent.end, 16.dp) - }, - country = account.country + } ) } diff --git a/feature/account-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AccountToolbar.kt b/feature/account-impl/src/main/kotlin/org/michaelbel/movies/account/ui/AccountToolbar.kt similarity index 89% rename from feature/account-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AccountToolbar.kt rename to feature/account-impl/src/main/kotlin/org/michaelbel/movies/account/ui/AccountToolbar.kt index 9abb1b249..5662cb571 100644 --- a/feature/account-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AccountToolbar.kt +++ b/feature/account-impl/src/main/kotlin/org/michaelbel/movies/account/ui/AccountToolbar.kt @@ -1,4 +1,4 @@ -package org.michaelbel.movies.auth.ui +package org.michaelbel.movies.account.ui import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -19,7 +19,7 @@ import org.michaelbel.movies.ui.preview.DevicePreviews import org.michaelbel.movies.ui.theme.MoviesTheme @Composable -internal fun AccountToolbar( +fun AccountToolbar( modifier: Modifier = Modifier, onNavigationIconClick: () -> Unit ) { @@ -27,10 +27,11 @@ internal fun AccountToolbar( title = { Text( text = stringResource(R.string.account_title), - color = MaterialTheme.colorScheme.onPrimaryContainer, overflow = TextOverflow.Ellipsis, maxLines = 1, - style = MaterialTheme.typography.titleLarge + style = MaterialTheme.typography.titleLarge.copy( + color = MaterialTheme.colorScheme.onPrimaryContainer + ) ) }, modifier = modifier, diff --git a/feature/account/build.gradle.kts b/feature/account/build.gradle.kts index cea305925..8f07a9766 100644 --- a/feature/account/build.gradle.kts +++ b/feature/account/build.gradle.kts @@ -1,6 +1,7 @@ +@Suppress("dsl_scope_violation") plugins { - id("movies-android-library") - id("movies-android-library-compose") + alias(libs.plugins.library) + alias(libs.plugins.kotlin) } android { @@ -33,6 +34,11 @@ android { kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get() } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + lint { quiet = true abortOnError = false @@ -44,4 +50,6 @@ android { dependencies { implementation(project(":feature:account-impl")) + + lintChecks(libs.lint.checks) } \ No newline at end of file diff --git a/feature/account/src/main/kotlin/org/michaelbel/movies/auth/AccountNavigation.kt b/feature/account/src/main/kotlin/org/michaelbel/movies/auth/AccountNavigation.kt index cb86adb78..364c8ce89 100644 --- a/feature/account/src/main/kotlin/org/michaelbel/movies/auth/AccountNavigation.kt +++ b/feature/account/src/main/kotlin/org/michaelbel/movies/auth/AccountNavigation.kt @@ -3,7 +3,7 @@ package org.michaelbel.movies.auth import androidx.compose.ui.window.DialogProperties import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.dialog -import org.michaelbel.movies.auth.ui.AccountRoute +import org.michaelbel.movies.account.ui.AccountRoute import org.michaelbel.movies.navigation.MoviesNavigationDestination object AccountDestination: MoviesNavigationDestination { diff --git a/feature/auth-impl/build.gradle.kts b/feature/auth-impl/build.gradle.kts index cfbe35aa3..e2273d0cc 100644 --- a/feature/auth-impl/build.gradle.kts +++ b/feature/auth-impl/build.gradle.kts @@ -1,6 +1,7 @@ +@Suppress("dsl_scope_violation") plugins { - id("movies-android-library") - id("movies-android-library-compose") + alias(libs.plugins.library) + alias(libs.plugins.kotlin) id("movies-android-hilt") } @@ -10,6 +11,7 @@ android { defaultConfig { minSdk = libs.versions.min.sdk.get().toInt() compileSdk = libs.versions.compile.sdk.get().toInt() + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } buildTypes { @@ -37,6 +39,11 @@ android { kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get() } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + lint { quiet = true abortOnError = false @@ -52,4 +59,13 @@ dependencies { implementation(project(":core:common")) implementation(project(":core:domain")) implementation(project(":core:network")) + + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.test.ext.junit.ktx) + androidTestImplementation(libs.androidx.espresso.core) + androidTestImplementation(libs.androidx.compose.ui.test.junit4) + androidTestImplementation(libs.androidx.benchmark.junit) + debugImplementation(libs.androidx.compose.ui.test.manifest) + + lintChecks(libs.lint.checks) } \ No newline at end of file diff --git a/feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/AuthViewModel.kt b/feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/AuthViewModel.kt index 4e6ecb2e7..ff05731c2 100644 --- a/feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/AuthViewModel.kt +++ b/feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/AuthViewModel.kt @@ -1,9 +1,9 @@ package org.michaelbel.movies.auth +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import org.michaelbel.movies.common.viewmodel.BaseViewModel import org.michaelbel.movies.domain.exceptions.AccountDetailsException @@ -21,35 +21,32 @@ class AuthViewModel @Inject constructor( private val accountInteractor: AccountInteractor ): BaseViewModel() { - private val _error: MutableStateFlow = MutableStateFlow(null) - val error: StateFlow = _error.asStateFlow() - - private val _loading: MutableStateFlow = MutableStateFlow(false) - val loading: StateFlow = _loading.asStateFlow() + var loading: Boolean by mutableStateOf(false) + var error: Throwable? by mutableStateOf(null) override fun handleError(throwable: Throwable) { - _loading.value = false + loading = false when (throwable) { is CreateRequestTokenException -> { - _error.value = CreateRequestTokenException + error = CreateRequestTokenException } is CreateSessionWithLoginException -> { - _error.value = CreateSessionWithLoginException + error = CreateSessionWithLoginException } is CreateSessionException -> { - _error.value = CreateSessionException + error = CreateSessionException } is AccountDetailsException -> { - _error.value = AccountDetailsException + error = AccountDetailsException } else -> super.handleError(throwable) } } fun onSignInClick(username: String, password: String, onResult: () -> Unit) = launch { - _error.value = null - _loading.value = true + error = null + loading = true val token: Token = authenticationInteractor.createRequestToken() val sessionToken: Token = authenticationInteractor.createSessionWithLogin( diff --git a/feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AuthErrorBox.kt b/feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AuthErrorBox.kt index 00814fd89..1616d291f 100644 --- a/feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AuthErrorBox.kt +++ b/feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AuthErrorBox.kt @@ -22,9 +22,9 @@ import org.michaelbel.movies.ui.preview.DevicePreviews import org.michaelbel.movies.ui.theme.MoviesTheme @Composable -internal fun AuthErrorBox( - modifier: Modifier = Modifier, - error: Throwable? +fun AuthErrorBox( + error: Throwable?, + modifier: Modifier = Modifier ) { Row( modifier = modifier, @@ -34,17 +34,13 @@ internal fun AuthErrorBox( Icon( imageVector = MoviesIcons.Error, contentDescription = null, - modifier = Modifier - .size(24.dp), + modifier = Modifier.size(24.dp), tint = MaterialTheme.colorScheme.error ) Text( text = error.text, - modifier = Modifier - .padding( - start = 4.dp - ), + modifier = Modifier.padding(start = 4.dp), color = MaterialTheme.colorScheme.error, fontSize = 13.sp, textAlign = TextAlign.Start, @@ -60,9 +56,9 @@ private fun AuthErrorBoxPreview( ) { MoviesTheme { AuthErrorBox( + error = error, modifier = Modifier - .background(MaterialTheme.colorScheme.primaryContainer), - error = error + .background(MaterialTheme.colorScheme.primaryContainer) ) } } \ No newline at end of file diff --git a/feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AuthLinksBox.kt b/feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AuthLinksBox.kt index 7857816f0..12090b7bc 100644 --- a/feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AuthLinksBox.kt +++ b/feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AuthLinksBox.kt @@ -29,7 +29,7 @@ import org.michaelbel.movies.ui.preview.DevicePreviews import org.michaelbel.movies.ui.theme.MoviesTheme @Composable -internal fun AuthLinksBox( +fun AuthLinksBox( modifier: Modifier = Modifier ) { val toolbarColor: Int = MaterialTheme.colorScheme.primary.toArgb() @@ -56,22 +56,19 @@ internal fun AuthLinksBox( Text( text = stringResource(R.string.auth_terms_of_use), modifier = Modifier - .padding( - vertical = 16.dp - ) + .padding(vertical = 16.dp) .clickableWithoutRipple { openUrl(resultContract, toolbarColor, TMDB_TERMS_OF_USE) }, - color = MaterialTheme.colorScheme.primary, maxLines = 1, - style = MaterialTheme.typography.bodyMedium + style = MaterialTheme.typography.bodyMedium.copy( + color = MaterialTheme.colorScheme.primary + ) ) Box( modifier = Modifier - .padding( - horizontal = 8.dp - ) + .padding(horizontal = 8.dp) .size(3.dp) .clip(CircleShape) .background(MaterialTheme.colorScheme.primary) @@ -80,15 +77,14 @@ internal fun AuthLinksBox( Text( text = stringResource(R.string.auth_privacy_policy), modifier = Modifier - .padding( - vertical = 16.dp - ) + .padding(vertical = 16.dp) .clickableWithoutRipple { openUrl(resultContract, toolbarColor, TMDB_PRIVACY_POLICY) }, - color = MaterialTheme.colorScheme.primary, maxLines = 1, - style = MaterialTheme.typography.bodyMedium + style = MaterialTheme.typography.bodyMedium.copy( + color = MaterialTheme.colorScheme.primary + ) ) } } 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 ba9dac80a..fb190124d 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 @@ -41,7 +41,6 @@ import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.compose.collectAsStateWithLifecycle import org.michaelbel.movies.auth.AuthViewModel import org.michaelbel.movies.auth_impl.R import org.michaelbel.movies.ui.icons.MoviesIcons @@ -52,29 +51,26 @@ fun AuthRoute( modifier: Modifier = Modifier, viewModel: AuthViewModel = hiltViewModel() ) { - val error: Throwable? by viewModel.error.collectAsStateWithLifecycle() - val loading: Boolean by viewModel.loading.collectAsStateWithLifecycle() - AuthScreenContent( - modifier = modifier, - error = error, - loading = loading, + error = viewModel.error, + loading = viewModel.loading, onBackClick = onBackClick, onSignInClick = { username, password -> viewModel.onSignInClick(username, password) { onBackClick() } - } + }, + modifier = modifier ) } @Composable internal fun AuthScreenContent( - modifier: Modifier = Modifier, error: Throwable?, loading: Boolean, onBackClick: () -> Unit, - onSignInClick: (String, String) -> Unit + onSignInClick: (String, String) -> Unit, + modifier: Modifier = Modifier ) { val focusManager: FocusManager = LocalFocusManager.current val focusRequester: FocusRequester = remember { FocusRequester() } @@ -86,9 +82,7 @@ internal fun AuthScreenContent( ConstraintLayout( modifier - .padding( - horizontal = 16.dp - ) + .padding(horizontal = 16.dp) .fillMaxWidth() .background( color = MaterialTheme.colorScheme.primaryContainer, diff --git a/feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AuthToolbar.kt b/feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AuthToolbar.kt index e99024412..cb20736a5 100644 --- a/feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AuthToolbar.kt +++ b/feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AuthToolbar.kt @@ -19,7 +19,7 @@ import org.michaelbel.movies.ui.preview.DevicePreviews import org.michaelbel.movies.ui.theme.MoviesTheme @Composable -internal fun AuthToolbar( +fun AuthToolbar( modifier: Modifier = Modifier, onNavigationIconClick: () -> Unit ) { @@ -27,10 +27,11 @@ internal fun AuthToolbar( title = { Text( text = stringResource(R.string.auth_title), - color = MaterialTheme.colorScheme.onPrimaryContainer, overflow = TextOverflow.Ellipsis, maxLines = 1, - style = MaterialTheme.typography.titleLarge + style = MaterialTheme.typography.titleLarge.copy( + color = MaterialTheme.colorScheme.onPrimaryContainer + ) ) }, modifier = modifier, diff --git a/feature/auth/build.gradle.kts b/feature/auth/build.gradle.kts index 6f75a81b5..428bbf2c3 100644 --- a/feature/auth/build.gradle.kts +++ b/feature/auth/build.gradle.kts @@ -1,6 +1,7 @@ +@Suppress("dsl_scope_violation") plugins { - id("movies-android-library") - id("movies-android-library-compose") + alias(libs.plugins.library) + alias(libs.plugins.kotlin) } android { @@ -33,6 +34,11 @@ android { kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get() } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + lint { quiet = true abortOnError = false @@ -44,4 +50,6 @@ android { dependencies { implementation(project(":feature:auth-impl")) + + lintChecks(libs.lint.checks) } \ No newline at end of file diff --git a/feature/details-impl/build.gradle.kts b/feature/details-impl/build.gradle.kts index 69355e997..280ed4544 100644 --- a/feature/details-impl/build.gradle.kts +++ b/feature/details-impl/build.gradle.kts @@ -1,6 +1,7 @@ +@Suppress("dsl_scope_violation") plugins { - id("movies-android-library") - id("movies-android-library-compose") + alias(libs.plugins.library) + alias(libs.plugins.kotlin) id("movies-android-hilt") } @@ -10,6 +11,7 @@ android { defaultConfig { minSdk = libs.versions.min.sdk.get().toInt() compileSdk = libs.versions.compile.sdk.get().toInt() + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } buildTypes { @@ -36,6 +38,11 @@ android { kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get() } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + lint { quiet = true abortOnError = false @@ -52,4 +59,13 @@ dependencies { implementation(project(":core:common")) implementation(project(":core:domain")) implementation(project(":core:network")) + + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.test.ext.junit.ktx) + androidTestImplementation(libs.androidx.espresso.core) + androidTestImplementation(libs.androidx.compose.ui.test.junit4) + androidTestImplementation(libs.androidx.benchmark.junit) + debugImplementation(libs.androidx.compose.ui.test.manifest) + + lintChecks(libs.lint.checks) } \ No newline at end of file diff --git a/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsAdvert.kt b/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsAdvert.kt index 08812dd5b..f411b74ba 100644 --- a/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsAdvert.kt +++ b/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsAdvert.kt @@ -1,22 +1,6 @@ package org.michaelbel.movies.details.ui -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import androidx.compose.ui.viewinterop.AndroidView -import com.google.android.gms.ads.AdRequest -import com.google.android.gms.ads.AdSize -import com.google.android.gms.ads.AdView -import org.michaelbel.movies.ads.R -import org.michaelbel.movies.ui.preview.DevicePreviews -import org.michaelbel.movies.ui.theme.MoviesTheme - -@Composable +/*@Composable internal fun DetailsAdvert( modifier: Modifier = Modifier, adRequest: AdRequest @@ -50,4 +34,4 @@ private fun DetailsAdvertPreview() { adRequest = AdRequest.Builder().build() ) } -} \ No newline at end of file +}*/ \ No newline at end of file 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 bcc7fd8c9..affaed5b1 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 @@ -38,7 +38,7 @@ import org.michaelbel.movies.ui.preview.provider.MovieDbPreviewParameterProvider import org.michaelbel.movies.ui.theme.MoviesTheme @Composable -internal fun DetailsContent( +fun DetailsContent( movie: MovieDb, modifier: Modifier = Modifier, placeholder: Boolean = false @@ -113,8 +113,9 @@ internal fun DetailsContent( ) { Text( text = stringResource(R.string.details_no_image), - color = MaterialTheme.colorScheme.secondary, - style = MaterialTheme.typography.bodyLarge + style = MaterialTheme.typography.bodyLarge.copy( + color = MaterialTheme.colorScheme.secondary + ) ) } @@ -147,10 +148,11 @@ internal fun DetailsContent( Text( text = movie.title, modifier = titleModifier, - color = MaterialTheme.colorScheme.onPrimaryContainer, overflow = TextOverflow.Ellipsis, maxLines = 3, - style = MaterialTheme.typography.titleLarge + style = MaterialTheme.typography.titleLarge.copy( + color = MaterialTheme.colorScheme.onPrimaryContainer + ) ) val overviewModifier: Modifier = if (placeholder) { @@ -182,10 +184,11 @@ internal fun DetailsContent( Text( text = movie.overview, modifier = overviewModifier, - color = MaterialTheme.colorScheme.onPrimaryContainer, overflow = TextOverflow.Ellipsis, maxLines = 10, - style = MaterialTheme.typography.bodyMedium + style = MaterialTheme.typography.bodyMedium.copy( + color = MaterialTheme.colorScheme.onPrimaryContainer + ) ) } } diff --git a/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsFailure.kt b/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsFailure.kt index fecde536c..a5ed74bce 100644 --- a/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsFailure.kt +++ b/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsFailure.kt @@ -18,7 +18,7 @@ import org.michaelbel.movies.ui.preview.DevicePreviews import org.michaelbel.movies.ui.theme.MoviesTheme @Composable -internal fun DetailsFailure( +fun DetailsFailure( modifier: Modifier = Modifier ) { ConstraintLayout( @@ -51,9 +51,10 @@ internal fun DetailsFailure( top.linkTo(image.bottom, 8.dp) end.linkTo(parent.end, 16.dp) }, - color = MaterialTheme.colorScheme.onBackground, textAlign = TextAlign.Center, - style = MaterialTheme.typography.bodyMedium + style = MaterialTheme.typography.bodyMedium.copy( + color = MaterialTheme.colorScheme.onBackground + ) ) } } diff --git a/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsLoading.kt b/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsLoading.kt index 4963df8a6..dbb2fed67 100644 --- a/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsLoading.kt +++ b/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsLoading.kt @@ -10,7 +10,7 @@ import org.michaelbel.movies.ui.preview.DevicePreviews import org.michaelbel.movies.ui.theme.MoviesTheme @Composable -internal fun DetailsLoading( +fun DetailsLoading( modifier: Modifier = Modifier ) { DetailsContent( 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 a39661287..fe17ed015 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 @@ -33,20 +33,20 @@ fun DetailsRoute( DetailsScreenContent( onBackClick = onBackClick, - modifier = modifier, detailsState = detailsState, networkStatus = networkStatus, - onRetry = viewModel::retry + onRetry = viewModel::retry, + modifier = modifier ) } @Composable private fun DetailsScreenContent( onBackClick: () -> Unit, - modifier: Modifier = Modifier, detailsState: ScreenState, networkStatus: NetworkStatus, - onRetry: () -> Unit + onRetry: () -> Unit, + modifier: Modifier = Modifier ) { if (networkStatus.isAvailable && detailsState.isFailure && detailsState.throwable is UnknownHostException) { onRetry() @@ -56,11 +56,10 @@ private fun DetailsScreenContent( modifier = modifier, topBar = { DetailsToolbar( - modifier = Modifier - .statusBarsPadding(), - onNavigationIconClick = onBackClick, movieTitle = detailsState.toolbarTitle, - movieUrl = detailsState.movieUrl + movieUrl = detailsState.movieUrl, + onNavigationIconClick = onBackClick, + modifier = Modifier.statusBarsPadding() ) }, containerColor = MaterialTheme.colorScheme.primaryContainer diff --git a/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/ShareButton.kt b/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsShareButton.kt similarity index 91% rename from feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/ShareButton.kt rename to feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsShareButton.kt index 190daa4c7..fe28093d6 100644 --- a/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/ShareButton.kt +++ b/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsShareButton.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.Image import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.PreviewParameter @@ -20,8 +21,9 @@ import org.michaelbel.movies.ui.preview.provider.MovieDbPreviewParameterProvider import org.michaelbel.movies.ui.theme.MoviesTheme @Composable -internal fun ShareButton( - movieUrl: String +fun DetailsShareButton( + movieUrl: String, + modifier: Modifier = Modifier ) { val context: Context = LocalContext.current val resultContract = rememberLauncherForActivityResult( @@ -39,7 +41,8 @@ internal fun ShareButton( Intent.createChooser(intent, context.getString(R.string.details_share_via)) ) } - } + }, + modifier = modifier ) { Image( imageVector = MoviesIcons.Share, @@ -55,7 +58,7 @@ private fun ShareButtonPreview( @PreviewParameter(MovieDbPreviewParameterProvider::class) movie: MovieDb ) { MoviesTheme { - ShareButton( + DetailsShareButton( movieUrl = movie.url ) } diff --git a/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsToolbar.kt b/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsToolbar.kt index 791a16adb..aa798e787 100644 --- a/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsToolbar.kt +++ b/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsToolbar.kt @@ -21,20 +21,21 @@ import org.michaelbel.movies.ui.preview.provider.TitlePreviewParameterProvider import org.michaelbel.movies.ui.theme.MoviesTheme @Composable -internal fun DetailsToolbar( - modifier: Modifier = Modifier, - onNavigationIconClick: () -> Unit, +fun DetailsToolbar( movieTitle: String, - movieUrl: String? + movieUrl: String?, + onNavigationIconClick: () -> Unit, + modifier: Modifier = Modifier ) { TopAppBar( title = { Text( text = movieTitle, - color = MaterialTheme.colorScheme.onPrimaryContainer, overflow = TextOverflow.Ellipsis, maxLines = 1, - style = MaterialTheme.typography.titleLarge + style = MaterialTheme.typography.titleLarge.copy( + color = MaterialTheme.colorScheme.onPrimaryContainer + ) ) }, modifier = modifier, @@ -44,7 +45,7 @@ internal fun DetailsToolbar( enter = fadeIn() ) { if (movieUrl != null) { - ShareButton( + DetailsShareButton( movieUrl = movieUrl ) } @@ -76,11 +77,10 @@ private fun DetailsToolbarPreview( ) { MoviesTheme { DetailsToolbar( - modifier = Modifier - .statusBarsPadding(), - onNavigationIconClick = {}, movieTitle = title, - movieUrl = null + movieUrl = null, + onNavigationIconClick = {}, + modifier = Modifier.statusBarsPadding() ) } } \ No newline at end of file diff --git a/feature/details/build.gradle.kts b/feature/details/build.gradle.kts index 4ba87df58..f51e4fdeb 100644 --- a/feature/details/build.gradle.kts +++ b/feature/details/build.gradle.kts @@ -1,6 +1,7 @@ +@Suppress("dsl_scope_violation") plugins { - id("movies-android-library") - id("movies-android-library-compose") + alias(libs.plugins.library) + alias(libs.plugins.kotlin) } android { @@ -27,6 +28,11 @@ android { kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get() } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + lint { quiet = true abortOnError = false @@ -38,4 +44,6 @@ android { dependencies { implementation(project(":feature:details-impl")) + + lintChecks(libs.lint.checks) } \ No newline at end of file diff --git a/feature/feed-impl/build.gradle.kts b/feature/feed-impl/build.gradle.kts index 10562cf19..f6b1d3328 100644 --- a/feature/feed-impl/build.gradle.kts +++ b/feature/feed-impl/build.gradle.kts @@ -1,6 +1,7 @@ +@Suppress("dsl_scope_violation") plugins { - id("movies-android-library") - id("movies-android-library-compose") + alias(libs.plugins.library) + alias(libs.plugins.kotlin) id("movies-android-hilt") } @@ -10,6 +11,7 @@ android { defaultConfig { minSdk = libs.versions.min.sdk.get().toInt() compileSdk = libs.versions.compile.sdk.get().toInt() + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } buildTypes { @@ -37,6 +39,11 @@ android { kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get() } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + lint { quiet = true abortOnError = false @@ -52,4 +59,13 @@ dependencies { implementation(project(":core:common")) implementation(project(":core:domain")) implementation(project(":core:network")) + + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.test.ext.junit.ktx) + androidTestImplementation(libs.androidx.espresso.core) + androidTestImplementation(libs.androidx.compose.ui.test.junit4) + androidTestImplementation(libs.androidx.benchmark.junit) + debugImplementation(libs.androidx.compose.ui.test.manifest) + + lintChecks(libs.lint.checks) } \ No newline at end of file diff --git a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedApiKeyBox.kt b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedApiKeyBox.kt index 3f4a97918..d0ae1d58c 100644 --- a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedApiKeyBox.kt +++ b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedApiKeyBox.kt @@ -17,7 +17,7 @@ import org.michaelbel.movies.ui.preview.DevicePreviews import org.michaelbel.movies.ui.theme.MoviesTheme @Composable -internal fun FeedApiKeyBox( +fun FeedApiKeyBox( modifier: Modifier = Modifier ) { Column( @@ -27,8 +27,9 @@ internal fun FeedApiKeyBox( ) { Text( text = stringResource(R.string.feed_error_api_key_null), - color = MaterialTheme.colorScheme.onBackground, - style = MaterialTheme.typography.bodyLarge + style = MaterialTheme.typography.bodyLarge.copy( + color = MaterialTheme.colorScheme.onBackground + ) ) } } 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 a15957c68..de4eff286 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 @@ -22,21 +22,22 @@ import org.michaelbel.movies.ui.ktx.isPagingFailure import org.michaelbel.movies.ui.ktx.isPagingLoading @Composable -internal fun FeedContent( - modifier: Modifier = Modifier, - paddingValues: PaddingValues = PaddingValues(), +fun FeedContent( listState: LazyListState, pagingItems: LazyPagingItems, - onMovieClick: (Int) -> Unit + onMovieClick: (Int) -> Unit, + contentPadding: PaddingValues, + modifier: Modifier = Modifier, ) { LazyColumn( modifier = modifier, state = listState, - contentPadding = paddingValues + contentPadding = contentPadding ) { items(pagingItems) { movieItem -> movieItem?.let { movie -> FeedMovieBox( + movie = movie, modifier = Modifier .fillMaxWidth() .padding( @@ -47,8 +48,7 @@ internal fun FeedContent( .background(MaterialTheme.colorScheme.inversePrimary) .clickable { onMovieClick(movie.movieId) - }, - movie = movie + } ) } } diff --git a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedErrorBox.kt b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedErrorBox.kt index e4c78376e..03db1ae03 100644 --- a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedErrorBox.kt +++ b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedErrorBox.kt @@ -17,7 +17,7 @@ import org.michaelbel.movies.ui.preview.DevicePreviews import org.michaelbel.movies.ui.theme.MoviesTheme @Composable -internal fun FeedErrorBox( +fun FeedErrorBox( modifier: Modifier = Modifier ) { Column( @@ -27,8 +27,9 @@ internal fun FeedErrorBox( ) { Text( text = stringResource(R.string.feed_retry), - color = MaterialTheme.colorScheme.onBackground, - style = MaterialTheme.typography.bodyLarge + style = MaterialTheme.typography.bodyLarge.copy( + color = MaterialTheme.colorScheme.onBackground + ) ) } } diff --git a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedFailure.kt b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedFailure.kt index 4e0c878d6..56918c5e9 100644 --- a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedFailure.kt +++ b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedFailure.kt @@ -20,7 +20,7 @@ import org.michaelbel.movies.ui.preview.DevicePreviews import org.michaelbel.movies.ui.theme.MoviesTheme @Composable -internal fun FeedFailure( +fun FeedFailure( modifier: Modifier = Modifier, onCheckConnectivityClick: () -> Unit ) { @@ -54,9 +54,10 @@ internal fun FeedFailure( top.linkTo(image.bottom, 8.dp) end.linkTo(parent.end, 16.dp) }, - color = MaterialTheme.colorScheme.onBackground, textAlign = TextAlign.Center, - style = MaterialTheme.typography.bodyMedium + style = MaterialTheme.typography.bodyMedium.copy( + color = MaterialTheme.colorScheme.onBackground + ) ) if (Build.VERSION.SDK_INT >= 29) { diff --git a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedLoading.kt b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedLoading.kt index aea8aa8ac..352bca9c5 100644 --- a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedLoading.kt +++ b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedLoading.kt @@ -19,7 +19,7 @@ import org.michaelbel.movies.ui.preview.DevicePreviews import org.michaelbel.movies.ui.theme.MoviesTheme @Composable -internal fun FeedLoading( +fun FeedLoading( modifier: Modifier = Modifier, paddingValues: PaddingValues = PaddingValues(), ) { @@ -29,6 +29,7 @@ internal fun FeedLoading( ) { items(MovieResponse.DEFAULT_PAGE_SIZE) { FeedMovieBox( + movie = MovieDb.Empty, modifier = Modifier .fillMaxWidth() .padding( @@ -40,8 +41,7 @@ internal fun FeedLoading( color = MaterialTheme.colorScheme.inversePrimary, shape = MaterialTheme.shapes.small, highlight = PlaceholderHighlight.fade() - ), - movie = MovieDb.Empty + ) ) } } diff --git a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedLoadingBox.kt b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedLoadingBox.kt index ec7d02d85..fa5121ef5 100644 --- a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedLoadingBox.kt +++ b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedLoadingBox.kt @@ -15,7 +15,7 @@ import org.michaelbel.movies.ui.preview.DevicePreviews import org.michaelbel.movies.ui.theme.MoviesTheme @Composable -internal fun FeedLoadingBox( +fun FeedLoadingBox( modifier: Modifier = Modifier ) { Column( diff --git a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedMovieBox.kt b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedMovieBox.kt index 1b96a36be..07f382752 100644 --- a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedMovieBox.kt +++ b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedMovieBox.kt @@ -32,9 +32,9 @@ import org.michaelbel.movies.ui.preview.provider.MoviePreviewParameterProvider import org.michaelbel.movies.ui.theme.MoviesTheme @Composable -internal fun FeedMovieBox( - modifier: Modifier = Modifier, - movie: MovieDb +fun FeedMovieBox( + movie: MovieDb, + modifier: Modifier = Modifier ) { var isNoImageVisible: Boolean by remember { mutableStateOf(false) } @@ -78,8 +78,9 @@ internal fun FeedMovieBox( ) { Text( text = stringResource(R.string.feed_no_image), - color = MaterialTheme.colorScheme.secondary, - style = MaterialTheme.typography.bodyLarge + style = MaterialTheme.typography.bodyLarge.copy( + color = MaterialTheme.colorScheme.secondary + ) ) } @@ -94,10 +95,11 @@ internal fun FeedMovieBox( end.linkTo(parent.end, 16.dp) bottom.linkTo(parent.bottom, 16.dp) }, - color = MaterialTheme.colorScheme.onPrimaryContainer, maxLines = 10, overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.bodyLarge + style = MaterialTheme.typography.bodyLarge.copy( + color = MaterialTheme.colorScheme.onPrimaryContainer + ) ) } } @@ -109,6 +111,7 @@ private fun MovieBoxPreview( ) { MoviesTheme { FeedMovieBox( + movie = movie, modifier = Modifier .fillMaxWidth() .padding( @@ -116,8 +119,7 @@ private fun MovieBoxPreview( vertical = 4.dp ) .clip(MaterialTheme.shapes.small) - .background(MaterialTheme.colorScheme.inversePrimary), - movie = movie + .background(MaterialTheme.colorScheme.inversePrimary) ) } } \ No newline at end of file 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 e25ebf93f..09df85ecb 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 @@ -59,7 +59,6 @@ fun FeedRoute( val networkStatus: NetworkStatus by viewModel.networkStatus.collectAsStateWithLifecycle() FeedScreenContent( - modifier = modifier, pagingItems = pagingItems, account = account.orEmpty, networkStatus = networkStatus, @@ -67,13 +66,13 @@ fun FeedRoute( onNavigateToAuth = onNavigateToAuth, onNavigateToAccount = onNavigateToAccount, onNavigateToSettings = onNavigateToSettings, - onNavigateToDetails = onNavigateToDetails + onNavigateToDetails = onNavigateToDetails, + modifier = modifier ) } @Composable private fun FeedScreenContent( - modifier: Modifier = Modifier, pagingItems: LazyPagingItems, account: AccountDb, networkStatus: NetworkStatus, @@ -81,7 +80,8 @@ private fun FeedScreenContent( onNavigateToAuth: () -> Unit, onNavigateToAccount: () -> Unit, onNavigateToSettings: () -> Unit, - onNavigateToDetails: (Int) -> Unit + onNavigateToDetails: (Int) -> Unit, + modifier: Modifier = Modifier ) { val context: Context = LocalContext.current val scope: CoroutineScope = rememberCoroutineScope() @@ -167,12 +167,11 @@ private fun FeedScreenContent( } else -> { FeedContent( - modifier = Modifier - .fillMaxSize(), - paddingValues = paddingValues, listState = listState, pagingItems = pagingItems, - onMovieClick = onNavigateToDetails + onMovieClick = onNavigateToDetails, + contentPadding = paddingValues, + modifier = Modifier.fillMaxSize() ) } } diff --git a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedToolbar.kt b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedToolbar.kt index dbb2e014d..99e344503 100644 --- a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedToolbar.kt +++ b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedToolbar.kt @@ -26,22 +26,23 @@ import org.michaelbel.movies.ui.preview.provider.BooleanPreviewParameterProvider import org.michaelbel.movies.ui.theme.MoviesTheme @Composable -internal fun FeedToolbar( - modifier: Modifier = Modifier, +fun FeedToolbar( account: AccountDb, isSettingsIconVisible: Boolean, onAuthIconClick: () -> Unit, onAccountIconClick: () -> Unit, - onSettingsIconClick: () -> Unit + onSettingsIconClick: () -> Unit, + modifier: Modifier = Modifier ) { TopAppBar( title = { Text( text = stringResource(R.string.feed_title), - color = MaterialTheme.colorScheme.onPrimaryContainer, overflow = TextOverflow.Ellipsis, maxLines = 1, - style = MaterialTheme.typography.titleLarge + style = MaterialTheme.typography.titleLarge.copy( + color = MaterialTheme.colorScheme.onPrimaryContainer + ) ) }, modifier = modifier, @@ -69,10 +70,9 @@ internal fun FeedToolbar( ) } else { AccountAvatar( - modifier = Modifier - .size(32.dp), account = account, - fontSize = account.lettersTextFontSizeSmall + fontSize = account.lettersTextFontSizeSmall, + modifier = Modifier.size(32.dp) ) } } @@ -90,13 +90,12 @@ private fun FeedToolbarPreview( ) { MoviesTheme { FeedToolbar( - modifier = Modifier - .statusBarsPadding(), account = AccountDb.Empty, isSettingsIconVisible = visible, onAccountIconClick = {}, onAuthIconClick = {}, - onSettingsIconClick = {} + onSettingsIconClick = {}, + modifier = Modifier.statusBarsPadding() ) } } \ No newline at end of file diff --git a/feature/feed/build.gradle.kts b/feature/feed/build.gradle.kts index a641d7fdc..2df65842f 100644 --- a/feature/feed/build.gradle.kts +++ b/feature/feed/build.gradle.kts @@ -1,6 +1,7 @@ +@Suppress("dsl_scope_violation") plugins { - id("movies-android-library") - id("movies-android-library-compose") + alias(libs.plugins.library) + alias(libs.plugins.kotlin) } android { @@ -27,6 +28,11 @@ android { kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get() } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + lint { quiet = true abortOnError = false @@ -38,4 +44,6 @@ android { dependencies { implementation(project(":feature:feed-impl")) + + lintChecks(libs.lint.checks) } \ No newline at end of file diff --git a/feature/settings-impl/build.gradle.kts b/feature/settings-impl/build.gradle.kts index e38b593e0..8843c8187 100644 --- a/feature/settings-impl/build.gradle.kts +++ b/feature/settings-impl/build.gradle.kts @@ -1,6 +1,7 @@ +@Suppress("dsl_scope_violation") plugins { - id("movies-android-library") - id("movies-android-library-compose") + alias(libs.plugins.library) + alias(libs.plugins.kotlin) id("movies-android-hilt") } @@ -10,6 +11,7 @@ android { defaultConfig { minSdk = libs.versions.min.sdk.get().toInt() compileSdk = libs.versions.compile.sdk.get().toInt() + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } buildTypes { @@ -30,6 +32,7 @@ android { } buildFeatures { + buildConfig = true compose = true } @@ -37,6 +40,11 @@ android { kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get() } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + lint { quiet = true abortOnError = false @@ -54,7 +62,11 @@ dependencies { implementation(project(":core:domain")) testImplementation(libs.junit) - debugImplementation(libs.androidx.compose.ui.test.manifest) - androidTestImplementation(libs.androidx.compose.ui.test.junit4) androidTestImplementation(libs.androidx.test.ext.junit.ktx) + androidTestImplementation(libs.androidx.espresso.core) + androidTestImplementation(libs.androidx.compose.ui.test.junit4) + androidTestImplementation(libs.androidx.benchmark.junit) + debugImplementation(libs.androidx.compose.ui.test.manifest) + + lintChecks(libs.lint.checks) } \ No newline at end of file diff --git a/app/src/androidTest/kotlin/org/michaelbel/movies/SettingsDynamicColorsBoxTest.kt b/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsDynamicColorsBoxTest.kt similarity index 77% rename from app/src/androidTest/kotlin/org/michaelbel/movies/SettingsDynamicColorsBoxTest.kt rename to feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsDynamicColorsBoxTest.kt index 2bba12e17..f935b9e27 100644 --- a/app/src/androidTest/kotlin/org/michaelbel/movies/SettingsDynamicColorsBoxTest.kt +++ b/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsDynamicColorsBoxTest.kt @@ -1,23 +1,21 @@ -package org.michaelbel.movies +package org.michaelbel.movies.settings.ui import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.ui.Modifier -import androidx.compose.ui.test.assert +import androidx.compose.ui.test.assertHasClickAction +import androidx.compose.ui.test.assertHasNoClickAction import androidx.compose.ui.test.assertIsDisplayed -import androidx.compose.ui.test.hasClickAction -import androidx.compose.ui.test.hasNoClickAction import androidx.compose.ui.test.junit4.ComposeContentTestRule import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.unit.dp import org.junit.Rule import org.junit.Test -import org.michaelbel.movies.settings.ui.SettingsDynamicColorsBox import org.michaelbel.movies.ui.theme.MoviesTheme -class SettingsDynamicColorsBoxTest { +internal class SettingsDynamicColorsBoxTest { @get:Rule val composeTestRule: ComposeContentTestRule = createComposeRule() @@ -27,28 +25,29 @@ class SettingsDynamicColorsBoxTest { composeTestRule.setContent { MoviesTheme { SettingsDynamicColorsBox( + isDynamicColorsEnabled = IS_DYNAMIC_COLORS_ENABLED, modifier = Modifier .fillMaxWidth() .height(48.dp) - .clickable {}, - isDynamicColorsEnabled = IS_DYNAMIC_COLORS_ENABLED + .clickable {} ) } } composeTestRule .onNodeWithTag(testTag = "ConstraintLayout", useUnmergedTree = true) - .assert(hasClickAction()) + .assertIsDisplayed() + .assertHasClickAction() composeTestRule .onNodeWithTag(testTag = "Text", useUnmergedTree = true) .assertIsDisplayed() - .assert(hasNoClickAction()) + .assertHasNoClickAction() composeTestRule .onNodeWithTag(testTag = "Switch", useUnmergedTree = true) .assertIsDisplayed() - .assert(hasNoClickAction()) + .assertHasNoClickAction() } private companion object { diff --git a/app/src/androidTest/kotlin/org/michaelbel/movies/SettingsLanguageBoxTest.kt b/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsLanguageBoxTest.kt similarity index 63% rename from app/src/androidTest/kotlin/org/michaelbel/movies/SettingsLanguageBoxTest.kt rename to feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsLanguageBoxTest.kt index 072763351..b9cdb69cb 100644 --- a/app/src/androidTest/kotlin/org/michaelbel/movies/SettingsLanguageBoxTest.kt +++ b/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsLanguageBoxTest.kt @@ -1,24 +1,21 @@ -package org.michaelbel.movies +package org.michaelbel.movies.settings.ui -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.ui.Modifier -import androidx.compose.ui.test.assert +import androidx.compose.ui.test.assertHasClickAction +import androidx.compose.ui.test.assertHasNoClickAction import androidx.compose.ui.test.assertIsDisplayed -import androidx.compose.ui.test.hasClickAction -import androidx.compose.ui.test.hasNoClickAction import androidx.compose.ui.test.junit4.ComposeContentTestRule import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.unit.dp import org.junit.Rule import org.junit.Test -import org.michaelbel.movies.settings.ui.SettingsLanguageBox -import org.michaelbel.movies.ui.language.model.AppLanguage +import org.michaelbel.movies.common.localization.model.AppLanguage import org.michaelbel.movies.ui.theme.MoviesTheme -class SettingsLanguageBoxTest { +internal class SettingsLanguageBoxTest { @get:Rule val composeTestRule: ComposeContentTestRule = createComposeRule() @@ -28,31 +25,37 @@ class SettingsLanguageBoxTest { composeTestRule.setContent { MoviesTheme { SettingsLanguageBox( + languages = LANGUAGES, + currentLanguage = CURRENT_LANGUAGE, + onLanguageSelect = {}, modifier = Modifier .fillMaxWidth() .height(48.dp) - .clickable {}, - currentLanguage = CURRENT_LANGUAGE ) } } composeTestRule .onNodeWithTag(testTag = "ConstraintLayout", useUnmergedTree = true) - .assert(hasClickAction()) + .assertIsDisplayed() + .assertHasClickAction() composeTestRule .onNodeWithTag(testTag = "TitleText", useUnmergedTree = true) .assertIsDisplayed() - .assert(hasNoClickAction()) + .assertHasNoClickAction() composeTestRule .onNodeWithTag(testTag = "ValueText", useUnmergedTree = true) .assertIsDisplayed() - .assert(hasNoClickAction()) + .assertHasNoClickAction() } private companion object { - private val CURRENT_LANGUAGE = AppLanguage.English + private val LANGUAGES: List = listOf( + AppLanguage.English, + AppLanguage.Russian + ) + private val CURRENT_LANGUAGE: AppLanguage = AppLanguage.English } } \ No newline at end of file diff --git a/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsLanguageDialogTest.kt b/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsLanguageDialogTest.kt new file mode 100644 index 000000000..9a53d0161 --- /dev/null +++ b/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsLanguageDialogTest.kt @@ -0,0 +1,64 @@ +package org.michaelbel.movies.settings.ui + +import androidx.compose.ui.test.assertHasClickAction +import androidx.compose.ui.test.assertHasNoClickAction +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.ComposeContentTestRule +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithTag +import org.junit.Rule +import org.junit.Test +import org.michaelbel.movies.common.localization.model.AppLanguage +import org.michaelbel.movies.ui.theme.MoviesTheme + +internal class SettingsLanguageDialogTest { + + @get:Rule + val composeTestRule: ComposeContentTestRule = createComposeRule() + + @Test + fun testSettingsLanguageDialog() { + composeTestRule.setContent { + MoviesTheme { + SettingLanguageDialog( + languages = LANGUAGES, + currentLanguage = CURRENT_LANGUAGE, + onLanguageSelect = {}, + onDismissRequest = {} + ) + } + } + + composeTestRule + .onNodeWithTag(testTag = "ConfirmTextButton", useUnmergedTree = true) + .assertIsDisplayed() + .assertHasClickAction() + + composeTestRule + .onNodeWithTag(testTag = "ConfirmText", useUnmergedTree = true) + .assertIsDisplayed() + .assertHasNoClickAction() + + composeTestRule + .onNodeWithTag(testTag = "Icon", useUnmergedTree = true) + .assertIsDisplayed() + .assertHasNoClickAction() + + composeTestRule + .onNodeWithTag(testTag = "Title", useUnmergedTree = true) + .assertIsDisplayed() + .assertHasNoClickAction() + + composeTestRule + .onNodeWithTag(testTag = "Content", useUnmergedTree = true) + .assertIsDisplayed() + } + + private companion object { + private val LANGUAGES: List = listOf( + AppLanguage.English, + AppLanguage.Russian + ) + private val CURRENT_LANGUAGE: AppLanguage = AppLanguage.English + } +} \ No newline at end of file diff --git a/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsNetworkRequestDelayBoxTest.kt b/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsNetworkRequestDelayBoxTest.kt new file mode 100644 index 000000000..30cd02a13 --- /dev/null +++ b/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsNetworkRequestDelayBoxTest.kt @@ -0,0 +1,56 @@ +package org.michaelbel.movies.settings.ui + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.ui.Modifier +import androidx.compose.ui.test.assertHasNoClickAction +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.ComposeContentTestRule +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithTag +import org.junit.Rule +import org.junit.Test +import org.michaelbel.movies.ui.theme.MoviesTheme + +internal class SettingsNetworkRequestDelayBoxTest { + + @get:Rule + val composeTestRule: ComposeContentTestRule = createComposeRule() + + @Test + fun testSettingsNetworkRequestDelayBox() { + composeTestRule.setContent { + MoviesTheme { + SettingsNetworkRequestDelayBox( + modifier = Modifier + .fillMaxWidth(), + delay = NETWORK_REQUEST_DELAY, + onDelayChangeFinished = {} + ) + } + } + + composeTestRule + .onNodeWithTag(testTag = "ConstraintLayout", useUnmergedTree = true) + .assertIsDisplayed() + .assertHasNoClickAction() + + composeTestRule + .onNodeWithTag(testTag = "TitleText", useUnmergedTree = true) + .assertIsDisplayed() + .assertHasNoClickAction() + + composeTestRule + .onNodeWithTag(testTag = "ValueText", useUnmergedTree = true) + .assertIsDisplayed() + .assertHasNoClickAction() + + composeTestRule + .onNodeWithTag(testTag = "Slider", useUnmergedTree = true) + .assertIsDisplayed() + .assertHasNoClickAction() + } + + private companion object { + private const val NETWORK_REQUEST_DELAY: Int = 0 + } +} \ No newline at end of file diff --git a/app/src/androidTest/kotlin/org/michaelbel/movies/SettingsPostNotificationsBoxTest.kt b/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsPostNotificationsBoxTest.kt similarity index 64% rename from app/src/androidTest/kotlin/org/michaelbel/movies/SettingsPostNotificationsBoxTest.kt rename to feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsPostNotificationsBoxTest.kt index fabe6122e..a3f9648bc 100644 --- a/app/src/androidTest/kotlin/org/michaelbel/movies/SettingsPostNotificationsBoxTest.kt +++ b/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsPostNotificationsBoxTest.kt @@ -1,23 +1,20 @@ -package org.michaelbel.movies +package org.michaelbel.movies.settings.ui -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.ui.Modifier -import androidx.compose.ui.test.assert +import androidx.compose.ui.test.assertHasClickAction +import androidx.compose.ui.test.assertHasNoClickAction import androidx.compose.ui.test.assertIsDisplayed -import androidx.compose.ui.test.hasClickAction -import androidx.compose.ui.test.hasNoClickAction import androidx.compose.ui.test.junit4.ComposeContentTestRule import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.unit.dp import org.junit.Rule import org.junit.Test -import org.michaelbel.movies.settings.ui.SettingsPostNotificationsBox import org.michaelbel.movies.ui.theme.MoviesTheme -class SettingsPostNotificationsBoxTest { +internal class SettingsPostNotificationsBoxTest { @get:Rule val composeTestRule: ComposeContentTestRule = createComposeRule() @@ -29,29 +26,25 @@ class SettingsPostNotificationsBoxTest { SettingsPostNotificationsBox( modifier = Modifier .fillMaxWidth() - .height(48.dp) - .clickable {}, - areNotificationsEnabled = ARE_NOTIFICATIONS_ENABLED + .height(48.dp), + onShowPermissionSnackbar = {} ) } } composeTestRule .onNodeWithTag(testTag = "ConstraintLayout", useUnmergedTree = true) - .assert(hasClickAction()) + .assertIsDisplayed() + .assertHasClickAction() composeTestRule .onNodeWithTag(testTag = "Text", useUnmergedTree = true) .assertIsDisplayed() - .assert(hasNoClickAction()) + .assertHasNoClickAction() composeTestRule .onNodeWithTag(testTag = "Switch", useUnmergedTree = true) .assertIsDisplayed() - .assert(hasNoClickAction()) - } - - private companion object { - private const val ARE_NOTIFICATIONS_ENABLED = true + .assertHasNoClickAction() } } \ No newline at end of file diff --git a/app/src/androidTest/kotlin/org/michaelbel/movies/SettingsReviewBoxTest.kt b/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsReviewBoxTest.kt similarity index 78% rename from app/src/androidTest/kotlin/org/michaelbel/movies/SettingsReviewBoxTest.kt rename to feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsReviewBoxTest.kt index 8ef40df1b..2e8f30ca3 100644 --- a/app/src/androidTest/kotlin/org/michaelbel/movies/SettingsReviewBoxTest.kt +++ b/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsReviewBoxTest.kt @@ -1,23 +1,21 @@ -package org.michaelbel.movies +package org.michaelbel.movies.settings.ui import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.ui.Modifier -import androidx.compose.ui.test.assert +import androidx.compose.ui.test.assertHasClickAction +import androidx.compose.ui.test.assertHasNoClickAction import androidx.compose.ui.test.assertIsDisplayed -import androidx.compose.ui.test.hasClickAction -import androidx.compose.ui.test.hasNoClickAction import androidx.compose.ui.test.junit4.ComposeContentTestRule import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.unit.dp import org.junit.Rule import org.junit.Test -import org.michaelbel.movies.settings.ui.SettingsReviewBox import org.michaelbel.movies.ui.theme.MoviesTheme -class SettingsReviewBoxTest { +internal class SettingsReviewBoxTest { @get:Rule val composeTestRule: ComposeContentTestRule = createComposeRule() @@ -37,11 +35,12 @@ class SettingsReviewBoxTest { composeTestRule .onNodeWithTag(testTag = "ConstraintLayout", useUnmergedTree = true) - .assert(hasClickAction()) + .assertIsDisplayed() + .assertHasClickAction() composeTestRule .onNodeWithTag(testTag = "Text", useUnmergedTree = true) .assertIsDisplayed() - .assert(hasNoClickAction()) + .assertHasNoClickAction() } } \ No newline at end of file diff --git a/app/src/androidTest/kotlin/org/michaelbel/movies/SettingsRtlBoxTest.kt b/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsRtlBoxTest.kt similarity index 75% rename from app/src/androidTest/kotlin/org/michaelbel/movies/SettingsRtlBoxTest.kt rename to feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsRtlBoxTest.kt index 62d69af78..f74c215ab 100644 --- a/app/src/androidTest/kotlin/org/michaelbel/movies/SettingsRtlBoxTest.kt +++ b/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsRtlBoxTest.kt @@ -1,23 +1,21 @@ -package org.michaelbel.movies +package org.michaelbel.movies.settings.ui import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.ui.Modifier -import androidx.compose.ui.test.assert +import androidx.compose.ui.test.assertHasClickAction +import androidx.compose.ui.test.assertHasNoClickAction import androidx.compose.ui.test.assertIsDisplayed -import androidx.compose.ui.test.hasClickAction -import androidx.compose.ui.test.hasNoClickAction import androidx.compose.ui.test.junit4.ComposeContentTestRule import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.unit.dp import org.junit.Rule import org.junit.Test -import org.michaelbel.movies.settings.ui.SettingsRtlBox import org.michaelbel.movies.ui.theme.MoviesTheme -class SettingsRtlBoxTest { +internal class SettingsRtlBoxTest { @get:Rule val composeTestRule: ComposeContentTestRule = createComposeRule() @@ -27,28 +25,29 @@ class SettingsRtlBoxTest { composeTestRule.setContent { MoviesTheme { SettingsRtlBox( + isRtlEnabled = IS_RTL_ENABLED, modifier = Modifier .fillMaxWidth() .height(48.dp) - .clickable {}, - isRtlEnabled = IS_RTL_ENABLED + .clickable {} ) } } composeTestRule .onNodeWithTag(testTag = "ConstraintLayout", useUnmergedTree = true) - .assert(hasClickAction()) + .assertIsDisplayed() + .assertHasClickAction() composeTestRule .onNodeWithTag(testTag = "Text", useUnmergedTree = true) .assertIsDisplayed() - .assert(hasNoClickAction()) + .assertHasNoClickAction() composeTestRule .onNodeWithTag(testTag = "Switch", useUnmergedTree = true) .assertIsDisplayed() - .assert(hasNoClickAction()) + .assertHasNoClickAction() } private companion object { diff --git a/app/src/androidTest/kotlin/org/michaelbel/movies/SettingsThemeBoxTest.kt b/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsThemeBoxTest.kt similarity index 66% rename from app/src/androidTest/kotlin/org/michaelbel/movies/SettingsThemeBoxTest.kt rename to feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsThemeBoxTest.kt index 1b98968dc..e416a69d3 100644 --- a/app/src/androidTest/kotlin/org/michaelbel/movies/SettingsThemeBoxTest.kt +++ b/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsThemeBoxTest.kt @@ -1,24 +1,21 @@ -package org.michaelbel.movies +package org.michaelbel.movies.settings.ui -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.ui.Modifier -import androidx.compose.ui.test.assert +import androidx.compose.ui.test.assertHasClickAction +import androidx.compose.ui.test.assertHasNoClickAction import androidx.compose.ui.test.assertIsDisplayed -import androidx.compose.ui.test.hasClickAction -import androidx.compose.ui.test.hasNoClickAction import androidx.compose.ui.test.junit4.ComposeContentTestRule import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.unit.dp import org.junit.Rule import org.junit.Test -import org.michaelbel.movies.settings.ui.SettingsThemeBox +import org.michaelbel.movies.common.theme.AppTheme import org.michaelbel.movies.ui.theme.MoviesTheme -import org.michaelbel.movies.ui.theme.model.AppTheme -class SettingsThemeBoxTest { +internal class SettingsThemeBoxTest { @get:Rule val composeTestRule: ComposeContentTestRule = createComposeRule() @@ -28,31 +25,38 @@ class SettingsThemeBoxTest { composeTestRule.setContent { MoviesTheme { SettingsThemeBox( + themes = THEMES, + currentTheme = CURRENT_THEME, + onThemeSelect = {}, modifier = Modifier .fillMaxWidth() .height(48.dp) - .clickable {}, - currentTheme = CURRENT_THEME ) } } composeTestRule .onNodeWithTag(testTag = "ConstraintLayout", useUnmergedTree = true) - .assert(hasClickAction()) + .assertIsDisplayed() + .assertHasClickAction() composeTestRule .onNodeWithTag(testTag = "TitleText", useUnmergedTree = true) .assertIsDisplayed() - .assert(hasNoClickAction()) + .assertHasNoClickAction() composeTestRule .onNodeWithTag(testTag = "ValueText", useUnmergedTree = true) .assertIsDisplayed() - .assert(hasNoClickAction()) + .assertHasNoClickAction() } private companion object { + private val THEMES: List = listOf( + AppTheme.NightNo, + AppTheme.NightYes, + AppTheme.FollowSystem + ) private val CURRENT_THEME = AppTheme.FollowSystem } } \ No newline at end of file diff --git a/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsThemeDialogTest.kt b/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsThemeDialogTest.kt new file mode 100644 index 000000000..0341bc666 --- /dev/null +++ b/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsThemeDialogTest.kt @@ -0,0 +1,65 @@ +package org.michaelbel.movies.settings.ui + +import androidx.compose.ui.test.assertHasClickAction +import androidx.compose.ui.test.assertHasNoClickAction +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.ComposeContentTestRule +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithTag +import org.junit.Rule +import org.junit.Test +import org.michaelbel.movies.common.theme.AppTheme +import org.michaelbel.movies.ui.theme.MoviesTheme + +internal class SettingsThemeDialogTest { + + @get:Rule + val composeTestRule: ComposeContentTestRule = createComposeRule() + + @Test + fun testSettingsThemeDialog() { + composeTestRule.setContent { + MoviesTheme { + SettingThemeDialog( + themes = THEMES, + currentTheme = CURRENT_THEME, + onThemeSelect = {}, + onDismissRequest = {} + ) + } + } + + composeTestRule + .onNodeWithTag(testTag = "ConfirmTextButton", useUnmergedTree = true) + .assertIsDisplayed() + .assertHasClickAction() + + composeTestRule + .onNodeWithTag(testTag = "ConfirmText", useUnmergedTree = true) + .assertIsDisplayed() + .assertHasNoClickAction() + + composeTestRule + .onNodeWithTag(testTag = "Icon", useUnmergedTree = true) + .assertIsDisplayed() + .assertHasNoClickAction() + + composeTestRule + .onNodeWithTag(testTag = "Title", useUnmergedTree = true) + .assertIsDisplayed() + .assertHasNoClickAction() + + composeTestRule + .onNodeWithTag(testTag = "Content", useUnmergedTree = true) + .assertIsDisplayed() + } + + private companion object { + private val THEMES: List = listOf( + AppTheme.NightNo, + AppTheme.NightYes, + AppTheme.FollowSystem + ) + private val CURRENT_THEME = AppTheme.FollowSystem + } +} \ No newline at end of file diff --git a/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsToolbarTest.kt b/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsToolbarTest.kt new file mode 100644 index 000000000..8a1e5e9b3 --- /dev/null +++ b/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsToolbarTest.kt @@ -0,0 +1,52 @@ +package org.michaelbel.movies.settings.ui + +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.ui.Modifier +import androidx.compose.ui.test.assertHasClickAction +import androidx.compose.ui.test.assertHasNoClickAction +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.ComposeContentTestRule +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithTag +import org.junit.Rule +import org.junit.Test +import org.michaelbel.movies.ui.theme.MoviesTheme + +internal class SettingsToolbarTest { + + @get:Rule + val composeTestRule: ComposeContentTestRule = createComposeRule() + + @Test + fun testSettingsToolbar() { + composeTestRule.setContent { + MoviesTheme { + SettingsToolbar( + modifier = Modifier + .statusBarsPadding(), + onNavigationIconClick = {} + ) + } + } + + composeTestRule + .onNodeWithTag(testTag = "TopAppBar", useUnmergedTree = true) + .assertIsDisplayed() + .assertHasNoClickAction() + + composeTestRule + .onNodeWithTag(testTag = "TitleText", useUnmergedTree = true) + .assertIsDisplayed() + .assertHasNoClickAction() + + composeTestRule + .onNodeWithTag(testTag = "BackIconButton", useUnmergedTree = true) + .assertIsDisplayed() + .assertHasClickAction() + + composeTestRule + .onNodeWithTag(testTag = "BackImage", useUnmergedTree = true) + .assertIsDisplayed() + .assertHasNoClickAction() + } +} \ No newline at end of file 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 new file mode 100644 index 000000000..5ca7e95aa --- /dev/null +++ b/feature/settings-impl/src/androidTest/kotlin/org/michaelbel/movies/settings/ui/SettingsVersionBoxTest.kt @@ -0,0 +1,58 @@ +package org.michaelbel.movies.settings.ui + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.ui.Modifier +import androidx.compose.ui.test.assertHasNoClickAction +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.ComposeContentTestRule +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithTag +import org.junit.Rule +import org.junit.Test +import org.michaelbel.movies.common.version.AppVersionData +import org.michaelbel.movies.ui.theme.MoviesTheme + +internal class SettingsVersionBoxTest { + + @get:Rule + val composeTestRule: ComposeContentTestRule = createComposeRule() + + @Test + fun testSettingsVersionBox() { + composeTestRule.setContent { + MoviesTheme { + SettingsVersionBox( + appVersionData = APP_VERSION_DATA, + modifier = Modifier + .navigationBarsPadding() + .fillMaxWidth() + ) + } + } + + composeTestRule + .onNodeWithTag(testTag = "ConstraintLayout", useUnmergedTree = true) + .assertIsDisplayed() + .assertHasNoClickAction() + + composeTestRule + .onNodeWithTag(testTag = "Icon", useUnmergedTree = true) + .assertIsDisplayed() + .assertHasNoClickAction() + + composeTestRule + .onNodeWithTag(testTag = "TitleText", useUnmergedTree = true) + .assertIsDisplayed() + .assertHasNoClickAction() + + composeTestRule + .onNodeWithTag(testTag = "ValueText", useUnmergedTree = true) + .assertIsDisplayed() + .assertHasNoClickAction() + } + + private companion object { + private val APP_VERSION_DATA: AppVersionData = AppVersionData("1.0.0", 1L) + } +} \ No newline at end of file diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsDynamicColorsBox.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsDynamicColorsBox.kt index a6652e9f8..32f052d7b 100644 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsDynamicColorsBox.kt +++ b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsDynamicColorsBox.kt @@ -21,12 +21,11 @@ import org.michaelbel.movies.ui.theme.MoviesTheme @Composable fun SettingsDynamicColorsBox( - modifier: Modifier = Modifier, - isDynamicColorsEnabled: Boolean + isDynamicColorsEnabled: Boolean, + modifier: Modifier = Modifier ) { ConstraintLayout( - modifier = modifier - .testTag("ConstraintLayout") + modifier = modifier.testTag("ConstraintLayout") ) { val (title, value) = createRefs() @@ -41,8 +40,9 @@ fun SettingsDynamicColorsBox( bottom.linkTo(parent.bottom) } .testTag("Text"), - color = MaterialTheme.colorScheme.onPrimaryContainer, - style = MaterialTheme.typography.bodyLarge + style = MaterialTheme.typography.bodyLarge.copy( + color = MaterialTheme.colorScheme.onPrimaryContainer + ) ) Switch( @@ -68,11 +68,11 @@ private fun SettingsDynamicColorsBoxPreview( ) { MoviesTheme { SettingsDynamicColorsBox( + isDynamicColorsEnabled = isEnabled, modifier = Modifier .fillMaxWidth() .height(48.dp) - .background(MaterialTheme.colorScheme.primaryContainer), - isDynamicColorsEnabled = isEnabled + .background(MaterialTheme.colorScheme.primaryContainer) ) } } \ No newline at end of file diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsLanguageBox.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsLanguageBox.kt index 33fb81f89..321b647df 100644 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsLanguageBox.kt +++ b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsLanguageBox.kt @@ -27,10 +27,10 @@ import org.michaelbel.movies.ui.theme.MoviesTheme @Composable fun SettingsLanguageBox( - modifier: Modifier = Modifier, languages: List, currentLanguage: AppLanguage, - onLanguageSelect: (AppLanguage) -> Unit + onLanguageSelect: (AppLanguage) -> Unit, + modifier: Modifier = Modifier, ) { var languageDialog: Boolean by remember { mutableStateOf(false) } @@ -65,8 +65,9 @@ fun SettingsLanguageBox( bottom.linkTo(parent.bottom) } .testTag("TitleText"), - color = MaterialTheme.colorScheme.onPrimaryContainer, - style = MaterialTheme.typography.bodyLarge + style = MaterialTheme.typography.bodyLarge.copy( + color = MaterialTheme.colorScheme.onPrimaryContainer + ) ) Text( @@ -80,8 +81,9 @@ fun SettingsLanguageBox( bottom.linkTo(parent.bottom) } .testTag("ValueText"), - color = MaterialTheme.colorScheme.primary, - style = MaterialTheme.typography.bodyLarge + style = MaterialTheme.typography.bodyLarge.copy( + color = MaterialTheme.colorScheme.primary + ) ) } } @@ -93,13 +95,13 @@ private fun SettingsLanguageBoxPreview( ) { MoviesTheme { SettingsLanguageBox( + languages = listOf(AppLanguage.English, AppLanguage.Russian), + currentLanguage = language, + onLanguageSelect = {}, modifier = Modifier .fillMaxWidth() .height(48.dp) - .background(MaterialTheme.colorScheme.primaryContainer), - languages = listOf(AppLanguage.English, AppLanguage.Russian), - currentLanguage = language, - onLanguageSelect = {} + .background(MaterialTheme.colorScheme.primaryContainer) ) } } \ No newline at end of file diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsLanguageDialog.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsLanguageDialog.kt index cbac8a4ee..900e1bd7e 100644 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsLanguageDialog.kt +++ b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsLanguageDialog.kt @@ -17,6 +17,7 @@ import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp @@ -30,7 +31,7 @@ import org.michaelbel.movies.ui.preview.provider.LanguagePreviewParameterProvide import org.michaelbel.movies.ui.theme.MoviesTheme @Composable -internal fun SettingLanguageDialog( +fun SettingLanguageDialog( languages: List, currentLanguage: AppLanguage, onLanguageSelect: (AppLanguage) -> Unit, @@ -41,25 +42,35 @@ internal fun SettingLanguageDialog( confirmButton = { TextButton( onClick = onDismissRequest, + modifier = Modifier + .testTag("ConfirmTextButton") ) { Text( text = stringResource(R.string.settings_action_cancel), - color = MaterialTheme.colorScheme.primary, - style = MaterialTheme.typography.labelLarge + modifier = Modifier + .testTag("ConfirmText"), + style = MaterialTheme.typography.labelLarge.copy( + color = MaterialTheme.colorScheme.primary + ) ) } }, icon = { Icon( imageVector = MoviesIcons.Language, - contentDescription = null + contentDescription = null, + modifier = Modifier + .testTag("Icon") ) }, title = { Text( text = stringResource(R.string.settings_language), - color = MaterialTheme.colorScheme.onSurface, - style = MaterialTheme.typography.headlineSmall + modifier = Modifier + .testTag("Title"), + style = MaterialTheme.typography.headlineSmall.copy( + color = MaterialTheme.colorScheme.onSurface + ) ) }, text = { @@ -69,7 +80,8 @@ internal fun SettingLanguageDialog( onLanguageSelect = { language -> onLanguageSelect(language) onDismissRequest() - } + }, + modifier = Modifier.testTag("Content") ) }, shape = RoundedCornerShape(28.dp), @@ -87,9 +99,12 @@ internal fun SettingLanguageDialog( private fun SettingLanguageDialogContent( languages: List, currentLanguage: AppLanguage, - onLanguageSelect: (AppLanguage) -> Unit + onLanguageSelect: (AppLanguage) -> Unit, + modifier: Modifier = Modifier ) { - Column { + Column( + modifier = modifier + ) { languages.forEach { language: AppLanguage -> Row( modifier = Modifier @@ -108,19 +123,16 @@ private fun SettingLanguageDialogContent( unselectedColor = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.6F) ), modifier = Modifier - .padding( - start = 16.dp - ) + .padding(start = 16.dp) ) Text( text = language.languageText, modifier = Modifier - .padding( - start = 8.dp - ), - color = MaterialTheme.colorScheme.onBackground, - style = MaterialTheme.typography.bodyLarge + .padding(start = 8.dp), + style = MaterialTheme.typography.bodyLarge.copy( + color = MaterialTheme.colorScheme.onBackground + ) ) } } diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsNetworkRequestDelayBox.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsNetworkRequestDelayBox.kt index 4ecddd285..2c3f7d975 100644 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsNetworkRequestDelayBox.kt +++ b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsNetworkRequestDelayBox.kt @@ -12,6 +12,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp @@ -25,15 +26,16 @@ import kotlin.math.roundToInt @Composable fun SettingsNetworkRequestDelayBox( - modifier: Modifier = Modifier, delay: Int, - onDelayChangeFinished: (Int) -> Unit + onDelayChangeFinished: (Int) -> Unit, + modifier: Modifier = Modifier ) { var sliderPosition: Float by remember { mutableStateOf(delay.toFloat()) } sliderPosition = delay.toFloat() ConstraintLayout( modifier = modifier + .testTag("ConstraintLayout") ) { val (title, value, slider) = createRefs() @@ -45,9 +47,11 @@ fun SettingsNetworkRequestDelayBox( height = Dimension.wrapContent start.linkTo(parent.start, 16.dp) top.linkTo(parent.top, 8.dp) - }, - color = MaterialTheme.colorScheme.onPrimaryContainer, - style = MaterialTheme.typography.bodyLarge + } + .testTag("TitleText"), + style = MaterialTheme.typography.bodyLarge.copy( + color = MaterialTheme.colorScheme.onPrimaryContainer + ) ) Text( @@ -58,9 +62,11 @@ fun SettingsNetworkRequestDelayBox( height = Dimension.wrapContent top.linkTo(parent.top, 8.dp) end.linkTo(parent.end, 16.dp) - }, - color = MaterialTheme.colorScheme.primary, - style = MaterialTheme.typography.bodyLarge + } + .testTag("ValueText"), + style = MaterialTheme.typography.bodyLarge.copy( + color = MaterialTheme.colorScheme.primary + ) ) Slider( @@ -76,7 +82,8 @@ fun SettingsNetworkRequestDelayBox( top.linkTo(title.bottom, 4.dp) end.linkTo(parent.end, 16.dp) bottom.linkTo(parent.bottom, 8.dp) - }, + } + .testTag("Slider"), valueRange = 0F..10000F, steps = 9, onValueChangeFinished = { diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsPostNotificationsBox.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsPostNotificationsBox.kt index c081bf79b..3b20fc672 100644 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsPostNotificationsBox.kt +++ b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsPostNotificationsBox.kt @@ -102,8 +102,9 @@ fun SettingsPostNotificationsBox( bottom.linkTo(parent.bottom) } .testTag("Text"), - color = MaterialTheme.colorScheme.onPrimaryContainer, - style = MaterialTheme.typography.bodyLarge + style = MaterialTheme.typography.bodyLarge.copy( + color = MaterialTheme.colorScheme.onPrimaryContainer + ) ) Switch( diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsReviewBox.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsReviewBox.kt index 8442fdfbb..18b052f02 100644 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsReviewBox.kt +++ b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsReviewBox.kt @@ -21,8 +21,7 @@ fun SettingsReviewBox( modifier: Modifier = Modifier ) { ConstraintLayout( - modifier = modifier - .testTag("ConstraintLayout") + modifier = modifier.testTag("ConstraintLayout") ) { val (title) = createRefs() @@ -37,8 +36,9 @@ fun SettingsReviewBox( bottom.linkTo(parent.bottom) } .testTag("Text"), - color = MaterialTheme.colorScheme.onPrimaryContainer, - style = MaterialTheme.typography.bodyLarge + style = MaterialTheme.typography.bodyLarge.copy( + color = MaterialTheme.colorScheme.onPrimaryContainer + ) ) } } diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsRtlBox.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsRtlBox.kt index 427c79a33..0e15bd298 100644 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsRtlBox.kt +++ b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsRtlBox.kt @@ -21,12 +21,11 @@ import org.michaelbel.movies.ui.theme.MoviesTheme @Composable fun SettingsRtlBox( - modifier: Modifier = Modifier, - isRtlEnabled: Boolean + isRtlEnabled: Boolean, + modifier: Modifier = Modifier ) { ConstraintLayout( - modifier = modifier - .testTag("ConstraintLayout") + modifier = modifier.testTag("ConstraintLayout") ) { val (title, value) = createRefs() @@ -41,8 +40,9 @@ fun SettingsRtlBox( bottom.linkTo(parent.bottom) } .testTag("Text"), - color = MaterialTheme.colorScheme.onPrimaryContainer, - style = MaterialTheme.typography.bodyLarge + style = MaterialTheme.typography.bodyLarge.copy( + color = MaterialTheme.colorScheme.onPrimaryContainer + ) ) Switch( @@ -68,11 +68,11 @@ private fun SettingsRtlBoxPreview( ) { MoviesTheme { SettingsRtlBox( + isRtlEnabled = isEnabled, modifier = Modifier .fillMaxWidth() .height(48.dp) - .background(MaterialTheme.colorScheme.primaryContainer), - isRtlEnabled = isEnabled + .background(MaterialTheme.colorScheme.primaryContainer) ) } } \ No newline at end of file diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsScreenContent.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsScreenContent.kt index 4f2314044..0fd3e41d9 100644 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsScreenContent.kt +++ b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsScreenContent.kt @@ -63,7 +63,6 @@ fun SettingsRoute( SettingsScreenContent( onBackClick = onBackClick, - modifier = modifier, languages = viewModel.languages, currentLanguage = currentLanguage, onLanguageSelect = viewModel::selectLanguage, @@ -81,14 +80,14 @@ fun SettingsRoute( isAppFromGooglePlay = isAppFromGooglePlay, networkRequestDelay = networkRequestDelay, onDelayChangeFinished = viewModel::setNetworkRequestDelay, - appVersionData = appVersionData + appVersionData = appVersionData, + modifier = modifier ) } @Composable private fun SettingsScreenContent( onBackClick: () -> Unit, - modifier: Modifier = Modifier, languages: List, currentLanguage: AppLanguage, onLanguageSelect: (AppLanguage) -> Unit, @@ -106,7 +105,8 @@ private fun SettingsScreenContent( isAppFromGooglePlay: Boolean, networkRequestDelay: Int, onDelayChangeFinished: (Int) -> Unit, - appVersionData: AppVersionData + appVersionData: AppVersionData, + modifier: Modifier = Modifier ) { val context: Context = LocalContext.current val scope: CoroutineScope = rememberCoroutineScope() @@ -178,11 +178,11 @@ private fun SettingsScreenContent( ) }, bottomBar = { - SettingsLanguageBox( + SettingsVersionBox( + appVersionData = appVersionData, modifier = Modifier .navigationBarsPadding() - .fillMaxWidth(), - appVersionData = appVersionData + .fillMaxWidth() ) }, snackbarHost = { @@ -193,48 +193,47 @@ private fun SettingsScreenContent( containerColor = MaterialTheme.colorScheme.primaryContainer ) { paddingValues: PaddingValues -> Column( - modifier = Modifier - .padding(paddingValues) + modifier = Modifier.padding(paddingValues) ) { SettingsLanguageBox( - modifier = Modifier - .fillMaxWidth() - .height(48.dp), languages = languages, currentLanguage = currentLanguage, - onLanguageSelect = onLanguageSelect + onLanguageSelect = onLanguageSelect, + modifier = Modifier + .fillMaxWidth() + .height(48.dp) ) SettingsThemeBox( - modifier = Modifier - .fillMaxWidth() - .height(48.dp), themes = themes, currentTheme = currentTheme, - onThemeSelect = onThemeSelect + onThemeSelect = onThemeSelect, + modifier = Modifier + .fillMaxWidth() + .height(48.dp) ) if (isDynamicColorsFeatureEnabled) { SettingsDynamicColorsBox( + isDynamicColorsEnabled = dynamicColors, modifier = Modifier .fillMaxWidth() .height(48.dp) .clickable { onSetDynamicColors(!dynamicColors) - }, - isDynamicColorsEnabled = dynamicColors + } ) } if (isRtlFeatureEnabled) { SettingsRtlBox( + isRtlEnabled = isRtlEnabled, modifier = Modifier .fillMaxWidth() .height(48.dp) .clickable { onEnableRtlChanged(!isRtlEnabled) - }, - isRtlEnabled = isRtlEnabled + } ) } @@ -258,10 +257,9 @@ private fun SettingsScreenContent( if (BuildConfig.DEBUG) { SettingsNetworkRequestDelayBox( - modifier = Modifier - .fillMaxWidth(), delay = networkRequestDelay, - onDelayChangeFinished = onDelayChangeFinished + onDelayChangeFinished = onDelayChangeFinished, + modifier = Modifier.fillMaxWidth() ) } } diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsThemeBox.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsThemeBox.kt index 066339022..419baa594 100644 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsThemeBox.kt +++ b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsThemeBox.kt @@ -27,10 +27,10 @@ import org.michaelbel.movies.ui.theme.MoviesTheme @Composable fun SettingsThemeBox( - modifier: Modifier = Modifier, themes: List, currentTheme: AppTheme, - onThemeSelect: (AppTheme) -> Unit + onThemeSelect: (AppTheme) -> Unit, + modifier: Modifier = Modifier ) { var themeDialog: Boolean by remember { mutableStateOf(false) } @@ -65,8 +65,9 @@ fun SettingsThemeBox( bottom.linkTo(parent.bottom) } .testTag("TitleText"), - color = MaterialTheme.colorScheme.onPrimaryContainer, - style = MaterialTheme.typography.bodyLarge + style = MaterialTheme.typography.bodyLarge.copy( + color = MaterialTheme.colorScheme.onPrimaryContainer + ) ) Text( @@ -80,8 +81,9 @@ fun SettingsThemeBox( bottom.linkTo(parent.bottom) } .testTag("ValueText"), - color = MaterialTheme.colorScheme.primary, - style = MaterialTheme.typography.bodyLarge + style = MaterialTheme.typography.bodyLarge.copy( + color = MaterialTheme.colorScheme.primary + ) ) } } @@ -93,13 +95,13 @@ private fun SettingsThemeBoxPreview( ) { MoviesTheme { SettingsThemeBox( + themes = listOf(AppTheme.FollowSystem, AppTheme.NightNo, AppTheme.NightYes), + currentTheme = theme, + onThemeSelect = {}, modifier = Modifier .fillMaxWidth() .height(48.dp) - .background(MaterialTheme.colorScheme.primaryContainer), - themes = listOf(AppTheme.FollowSystem, AppTheme.NightNo, AppTheme.NightYes), - currentTheme = theme, - onThemeSelect = {} + .background(MaterialTheme.colorScheme.primaryContainer) ) } } \ No newline at end of file diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsThemeDialog.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsThemeDialog.kt index 9e3a66867..7108e505f 100644 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsThemeDialog.kt +++ b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsThemeDialog.kt @@ -17,6 +17,7 @@ import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter @@ -42,25 +43,35 @@ internal fun SettingThemeDialog( confirmButton = { TextButton( onClick = onDismissRequest, + modifier = Modifier + .testTag("ConfirmTextButton") ) { Text( text = stringResource(R.string.settings_action_cancel), - color = MaterialTheme.colorScheme.primary, - style = MaterialTheme.typography.labelLarge + modifier = Modifier + .testTag("ConfirmText"), + style = MaterialTheme.typography.labelLarge.copy( + color = MaterialTheme.colorScheme.primary + ) ) } }, icon = { Icon( painter = painterResource(MoviesIcons.ThemeLightDark), - contentDescription = null + contentDescription = null, + modifier = Modifier + .testTag("Icon") ) }, title = { Text( text = stringResource(R.string.settings_theme), - color = MaterialTheme.colorScheme.onSurface, - style = MaterialTheme.typography.headlineSmall + modifier = Modifier + .testTag("Title"), + style = MaterialTheme.typography.headlineSmall.copy( + color = MaterialTheme.colorScheme.onSurface + ) ) }, text = { @@ -70,7 +81,8 @@ internal fun SettingThemeDialog( onThemeSelect = { theme -> onThemeSelect(theme) onDismissRequest() - } + }, + modifier = Modifier.testTag("Content") ) }, shape = RoundedCornerShape(28.dp), @@ -88,9 +100,12 @@ internal fun SettingThemeDialog( private fun SettingThemeDialogContent( themes: List, currentTheme: AppTheme, - onThemeSelect: (AppTheme) -> Unit + onThemeSelect: (AppTheme) -> Unit, + modifier: Modifier = Modifier ) { - Column { + Column( + modifier = modifier + ) { themes.forEach { theme: AppTheme -> Row( modifier = Modifier @@ -109,19 +124,16 @@ private fun SettingThemeDialogContent( unselectedColor = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.6F) ), modifier = Modifier - .padding( - start = 16.dp - ) + .padding(start = 16.dp) ) Text( text = theme.themeText, modifier = Modifier - .padding( - start = 8.dp - ), - color = MaterialTheme.colorScheme.onBackground, - style = MaterialTheme.typography.bodyLarge + .padding(start = 8.dp), + style = MaterialTheme.typography.bodyLarge.copy( + color = MaterialTheme.colorScheme.onBackground + ) ) } } diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsToolbar.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsToolbar.kt index 5426b1fd3..2f58fad3c 100644 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsToolbar.kt +++ b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsToolbar.kt @@ -11,6 +11,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import org.michaelbel.movies.settings_impl.R @@ -27,20 +28,24 @@ internal fun SettingsToolbar( title = { Text( text = stringResource(R.string.settings_title), - color = MaterialTheme.colorScheme.onPrimaryContainer, + modifier = Modifier.testTag("TitleText"), overflow = TextOverflow.Ellipsis, maxLines = 1, - style = MaterialTheme.typography.titleLarge + style = MaterialTheme.typography.titleLarge.copy( + color = MaterialTheme.colorScheme.onPrimaryContainer + ) ) }, - modifier = modifier, + modifier = modifier.testTag("TopAppBar"), navigationIcon = { IconButton( - onClick = onNavigationIconClick + onClick = onNavigationIconClick, + modifier = Modifier.testTag("BackIconButton") ) { Image( imageVector = MoviesIcons.ArrowBack, contentDescription = null, + modifier = Modifier.testTag("BackImage"), colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onPrimaryContainer) ) } @@ -56,8 +61,7 @@ internal fun SettingsToolbar( private fun SettingsToolbarPreview() { MoviesTheme { SettingsToolbar( - modifier = Modifier - .statusBarsPadding(), + modifier = Modifier.statusBarsPadding(), onNavigationIconClick = {} ) } diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsVersionBox.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsVersionBox.kt index 125a46bea..5fcbbde43 100644 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsVersionBox.kt +++ b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsVersionBox.kt @@ -24,13 +24,12 @@ import org.michaelbel.movies.ui.preview.provider.VersionPreviewParameterProvider import org.michaelbel.movies.ui.theme.MoviesTheme @Composable -fun SettingsLanguageBox( - modifier: Modifier = Modifier, - appVersionData: AppVersionData +fun SettingsVersionBox( + appVersionData: AppVersionData, + modifier: Modifier = Modifier ) { ConstraintLayout( - modifier = modifier - .testTag("ConstraintLayout") + modifier = modifier.testTag("ConstraintLayout") ) { val (icon, version, code) = createRefs() createHorizontalChain(icon, version, code, chainStyle = ChainStyle.Packed) @@ -46,7 +45,8 @@ fun SettingsLanguageBox( top.linkTo(parent.top) end.linkTo(version.start) bottom.linkTo(parent.bottom) - }, + } + .testTag("Icon"), tint = MaterialTheme.colorScheme.primary ) @@ -63,8 +63,9 @@ fun SettingsLanguageBox( } .padding(start = 4.dp) .testTag("TitleText"), - color = MaterialTheme.colorScheme.onPrimaryContainer, - style = MaterialTheme.typography.bodyMedium + style = MaterialTheme.typography.bodyMedium.copy( + color = MaterialTheme.colorScheme.onPrimaryContainer + ) ) Text( @@ -80,24 +81,25 @@ fun SettingsLanguageBox( } .padding(start = 2.dp) .testTag("ValueText"), - color = MaterialTheme.colorScheme.primary, - style = MaterialTheme.typography.bodySmall + style = MaterialTheme.typography.bodySmall.copy( + color = MaterialTheme.colorScheme.primary + ) ) } } @Composable @DevicePreviews -private fun SettingsLanguageBoxPreview( +private fun SettingsVersionBoxPreview( @PreviewParameter(VersionPreviewParameterProvider::class) appVersionData: AppVersionData ) { MoviesTheme { - SettingsLanguageBox( + SettingsVersionBox( + appVersionData = appVersionData, modifier = Modifier .fillMaxWidth() .wrapContentHeight() - .background(MaterialTheme.colorScheme.primaryContainer), - appVersionData = appVersionData + .background(MaterialTheme.colorScheme.primaryContainer) ) } } \ No newline at end of file diff --git a/feature/settings/build.gradle.kts b/feature/settings/build.gradle.kts index bcda9937d..827b0c8a1 100644 --- a/feature/settings/build.gradle.kts +++ b/feature/settings/build.gradle.kts @@ -1,6 +1,7 @@ +@Suppress("dsl_scope_violation") plugins { - id("movies-android-library") - id("movies-android-library-compose") + alias(libs.plugins.library) + alias(libs.plugins.kotlin) } android { @@ -27,6 +28,11 @@ android { kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get() } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + lint { quiet = true abortOnError = false @@ -38,4 +44,6 @@ android { dependencies { implementation(project(":feature:settings-impl")) + + lintChecks(libs.lint.checks) } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 0e3fb97ea..8d18c27d9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,18 +1,23 @@ -org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 -org.gradle.parallel=false +org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8 -kotlin.code.style=official +# https://docs.gradle.org/current/userguide/performance.html#parallel_execution +org.gradle.parallel=true +# https://d.android.com/jetpack/androidx/migrate android.useAndroidX=true -android.enableJetifier=true -android.nonTransitiveRClass=true -android.defaults.buildFeatures.aidl=false -android.defaults.buildFeatures.buildConfig=false -android.defaults.buildFeatures.compose=false -android.defaults.buildFeatures.dataBinding=false -android.defaults.buildFeatures.prefab=false -android.defaults.buildFeatures.renderScript=false +# If your project already has the enableJetifier flag and it's turned on, you can run +# Build Analyzer's Jetifier check to confirm if it's actually needed. +# https://d.android.com/jetpack/androidx/migrate#migrate_an_existing_project_using_android_studio +# https://d.android.com/build/optimize-your-build#disable-the-jetifier-flag +android.enableJetifier=false + +# https://d.android.com/reference/tools/gradle-api/8.0/com/android/build/api/dsl/BuildFeatures#resValues() +# Flag to enable Resource Values generation. +# Default value is true. android.defaults.buildFeatures.resValues=false -android.defaults.buildFeatures.shaders=false -android.defaults.buildFeatures.viewBinding=false \ No newline at end of file + +# https://d.android.com/reference/tools/gradle-api/8.0/com/android/build/api/dsl/BuildFeatures#shaders() +# Flag to enable Shader compilation. +# Default value is true. +android.defaults.buildFeatures.shaders=false \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 15d604c2b..e5b68e605 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,64 +8,68 @@ compile-sdk = "33" # @keep target-sdk = "33" # @pin -gradle = "7.4.2" +gradle = "8.0.0" # @pin update when updating compose compiler -kotlin = "1.8.10" -kotlin-ksp = "1.8.10-1.0.9" +kotlin = "1.8.20" +kotlin-ksp = "1.8.20-1.0.11" kotlin-coroutines = "1.6.4" kotlin-serialization = "1.5.0" # @pin update when updating kotlin -compose-compiler = "1.4.3" +compose-compiler = "1.4.6" detekt = "1.22.0" -spotless = "6.17.0" +spotless = "6.18.0" google-services = "4.3.15" -play-services-ads = "21.5.0" +play-services-ads = "22.0.0" play-services-base = "18.2.0" -firebase-analytics = "21.2.0" +firebase-analytics = "21.2.2" firebase-appdistribution = "3.2.0" -firebase-config = "21.2.1" +firebase-config = "21.3.0" firebase-crashlytics-plugin = "2.9.4" -firebase-crashlytics = "18.3.5" +firebase-crashlytics = "18.3.6" play-core = "1.8.1" -accompanist = "0.28.0" +accompanist = "0.30.1" material = "1.8.0" material-compose-theme-adapter = "1.2.1" hilt = "2.45" -androidx-compose-foundation = "1.3.1" -androidx-compose-runtime = "1.3.3" -androidx-compose-ui = "1.3.3" -androidx-compose-compiler = "1.4.3" -androidx-compose-material = "1.3.1" +androidx-compose-foundation = "1.4.2" +androidx-compose-runtime = "1.4.2" +androidx-compose-ui = "1.4.2" +androidx-compose-compiler = "1.4.6" +androidx-compose-material = "1.4.2" androidx-compose-material3 = "1.0.1" -androidx-appcompat = "1.6.0-rc01" -androidx-activity = "1.6.1" +androidx-appcompat = "1.7.0-alpha02" +androidx-activity = "1.7.1" androidx-browser = "1.5.0" -androidx-core = "1.9.0" -androidx-core-splashscreen = "1.0.0" +androidx-core = "1.10.0" +androidx-core-splashscreen = "1.0.1" androidx-constraintlayout = "1.0.1" -androidx-lifecycle = "2.6.0-rc01" +androidx-lifecycle = "2.6.1" androidx-hilt-navigation-compose = "1.0.0" androidx-hilt-work = "1.0.0" androidx-navigation = "2.5.3" androidx-paging = "1.0.0-alpha18" androidx-datastore = "1.0.0" androidx-startup = "1.1.1" -androidx-room = "2.5.0" +androidx-room = "2.5.1" androidx-test = "1.5.2" androidx-test-ext = "1.1.5" androidx-test-uiautomator = "2.2.0" androidx-espresso-core = "3.5.1" androidx-benchmark = "1.1.1" -androidx-profile-installer = "1.3.0-beta01" -androidx-work = "2.8.0" -coil = "2.2.2" -okhttp = "4.10.0" +androidx-profile-installer = "1.3.0" +androidx-work = "2.8.1" +coil = "2.3.0" +okhttp = "4.11.0" retrofit = "2.9.0" -retrofit-converter-serialization = "0.8.0" +retrofit-converter-serialization = "1.0.0" chucker = "3.5.2" timber = "5.0.1" javapoet = "1.13.0" junit = "4.13.2" +lint-checks = "1.2.0" +palantir-git = "3.0.0" +ben-manes-versions = "0.46.0" +littlerobots-version-catalog-update = "0.8.0" [libraries] kotlin-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlin-coroutines" } @@ -142,6 +146,7 @@ chucker-library-no-op = { module = "com.github.chuckerteam.chucker:library-no-op timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" } javapoet = { module = "com.squareup:javapoet", version.ref = "javapoet" } junit = { module = "junit:junit", version.ref = "junit" } +lint-checks = { module = "com.slack.lint.compose:compose-lint-checks", version.ref = "lint-checks" } gradle-plugin = { module = "com.android.tools.build:gradle", version.ref = "gradle" } kotlin-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } @@ -208,4 +213,7 @@ hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } androidx-navigation-safeargs = { id = "androidx.navigation.safeargs.kotlin", version.ref = "androidx-navigation" } application = { id = "com.android.application", version.ref = "gradle" } library = { id = "com.android.library", version.ref = "gradle" } -test = { id = "com.android.test", version.ref = "gradle" } \ No newline at end of file +test = { id = "com.android.test", version.ref = "gradle" } +palantir-git = { id = "com.palantir.git-version", version.ref = "palantir-git" } +ben-manes-versions = { id = "com.github.ben-manes.versions", version.ref = "ben-manes-versions" } +littlerobots-version-catalog-update = { id = "nl.littlerobots.version-catalog-update", version.ref = "littlerobots-version-catalog-update" } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0643dcb7d..afcd5d3a4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Sun Apr 04 22:00:34 MSK 2021 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME \ No newline at end of file diff --git a/readme.md b/readme.md index 13b3c60f8..79d29ca27 100644 --- a/readme.md +++ b/readme.md @@ -23,7 +23,7 @@ TMDB_API_KEY=your_own_tmdb_api_key ## Download [](https://play.google.com/store/apps/details?id=org.michaelbel.moviemade) -[](https://github.com/michaelbel/movies/releases/download/1.4.4/movies-v1.4.4.apk) +[](https://github.com/michaelbel/movies/releases/download/1.4.5/Movies-v1.4.5.1178.-release.apk) ## Technologies @@ -32,8 +32,8 @@ TMDB_API_KEY=your_own_tmdb_api_key - [x] [Clean Architecture](https://d.android.com/topic/architecture) - [x] [TMDB API](https://developers.themoviedb.org/3/getting-started) - [x] [KTS Gradle Files](https://d.android.com/studio/build/migrate-to-kts) -- [x] [Kotlin Symbol Processing API](https://d.android.com/studio/build/migrate-to-ksp) 1.8.10-1.0.9 -- [x] [Gradle Plugin](https://d.android.com/studio/releases/gradle-plugin) 7.4.2 +- [x] [Kotlin Symbol Processing API](https://d.android.com/studio/build/migrate-to-ksp) 1.8.20-1.0.11 +- [x] [Gradle Plugin](https://d.android.com/studio/releases/gradle-plugin) 8.0.0 - [x] Gradle Version Catalog - [x] MinSDK 21 - [x] TargetSDK 33 @@ -42,35 +42,35 @@ TMDB_API_KEY=your_own_tmdb_api_key - [x] [Dark Theme](https://d.android.com/develop/ui/views/theming/darktheme) - [x] [Dynamic Colors](https://d.android.com/develop/ui/views/theming/dynamic-colors) - [x] [Themed App Icon](https://d.android.com/develop/ui/views/launch/icon_design_adaptive) -- [x] 100% [Kotlin](https://d.android.com/kotlin) 1.8.10 -- [x] 100% [Jetpack Compose](https://d.android.com/jetpack/compose) 1.4.3 -- [x] [Accompanist](https://github.com/google/accompanist) 0.28.0 +- [x] 100% [Kotlin](https://d.android.com/kotlin) 1.8.20 +- [x] 100% [Jetpack Compose](https://d.android.com/jetpack/compose) 1.4.6 +- [x] [Accompanist](https://github.com/google/accompanist) 0.30.1 - [x] [Compose PreviewParameterProvider](https://d.android.com/jetpack/compose/tooling#previewparameter) - [x] [Downloadable Fonts](https://d.android.com/develop/ui/views/text-and-emoji/downloadable-fonts) - [x] [KotlinX Coroutines](https://github.com/Kotlin/kotlinx.coroutines) 1.6.4 -- [x] [KotlinX Serialization](https://github.com/Kotlin/kotlinx.serialization) 1.4.1 +- [x] [KotlinX Serialization](https://github.com/Kotlin/kotlinx.serialization) 1.5.0 - [x] [Appcompat](https://d.android.com/jetpack/androidx/releases/appcompat) 1.6.0-rc01 - [x] [Dagger Hilt](https://github.com/google/dagger) 2.45 - [x] [ViewModel](https://d.android.com/topic/libraries/architecture/viewmodel) - [x] [Lifecycle](https://d.android.com/topic/libraries/architecture/lifecycle) 2.6.0-alpha03 -- [x] [Room](https://d.android.com/training/data-storage/room) 2.5.0 -- [x] [WorkManager](https://d.android.com/topic/libraries/architecture/workmanager) 2.8.0 +- [x] [Room](https://d.android.com/training/data-storage/room) 2.5.1 +- [x] [WorkManager](https://d.android.com/topic/libraries/architecture/workmanager) 2.8.1 - [x] [DataStore](https://d.android.com/datastore) 1.0.0 - [x] [Startup](https://d.android.com/jetpack/androidx/releases/startup) 1.1.1 - [x] [Navigation](https://d.android.com/guide/navigation) 2.5.3 - [x] [Paging](https://d.android.com/topic/libraries/architecture/paging/v3-overview) (RemoteMediator & PagingSource) 1.0.0-alpha18 - [x] [ConstraintLayout](https://d.android.com/develop/ui/views/layout/constraint-layout) - [x] [Browser](https://d.android.com/jetpack/androidx/releases/browser) 1.5.0 -- [x] [OkHttp](https://github.com/square/okhttp) 4.10.0 +- [x] [OkHttp](https://github.com/square/okhttp) 4.11.0 - [x] [Retrofit](https://github.com/square/retrofit) 2.9.0 - [x] [Retrofit Kotlinx Converter Serialization](https://github.com/JakeWharton/retrofit2-kotlinx-serialization-converter) 0.8.0 - [x] [Chucker](https://github.com/ChuckerTeam/chucker) 3.5.2 -- [x] [Coil](https://github.com/coil-kt/coil) 2.2.2 +- [x] [Coil](https://github.com/coil-kt/coil) 2.3.0 - [x] [Timber](https://github.com/JakeWharton/timber) 5.0.1 -- [x] [Firebase Crashlytics](https://firebase.google.com/products/crashlytics) 18.3.5 +- [x] [Firebase Crashlytics](https://firebase.google.com/products/crashlytics) 18.3.6 - [x] [Firebase App Distribution](https://firebase.google.com/products/app-distribution) 3.2.0 -- [x] [Firebase Remote Config](https://firebase.google.com/products/remote-config) 21.2.1 -- [x] [Google Analytics for Firebase](https://firebase.google.com/products/analytics) 21.2.0 +- [x] [Firebase Remote Config](https://firebase.google.com/products/remote-config) 21.3.0 +- [x] [Google Analytics for Firebase](https://firebase.google.com/products/analytics) 21.2.2 - [x] [In-App Reviews](https://d.android.com/guide/playcore/in-app-review) - [x] [App Shortcuts](https://d.android.com/develop/ui/views/launch/shortcuts) - [x] [Dependabot](https://github.com/dependabot) @@ -78,10 +78,10 @@ TMDB_API_KEY=your_own_tmdb_api_key - [x] [Github Releases](https://github.com/michaelbel/movies/releases) - [x] [Lint](https://d.android.com/studio/write/lint) - [x] [Detekt](https://github.com/detekt/detekt) 1.22.0 -- [x] [Spotless](https://github.com/diffplug/spotless) 6.15.0 +- [x] [Spotless](https://github.com/diffplug/spotless) 6.18.0 - [x] [Distribute App via Telegram Bot](https://github.com/appleboy/telegram-action) - [x] [Non-Transitive R classes](https://d.android.com/studio/build/optimize-your-build#use-non-transitive-r-classes) -- [x] [SplashScreen API](https://d.android.com/develop/ui/views/launch/splash-screen) +- [x] [SplashScreen API](https://d.android.com/develop/ui/views/launch/splash-screen) 1.0.1 - [x] [Per-App Language Preferences](https://d.android.com/guide/topics/resources/app-languages) - [x] [Settings Panel](https://d.android.com/reference/android/provider/Settings.Panel) - [x] [Benchmark](https://d.android.com/topic/performance/benchmarking/benchmarking-overview)