diff --git a/core/common/build.gradle.kts b/core/common/build.gradle.kts index fc6607ecc..186c2af28 100644 --- a/core/common/build.gradle.kts +++ b/core/common/build.gradle.kts @@ -47,7 +47,6 @@ kotlin { } jvmMain.dependencies { api(libs.bundles.kotlinx.coroutines.desktop) - api(libs.bundles.jetbrains.androidx.lifecycle.viewmodel.compose.common) } } diff --git a/core/common/src/androidMain/kotlin/org/michaelbel/movies/common/biometric/di/BiometricKoinModule2.kt b/core/common/src/androidMain/kotlin/org/michaelbel/movies/common/biometric/di/BiometricKoinModule2.kt new file mode 100644 index 000000000..6d6250962 --- /dev/null +++ b/core/common/src/androidMain/kotlin/org/michaelbel/movies/common/biometric/di/BiometricKoinModule2.kt @@ -0,0 +1,11 @@ +package org.michaelbel.movies.common.biometric.di + +import org.koin.core.module.dsl.bind +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module +import org.michaelbel.movies.common.biometric.BiometricController2 +import org.michaelbel.movies.common.biometric.impl.BiometricControllerImpl2 + +actual val biometricKoinModule2 = module { + singleOf(::BiometricControllerImpl2) { bind() } +} \ No newline at end of file diff --git a/core/common/src/androidMain/kotlin/org/michaelbel/movies/common/biometric/impl/BiometricControllerImpl2.kt b/core/common/src/androidMain/kotlin/org/michaelbel/movies/common/biometric/impl/BiometricControllerImpl2.kt new file mode 100644 index 000000000..12725ae27 --- /dev/null +++ b/core/common/src/androidMain/kotlin/org/michaelbel/movies/common/biometric/impl/BiometricControllerImpl2.kt @@ -0,0 +1,19 @@ +package org.michaelbel.movies.common.biometric.impl + +import android.content.Context +import androidx.biometric.BiometricManager +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import org.michaelbel.movies.common.biometric.BiometricController2 + +internal class BiometricControllerImpl2( + private val context: Context +): BiometricController2 { + + override val isBiometricAvailable: Flow + get() { + val biometricManager = BiometricManager.from(context) + val authenticators = biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG or BiometricManager.Authenticators.DEVICE_CREDENTIAL) + return flowOf(authenticators == BiometricManager.BIOMETRIC_SUCCESS) + } +} \ No newline at end of file diff --git a/core/common/src/androidMain/kotlin/org/michaelbel/movies/common/ktx/LogKtx.kt b/core/common/src/androidMain/kotlin/org/michaelbel/movies/common/ktx/LogKtx.kt deleted file mode 100644 index 0fb3f8753..000000000 --- a/core/common/src/androidMain/kotlin/org/michaelbel/movies/common/ktx/LogKtx.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.michaelbel.movies.common.ktx - -import org.michaelbel.movies.common.BuildConfig - -fun printlnDebug(message: String) { - if (BuildConfig.DEBUG) { - println(message) - } -} \ No newline at end of file diff --git a/core/common/src/androidMain/kotlin/org/michaelbel/movies/common/viewmodel/BaseViewModel.kt b/core/common/src/androidMain/kotlin/org/michaelbel/movies/common/viewmodel/BaseViewModel.kt deleted file mode 100644 index a4590f383..000000000 --- a/core/common/src/androidMain/kotlin/org/michaelbel/movies/common/viewmodel/BaseViewModel.kt +++ /dev/null @@ -1,33 +0,0 @@ -package org.michaelbel.movies.common.viewmodel - -import androidx.annotation.CallSuper -import androidx.lifecycle.ViewModel -import kotlinx.coroutines.CoroutineExceptionHandler -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.cancelChildren -import org.michaelbel.movies.common.log.log -import kotlin.coroutines.CoroutineContext - -open class BaseViewModel: ViewModel(), CoroutineScope { - - private val scopeJob: Job = SupervisorJob() - - private val errorHandler = CoroutineExceptionHandler { _, throwable -> - handleError(throwable) - } - - override val coroutineContext: CoroutineContext = scopeJob + Dispatchers.Main + errorHandler - - override fun onCleared() { - coroutineContext.cancelChildren() - super.onCleared() - } - - @CallSuper - protected open fun handleError(throwable: Throwable) { - log(throwable) - } -} \ No newline at end of file diff --git a/core/common/src/commonMain/kotlin/org/michaelbel/movies/common/biometric/BiometricController2.kt b/core/common/src/commonMain/kotlin/org/michaelbel/movies/common/biometric/BiometricController2.kt new file mode 100644 index 000000000..7e9e2b864 --- /dev/null +++ b/core/common/src/commonMain/kotlin/org/michaelbel/movies/common/biometric/BiometricController2.kt @@ -0,0 +1,8 @@ +package org.michaelbel.movies.common.biometric + +import kotlinx.coroutines.flow.Flow + +interface BiometricController2 { + + val isBiometricAvailable: Flow +} \ No newline at end of file diff --git a/core/common/src/commonMain/kotlin/org/michaelbel/movies/common/biometric/di/BiometricKoinModule2.kt b/core/common/src/commonMain/kotlin/org/michaelbel/movies/common/biometric/di/BiometricKoinModule2.kt new file mode 100644 index 000000000..5ab634639 --- /dev/null +++ b/core/common/src/commonMain/kotlin/org/michaelbel/movies/common/biometric/di/BiometricKoinModule2.kt @@ -0,0 +1,5 @@ +package org.michaelbel.movies.common.biometric.di + +import org.koin.core.module.Module + +expect val biometricKoinModule2: Module \ No newline at end of file diff --git a/core/common/src/jvmMain/kotlin/org/michaelbel/movies/common/viewmodel/BaseViewModel.kt b/core/common/src/commonMain/kotlin/org/michaelbel/movies/common/viewmodel/BaseViewModel.kt similarity index 62% rename from core/common/src/jvmMain/kotlin/org/michaelbel/movies/common/viewmodel/BaseViewModel.kt rename to core/common/src/commonMain/kotlin/org/michaelbel/movies/common/viewmodel/BaseViewModel.kt index 4a7260333..668e34a18 100644 --- a/core/common/src/jvmMain/kotlin/org/michaelbel/movies/common/viewmodel/BaseViewModel.kt +++ b/core/common/src/commonMain/kotlin/org/michaelbel/movies/common/viewmodel/BaseViewModel.kt @@ -2,6 +2,8 @@ package org.michaelbel.movies.common.viewmodel import androidx.annotation.CallSuper import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob import org.michaelbel.movies.common.dispatchers.uiDispatcher @@ -11,6 +13,13 @@ open class BaseViewModel: ViewModel( viewModelScope = CoroutineScope(uiDispatcher + SupervisorJob()) ) { + private val errorHandler = CoroutineExceptionHandler { _, throwable -> + handleError(throwable) + } + + val scope: CoroutineScope + get() = CoroutineScope(viewModelScope.coroutineContext + errorHandler) + @CallSuper protected open fun handleError(throwable: Throwable) { log(throwable) diff --git a/core/common/src/iosMain/kotlin/org/michaelbel/movies/common/biometric/di/BiometricKoinModule2.kt b/core/common/src/iosMain/kotlin/org/michaelbel/movies/common/biometric/di/BiometricKoinModule2.kt new file mode 100644 index 000000000..6d6250962 --- /dev/null +++ b/core/common/src/iosMain/kotlin/org/michaelbel/movies/common/biometric/di/BiometricKoinModule2.kt @@ -0,0 +1,11 @@ +package org.michaelbel.movies.common.biometric.di + +import org.koin.core.module.dsl.bind +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module +import org.michaelbel.movies.common.biometric.BiometricController2 +import org.michaelbel.movies.common.biometric.impl.BiometricControllerImpl2 + +actual val biometricKoinModule2 = module { + singleOf(::BiometricControllerImpl2) { bind() } +} \ No newline at end of file diff --git a/core/common/src/iosMain/kotlin/org/michaelbel/movies/common/biometric/impl/BiometricControllerImpl2.kt b/core/common/src/iosMain/kotlin/org/michaelbel/movies/common/biometric/impl/BiometricControllerImpl2.kt new file mode 100644 index 000000000..f7c05c77a --- /dev/null +++ b/core/common/src/iosMain/kotlin/org/michaelbel/movies/common/biometric/impl/BiometricControllerImpl2.kt @@ -0,0 +1,11 @@ +package org.michaelbel.movies.common.biometric.impl + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import org.michaelbel.movies.common.biometric.BiometricController2 + +internal class BiometricControllerImpl2: BiometricController2 { + + override val isBiometricAvailable: Flow + get() = flowOf(false) +} \ No newline at end of file diff --git a/core/common/src/iosMain/kotlin/org/michaelbel/movies/common/viewmodel/BaseViewModel.kt b/core/common/src/iosMain/kotlin/org/michaelbel/movies/common/viewmodel/BaseViewModel.kt deleted file mode 100644 index 4a7260333..000000000 --- a/core/common/src/iosMain/kotlin/org/michaelbel/movies/common/viewmodel/BaseViewModel.kt +++ /dev/null @@ -1,18 +0,0 @@ -package org.michaelbel.movies.common.viewmodel - -import androidx.annotation.CallSuper -import androidx.lifecycle.ViewModel -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.SupervisorJob -import org.michaelbel.movies.common.dispatchers.uiDispatcher -import org.michaelbel.movies.common.log.log - -open class BaseViewModel: ViewModel( - viewModelScope = CoroutineScope(uiDispatcher + SupervisorJob()) -) { - - @CallSuper - protected open fun handleError(throwable: Throwable) { - log(throwable) - } -} \ No newline at end of file diff --git a/core/common/src/jsMain/kotlin/org/michaelbel/movies/common/biometric/di/BiometricKoinModule2.kt b/core/common/src/jsMain/kotlin/org/michaelbel/movies/common/biometric/di/BiometricKoinModule2.kt new file mode 100644 index 000000000..6d6250962 --- /dev/null +++ b/core/common/src/jsMain/kotlin/org/michaelbel/movies/common/biometric/di/BiometricKoinModule2.kt @@ -0,0 +1,11 @@ +package org.michaelbel.movies.common.biometric.di + +import org.koin.core.module.dsl.bind +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module +import org.michaelbel.movies.common.biometric.BiometricController2 +import org.michaelbel.movies.common.biometric.impl.BiometricControllerImpl2 + +actual val biometricKoinModule2 = module { + singleOf(::BiometricControllerImpl2) { bind() } +} \ No newline at end of file diff --git a/core/common/src/jsMain/kotlin/org/michaelbel/movies/common/biometric/impl/BiometricControllerImpl2.kt b/core/common/src/jsMain/kotlin/org/michaelbel/movies/common/biometric/impl/BiometricControllerImpl2.kt new file mode 100644 index 000000000..f7c05c77a --- /dev/null +++ b/core/common/src/jsMain/kotlin/org/michaelbel/movies/common/biometric/impl/BiometricControllerImpl2.kt @@ -0,0 +1,11 @@ +package org.michaelbel.movies.common.biometric.impl + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import org.michaelbel.movies.common.biometric.BiometricController2 + +internal class BiometricControllerImpl2: BiometricController2 { + + override val isBiometricAvailable: Flow + get() = flowOf(false) +} \ No newline at end of file diff --git a/core/common/src/jsMain/kotlin/org/michaelbel/movies/common/viewmodel/BaseViewModel.kt b/core/common/src/jsMain/kotlin/org/michaelbel/movies/common/viewmodel/BaseViewModel.kt deleted file mode 100644 index 4a7260333..000000000 --- a/core/common/src/jsMain/kotlin/org/michaelbel/movies/common/viewmodel/BaseViewModel.kt +++ /dev/null @@ -1,18 +0,0 @@ -package org.michaelbel.movies.common.viewmodel - -import androidx.annotation.CallSuper -import androidx.lifecycle.ViewModel -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.SupervisorJob -import org.michaelbel.movies.common.dispatchers.uiDispatcher -import org.michaelbel.movies.common.log.log - -open class BaseViewModel: ViewModel( - viewModelScope = CoroutineScope(uiDispatcher + SupervisorJob()) -) { - - @CallSuper - protected open fun handleError(throwable: Throwable) { - log(throwable) - } -} \ No newline at end of file diff --git a/core/common/src/jvmMain/kotlin/org/michaelbel/movies/common/biometric/di/BiometricKoinModule2.kt b/core/common/src/jvmMain/kotlin/org/michaelbel/movies/common/biometric/di/BiometricKoinModule2.kt new file mode 100644 index 000000000..6d6250962 --- /dev/null +++ b/core/common/src/jvmMain/kotlin/org/michaelbel/movies/common/biometric/di/BiometricKoinModule2.kt @@ -0,0 +1,11 @@ +package org.michaelbel.movies.common.biometric.di + +import org.koin.core.module.dsl.bind +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module +import org.michaelbel.movies.common.biometric.BiometricController2 +import org.michaelbel.movies.common.biometric.impl.BiometricControllerImpl2 + +actual val biometricKoinModule2 = module { + singleOf(::BiometricControllerImpl2) { bind() } +} \ No newline at end of file diff --git a/core/common/src/jvmMain/kotlin/org/michaelbel/movies/common/biometric/impl/BiometricControllerImpl2.kt b/core/common/src/jvmMain/kotlin/org/michaelbel/movies/common/biometric/impl/BiometricControllerImpl2.kt new file mode 100644 index 000000000..f7c05c77a --- /dev/null +++ b/core/common/src/jvmMain/kotlin/org/michaelbel/movies/common/biometric/impl/BiometricControllerImpl2.kt @@ -0,0 +1,11 @@ +package org.michaelbel.movies.common.biometric.impl + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import org.michaelbel.movies.common.biometric.BiometricController2 + +internal class BiometricControllerImpl2: BiometricController2 { + + override val isBiometricAvailable: Flow + get() = flowOf(false) +} \ No newline at end of file diff --git a/core/common/src/wasmJsMain/kotlin/org/michaelbel/movies/common/biometric/di/BiometricKoinModule2.kt b/core/common/src/wasmJsMain/kotlin/org/michaelbel/movies/common/biometric/di/BiometricKoinModule2.kt new file mode 100644 index 000000000..6d6250962 --- /dev/null +++ b/core/common/src/wasmJsMain/kotlin/org/michaelbel/movies/common/biometric/di/BiometricKoinModule2.kt @@ -0,0 +1,11 @@ +package org.michaelbel.movies.common.biometric.di + +import org.koin.core.module.dsl.bind +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module +import org.michaelbel.movies.common.biometric.BiometricController2 +import org.michaelbel.movies.common.biometric.impl.BiometricControllerImpl2 + +actual val biometricKoinModule2 = module { + singleOf(::BiometricControllerImpl2) { bind() } +} \ No newline at end of file diff --git a/core/common/src/wasmJsMain/kotlin/org/michaelbel/movies/common/biometric/impl/BiometricControllerImpl2.kt b/core/common/src/wasmJsMain/kotlin/org/michaelbel/movies/common/biometric/impl/BiometricControllerImpl2.kt new file mode 100644 index 000000000..f7c05c77a --- /dev/null +++ b/core/common/src/wasmJsMain/kotlin/org/michaelbel/movies/common/biometric/impl/BiometricControllerImpl2.kt @@ -0,0 +1,11 @@ +package org.michaelbel.movies.common.biometric.impl + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import org.michaelbel.movies.common.biometric.BiometricController2 + +internal class BiometricControllerImpl2: BiometricController2 { + + override val isBiometricAvailable: Flow + get() = flowOf(false) +} \ No newline at end of file diff --git a/core/common/src/wasmJsMain/kotlin/org/michaelbel/movies/common/viewmodel/BaseViewModel.kt b/core/common/src/wasmJsMain/kotlin/org/michaelbel/movies/common/viewmodel/BaseViewModel.kt deleted file mode 100644 index 4a7260333..000000000 --- a/core/common/src/wasmJsMain/kotlin/org/michaelbel/movies/common/viewmodel/BaseViewModel.kt +++ /dev/null @@ -1,18 +0,0 @@ -package org.michaelbel.movies.common.viewmodel - -import androidx.annotation.CallSuper -import androidx.lifecycle.ViewModel -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.SupervisorJob -import org.michaelbel.movies.common.dispatchers.uiDispatcher -import org.michaelbel.movies.common.log.log - -open class BaseViewModel: ViewModel( - viewModelScope = CoroutineScope(uiDispatcher + SupervisorJob()) -) { - - @CallSuper - protected open fun handleError(throwable: Throwable) { - log(throwable) - } -} \ No newline at end of file diff --git a/core/notifications/build.gradle.kts b/core/notifications/build.gradle.kts index 2bcafdc1a..f601006f3 100644 --- a/core/notifications/build.gradle.kts +++ b/core/notifications/build.gradle.kts @@ -4,7 +4,9 @@ import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi plugins { alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.kotlin.compose) alias(libs.plugins.android.library) + alias(libs.plugins.compose) } kotlin { @@ -35,4 +37,8 @@ android { minSdk = libs.versions.min.sdk.get().toInt() compileSdk = libs.versions.compile.sdk.get().toInt() } + + buildFeatures { + compose = true + } } \ No newline at end of file diff --git a/core/notifications/src/androidMain/kotlin/org/michaelbel/movies/notifications/ktx/NotificationKtx.kt b/core/notifications/src/androidMain/kotlin/org/michaelbel/movies/notifications/ktx/NotificationKtx.kt new file mode 100644 index 000000000..4e78dafec --- /dev/null +++ b/core/notifications/src/androidMain/kotlin/org/michaelbel/movies/notifications/ktx/NotificationKtx.kt @@ -0,0 +1,44 @@ +package org.michaelbel.movies.notifications.ktx + +import android.Manifest +import android.app.Activity +import android.os.Build +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext +import org.michaelbel.movies.ui.ktx.appNotificationSettingsIntent + +@Composable +fun rememberPostNotificationsPermissionHandler( + areNotificationsEnabled: Boolean, + onPermissionGranted: () -> Unit, + onPermissionDenied: () -> Unit +): () -> Unit { + val context = LocalContext.current + val postNotificationsPermissionLauncher = rememberLauncherForActivityResult( + ActivityResultContracts.RequestPermission() + ) { granted -> + when { + granted -> onPermissionGranted() + else -> { + if (Build.VERSION.SDK_INT >= 33) { + val shouldRequest = (context as Activity).shouldShowRequestPermissionRationale( + Manifest.permission.POST_NOTIFICATIONS) + if (!shouldRequest) { + onPermissionDenied() + } + } + } + } + } + val resultContract = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {} + return { + if (areNotificationsEnabled) { + val intent = context.appNotificationSettingsIntent + resultContract.launch(intent) + } else if (Build.VERSION.SDK_INT >= 33) { + postNotificationsPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) + } + } +} \ No newline at end of file diff --git a/core/platform-services/foss/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/review/ReviewServiceImpl.kt b/core/platform-services/foss/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/review/ReviewServiceImpl.kt index af3490306..ba5a9e5a5 100644 --- a/core/platform-services/foss/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/review/ReviewServiceImpl.kt +++ b/core/platform-services/foss/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/review/ReviewServiceImpl.kt @@ -2,10 +2,6 @@ package org.michaelbel.movies.platform.impl.review -import android.app.Activity import org.michaelbel.movies.platform.review.ReviewService -actual class ReviewServiceImpl: ReviewService { - - override fun requestReview(activity: Activity) {} -} \ No newline at end of file +actual class ReviewServiceImpl: ReviewService \ No newline at end of file diff --git a/core/platform-services/foss/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/update/UpdateServiceImpl.kt b/core/platform-services/foss/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/update/UpdateServiceImpl.kt index 8b7d259d9..abb226a7e 100644 --- a/core/platform-services/foss/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/update/UpdateServiceImpl.kt +++ b/core/platform-services/foss/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/update/UpdateServiceImpl.kt @@ -2,13 +2,6 @@ package org.michaelbel.movies.platform.impl.update -import android.app.Activity -import org.michaelbel.movies.platform.update.UpdateListener import org.michaelbel.movies.platform.update.UpdateService -actual class UpdateServiceImpl: UpdateService { - - override fun setUpdateAvailableListener(listener: UpdateListener) {} - - override fun startUpdate(activity: Activity) {} -} \ No newline at end of file +actual class UpdateServiceImpl: UpdateService \ No newline at end of file diff --git a/core/platform-services/gms/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/review/ReviewServiceImpl.kt b/core/platform-services/gms/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/review/ReviewServiceImpl.kt index 7dd13a636..ba1b28c63 100644 --- a/core/platform-services/gms/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/review/ReviewServiceImpl.kt +++ b/core/platform-services/gms/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/review/ReviewServiceImpl.kt @@ -8,10 +8,10 @@ class ReviewServiceImpl( private val reviewManager: ReviewManager ): ReviewService { - override fun requestReview(activity: Activity) { + override fun requestReview(activity: Any) { reviewManager.requestReviewFlow().addOnCompleteListener { task -> if (task.isSuccessful) { - reviewManager.launchReviewFlow(activity, task.result) + reviewManager.launchReviewFlow(activity as Activity, task.result) } } } diff --git a/core/platform-services/gms/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/update/UpdateServiceImpl.kt b/core/platform-services/gms/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/update/UpdateServiceImpl.kt index f8a9949e0..a8ecaa8b1 100644 --- a/core/platform-services/gms/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/update/UpdateServiceImpl.kt +++ b/core/platform-services/gms/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/update/UpdateServiceImpl.kt @@ -14,7 +14,7 @@ class UpdateServiceImpl( } } - override fun startUpdate(activity: Activity) { - inAppUpdate.startUpdateFlow(activity) + override fun startUpdate(activity: Any) { + inAppUpdate.startUpdateFlow(activity as Activity) } } \ No newline at end of file diff --git a/core/platform-services/hms/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/review/ReviewServiceImpl.kt b/core/platform-services/hms/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/review/ReviewServiceImpl.kt index 7bfaa913f..9f354a61c 100644 --- a/core/platform-services/hms/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/review/ReviewServiceImpl.kt +++ b/core/platform-services/hms/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/review/ReviewServiceImpl.kt @@ -1,9 +1,5 @@ package org.michaelbel.movies.platform.impl.review -import android.app.Activity import org.michaelbel.movies.platform.review.ReviewService -class ReviewServiceImpl: ReviewService { - - override fun requestReview(activity: Activity) {} -} \ No newline at end of file +class ReviewServiceImpl: ReviewService \ No newline at end of file diff --git a/core/platform-services/hms/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/update/UpdateServiceImpl.kt b/core/platform-services/hms/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/update/UpdateServiceImpl.kt index a66b96504..655a6e5e1 100644 --- a/core/platform-services/hms/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/update/UpdateServiceImpl.kt +++ b/core/platform-services/hms/src/androidMain/kotlin/org/michaelbel/movies/platform/impl/update/UpdateServiceImpl.kt @@ -1,12 +1,5 @@ package org.michaelbel.movies.platform.impl.update -import android.app.Activity -import org.michaelbel.movies.platform.update.UpdateListener import org.michaelbel.movies.platform.update.UpdateService -class UpdateServiceImpl: UpdateService { - - override fun setUpdateAvailableListener(listener: UpdateListener) {} - - override fun startUpdate(activity: Activity) {} -} \ No newline at end of file +class UpdateServiceImpl: UpdateService \ No newline at end of file diff --git a/core/platform-services/interactor/src/androidMain/kotlin/org/michaelbel/movies/platform/review/ReviewService.kt b/core/platform-services/interactor/src/androidMain/kotlin/org/michaelbel/movies/platform/review/ReviewService.kt deleted file mode 100644 index 4e0920d7c..000000000 --- a/core/platform-services/interactor/src/androidMain/kotlin/org/michaelbel/movies/platform/review/ReviewService.kt +++ /dev/null @@ -1,10 +0,0 @@ -@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") - -package org.michaelbel.movies.platform.review - -import android.app.Activity - -actual interface ReviewService { - - fun requestReview(activity: Activity) -} \ No newline at end of file diff --git a/core/platform-services/interactor/src/androidMain/kotlin/org/michaelbel/movies/platform/update/UpdateService.kt b/core/platform-services/interactor/src/androidMain/kotlin/org/michaelbel/movies/platform/update/UpdateService.kt deleted file mode 100644 index 70b2ae00b..000000000 --- a/core/platform-services/interactor/src/androidMain/kotlin/org/michaelbel/movies/platform/update/UpdateService.kt +++ /dev/null @@ -1,12 +0,0 @@ -@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") - -package org.michaelbel.movies.platform.update - -import android.app.Activity - -actual interface UpdateService { - - fun setUpdateAvailableListener(listener: UpdateListener) - - fun startUpdate(activity: Activity) -} \ No newline at end of file diff --git a/core/platform-services/interactor/src/commonMain/kotlin/org/michaelbel/movies/platform/review/ReviewService.kt b/core/platform-services/interactor/src/commonMain/kotlin/org/michaelbel/movies/platform/review/ReviewService.kt index de6179b31..596d54284 100644 --- a/core/platform-services/interactor/src/commonMain/kotlin/org/michaelbel/movies/platform/review/ReviewService.kt +++ b/core/platform-services/interactor/src/commonMain/kotlin/org/michaelbel/movies/platform/review/ReviewService.kt @@ -1,5 +1,5 @@ -@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") - package org.michaelbel.movies.platform.review -expect interface ReviewService \ No newline at end of file +interface ReviewService { + fun requestReview(activity: Any) {} +} \ No newline at end of file diff --git a/core/platform-services/interactor/src/commonMain/kotlin/org/michaelbel/movies/platform/update/UpdateService.kt b/core/platform-services/interactor/src/commonMain/kotlin/org/michaelbel/movies/platform/update/UpdateService.kt index 6821a36e1..f453ed2a1 100644 --- a/core/platform-services/interactor/src/commonMain/kotlin/org/michaelbel/movies/platform/update/UpdateService.kt +++ b/core/platform-services/interactor/src/commonMain/kotlin/org/michaelbel/movies/platform/update/UpdateService.kt @@ -1,5 +1,8 @@ -@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") - package org.michaelbel.movies.platform.update -expect interface UpdateService \ No newline at end of file +interface UpdateService { + + fun setUpdateAvailableListener(listener: UpdateListener) {} + + fun startUpdate(activity: Any) {} +} \ No newline at end of file diff --git a/core/platform-services/interactor/src/iosMain/kotlin/org/michaelbel/movies/platform/review/ReviewService.kt b/core/platform-services/interactor/src/iosMain/kotlin/org/michaelbel/movies/platform/review/ReviewService.kt deleted file mode 100644 index 317de3959..000000000 --- a/core/platform-services/interactor/src/iosMain/kotlin/org/michaelbel/movies/platform/review/ReviewService.kt +++ /dev/null @@ -1,5 +0,0 @@ -@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") - -package org.michaelbel.movies.platform.review - -actual interface ReviewService \ No newline at end of file diff --git a/core/platform-services/interactor/src/iosMain/kotlin/org/michaelbel/movies/platform/update/UpdateService.kt b/core/platform-services/interactor/src/iosMain/kotlin/org/michaelbel/movies/platform/update/UpdateService.kt deleted file mode 100644 index c80d02be2..000000000 --- a/core/platform-services/interactor/src/iosMain/kotlin/org/michaelbel/movies/platform/update/UpdateService.kt +++ /dev/null @@ -1,5 +0,0 @@ -@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") - -package org.michaelbel.movies.platform.update - -actual interface UpdateService \ No newline at end of file diff --git a/core/platform-services/interactor/src/jsMain/kotlin/org/michaelbel/movies/platform/review/ReviewService.kt b/core/platform-services/interactor/src/jsMain/kotlin/org/michaelbel/movies/platform/review/ReviewService.kt deleted file mode 100644 index 317de3959..000000000 --- a/core/platform-services/interactor/src/jsMain/kotlin/org/michaelbel/movies/platform/review/ReviewService.kt +++ /dev/null @@ -1,5 +0,0 @@ -@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") - -package org.michaelbel.movies.platform.review - -actual interface ReviewService \ No newline at end of file diff --git a/core/platform-services/interactor/src/jsMain/kotlin/org/michaelbel/movies/platform/update/UpdateService.kt b/core/platform-services/interactor/src/jsMain/kotlin/org/michaelbel/movies/platform/update/UpdateService.kt deleted file mode 100644 index c80d02be2..000000000 --- a/core/platform-services/interactor/src/jsMain/kotlin/org/michaelbel/movies/platform/update/UpdateService.kt +++ /dev/null @@ -1,5 +0,0 @@ -@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") - -package org.michaelbel.movies.platform.update - -actual interface UpdateService \ No newline at end of file diff --git a/core/platform-services/interactor/src/jvmMain/kotlin/org/michaelbel/movies/platform/review/ReviewService.kt b/core/platform-services/interactor/src/jvmMain/kotlin/org/michaelbel/movies/platform/review/ReviewService.kt deleted file mode 100644 index 317de3959..000000000 --- a/core/platform-services/interactor/src/jvmMain/kotlin/org/michaelbel/movies/platform/review/ReviewService.kt +++ /dev/null @@ -1,5 +0,0 @@ -@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") - -package org.michaelbel.movies.platform.review - -actual interface ReviewService \ No newline at end of file diff --git a/core/platform-services/interactor/src/jvmMain/kotlin/org/michaelbel/movies/platform/update/UpdateService.kt b/core/platform-services/interactor/src/jvmMain/kotlin/org/michaelbel/movies/platform/update/UpdateService.kt deleted file mode 100644 index c80d02be2..000000000 --- a/core/platform-services/interactor/src/jvmMain/kotlin/org/michaelbel/movies/platform/update/UpdateService.kt +++ /dev/null @@ -1,5 +0,0 @@ -@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") - -package org.michaelbel.movies.platform.update - -actual interface UpdateService \ No newline at end of file diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index 4f1c1b757..05a1b12cd 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -46,7 +46,6 @@ kotlin { api(compose.desktop.common) api(compose.desktop.currentOs) api(libs.bundles.compose.desktop) - api(libs.bundles.jetbrains.androidx.lifecycle.viewmodel.compose.common) } } diff --git a/core/ui/src/androidMain/kotlin/org/michaelbel/movies/ui/accessibility/MoviesContentDescription.kt b/core/ui/src/androidMain/kotlin/org/michaelbel/movies/ui/accessibility/MoviesContentDescription.kt index aa89ec3b9..2204b3071 100644 --- a/core/ui/src/androidMain/kotlin/org/michaelbel/movies/ui/accessibility/MoviesContentDescription.kt +++ b/core/ui/src/androidMain/kotlin/org/michaelbel/movies/ui/accessibility/MoviesContentDescription.kt @@ -4,10 +4,7 @@ import androidx.annotation.StringRes import org.michaelbel.movies.ui.R object MoviesContentDescription { - @StringRes val AccountIcon = R.string.content_description_account_icon - @StringRes val AppIcon = R.string.content_description_app_icon @StringRes val HistoryIcon = R.string.content_description_download_icon @StringRes val MovieDetailsImage = R.string.content_description_movie_details_image - @StringRes val VoiceIcon = R.string.content_description_voice_icon val None: String? = null } \ No newline at end of file diff --git a/core/ui/src/androidMain/kotlin/org/michaelbel/movies/ui/compose/iconbutton/VoiceIcon.kt b/core/ui/src/androidMain/kotlin/org/michaelbel/movies/ui/compose/iconbutton/VoiceIcon.kt deleted file mode 100644 index 75c90d8e5..000000000 --- a/core/ui/src/androidMain/kotlin/org/michaelbel/movies/ui/compose/iconbutton/VoiceIcon.kt +++ /dev/null @@ -1,71 +0,0 @@ -package org.michaelbel.movies.ui.compose.iconbutton - -import android.content.Intent -import android.speech.RecognizerIntent -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -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.res.stringResource -import org.jetbrains.compose.ui.tooling.preview.Preview -import org.michaelbel.movies.ui.accessibility.MoviesContentDescription -import org.michaelbel.movies.ui.icons.MoviesIcons -import org.michaelbel.movies.ui.theme.MoviesTheme - -@Composable -fun VoiceIcon( - onInputText: (String) -> Unit, - modifier: Modifier = Modifier -) { - val speechRecognizeContract = rememberLauncherForActivityResult( - ActivityResultContracts.StartActivityForResult() - ) { activityResult -> - val data = activityResult.data - val spokenText = data?.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)?.let { results -> - results[0] - } - if (!spokenText.isNullOrEmpty()) { - onInputText(spokenText) - } - } - - val onStartSpeechRecognize: () -> Unit = { - Intent( - RecognizerIntent.ACTION_RECOGNIZE_SPEECH - ).apply { - putExtra( - RecognizerIntent.EXTRA_LANGUAGE_MODEL, - RecognizerIntent.LANGUAGE_MODEL_FREE_FORM - ) - }.also { intent -> - speechRecognizeContract.launch(intent) - } - } - - IconButton( - onClick = onStartSpeechRecognize, - modifier = modifier - ) { - Image( - imageVector = MoviesIcons.KeyboardVoice, - contentDescription = stringResource(MoviesContentDescription.VoiceIcon), - colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onPrimaryContainer) - ) - } -} - -@Preview -@Composable -private fun VoiceIconPreview() { - MoviesTheme { - VoiceIcon( - onInputText = {}, - modifier = Modifier.background(MaterialTheme.colorScheme.primaryContainer) - ) - } -} \ No newline at end of file diff --git a/core/ui/src/androidMain/kotlin/org/michaelbel/movies/ui/icons/MoviesAndroidIcons.kt b/core/ui/src/androidMain/kotlin/org/michaelbel/movies/ui/icons/MoviesAndroidIcons.kt index 7bb995e6f..3fd3d8e39 100644 --- a/core/ui/src/androidMain/kotlin/org/michaelbel/movies/ui/icons/MoviesAndroidIcons.kt +++ b/core/ui/src/androidMain/kotlin/org/michaelbel/movies/ui/icons/MoviesAndroidIcons.kt @@ -1,12 +1,8 @@ package org.michaelbel.movies.ui.icons import androidx.annotation.DrawableRes -import androidx.compose.ui.graphics.vector.ImageVector import org.michaelbel.movies.ui.R -/** - * Movies icons. Material icons are [ImageVector]s, custom icons are drawable resource IDs. - */ object MoviesAndroidIcons { @DrawableRes val MovieFilter24 = R.drawable.ic_movie_filter_24 @DrawableRes val FileDownload24 = R.drawable.ic_file_download_24 diff --git a/core/ui/src/androidMain/kotlin/org/michaelbel/movies/ui/ktx/AndroidConfigurationKtx.kt b/core/ui/src/androidMain/kotlin/org/michaelbel/movies/ui/ktx/AndroidConfigurationKtx.kt index 50664e36c..8eeb92ece 100644 --- a/core/ui/src/androidMain/kotlin/org/michaelbel/movies/ui/ktx/AndroidConfigurationKtx.kt +++ b/core/ui/src/androidMain/kotlin/org/michaelbel/movies/ui/ktx/AndroidConfigurationKtx.kt @@ -1,14 +1,12 @@ package org.michaelbel.movies.ui.ktx import android.content.Context -import android.content.res.Configuration import android.os.Build import android.util.DisplayMetrics import android.view.WindowManager import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.displayCutout import androidx.compose.runtime.Composable -import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp @@ -31,7 +29,6 @@ val screenHeight: Dp val displayCutoutWindowInsets: WindowInsets @Composable get() = if (isPortrait) WindowInsets(0, 0, 0, 0) else WindowInsets.displayCutout -@Suppress("Deprecation") private inline val Context.deviceWidth: Int get() { val windowManager = ContextCompat.getSystemService(this, WindowManager::class.java) as WindowManager @@ -48,7 +45,6 @@ private inline val Context.deviceWidth: Int } } -@Suppress("Deprecation") private inline val Context.deviceHeight: Int get() { val windowManager = ContextCompat.getSystemService(this, WindowManager::class.java) as WindowManager diff --git a/core/ui/src/androidMain/kotlin/org/michaelbel/movies/ui/ktx/IntentKtx.kt b/core/ui/src/androidMain/kotlin/org/michaelbel/movies/ui/ktx/IntentKtx.kt new file mode 100644 index 000000000..3cb717d20 --- /dev/null +++ b/core/ui/src/androidMain/kotlin/org/michaelbel/movies/ui/ktx/IntentKtx.kt @@ -0,0 +1,44 @@ +package org.michaelbel.movies.ui.ktx + +import android.content.Intent +import android.os.Build +import android.provider.Settings +import android.speech.RecognizerIntent +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.runtime.Composable + +@Composable +fun rememberSpeechRecognitionLauncher(onInputText: (String) -> Unit): () -> Unit { + val speechRecognizeContract = rememberLauncherForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { activityResult -> + val data = activityResult.data + val spokenText = data?.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)?.let { results -> + results[0] + } + if (!spokenText.isNullOrEmpty()) { + onInputText(spokenText) + } + } + + return { + val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply { + putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM) + } + speechRecognizeContract.launch(intent) + } +} + +@Composable +fun rememberConnectivityClickHandler(): () -> Unit { + if (Build.VERSION.SDK_INT >= 29) { + val launcher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {} + return { + val intent = Intent(Settings.Panel.ACTION_INTERNET_CONNECTIVITY) + launcher.launch(intent) + } + } else { + return {} + } +} \ No newline at end of file diff --git a/core/ui/src/androidMain/kotlin/org/michaelbel/movies/ui/ktx/SettingsKtx.kt b/core/ui/src/androidMain/kotlin/org/michaelbel/movies/ui/ktx/SettingsKtx.kt index bf70cc451..cc361ebc9 100644 --- a/core/ui/src/androidMain/kotlin/org/michaelbel/movies/ui/ktx/SettingsKtx.kt +++ b/core/ui/src/androidMain/kotlin/org/michaelbel/movies/ui/ktx/SettingsKtx.kt @@ -22,6 +22,7 @@ val Context.appNotificationSettingsIntent: Intent intent.putExtra("app_uid", applicationInfo.uid) } } + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) return intent } diff --git a/core/ui/src/androidMain/res/values-ru/content_description.xml b/core/ui/src/androidMain/res/values-ru/content_description.xml index 99dcfc961..15aa19130 100644 --- a/core/ui/src/androidMain/res/values-ru/content_description.xml +++ b/core/ui/src/androidMain/res/values-ru/content_description.xml @@ -1,10 +1,8 @@ - Иконка Аккаунта Изображение Аватара Аккаунта Иконка 18+ Иконка Отображения - Иконка Приложения Иконка Назад Иконка Закрытия Иконка Загрузки @@ -15,6 +13,5 @@ Иконка Поделиться Иконка Поиска Иконка Настроек - Иконка Голосового Поиска Иконка Местоположения Пользователя \ No newline at end of file diff --git a/core/ui/src/androidMain/res/values-ru/strings.xml b/core/ui/src/androidMain/res/values-ru/strings.xml index e6e52dd0b..38798a06b 100644 --- a/core/ui/src/androidMain/res/values-ru/strings.xml +++ b/core/ui/src/androidMain/res/values-ru/strings.xml @@ -10,8 +10,6 @@ Повторить Картинка не загрузилась TMDB API не может быть пустым - При загрузке фильмов произошла ошибка - Проверить подключение к интернету Поделиться через Movies Плитка добавлена diff --git a/core/ui/src/androidMain/res/values/content_description.xml b/core/ui/src/androidMain/res/values/content_description.xml index fa42f6025..3950e213d 100644 --- a/core/ui/src/androidMain/res/values/content_description.xml +++ b/core/ui/src/androidMain/res/values/content_description.xml @@ -1,10 +1,8 @@ - Account Icon Account Avatar Image Adult Icon Appearance Icon - App Icon Back Icon Close Icon Download Icon @@ -15,6 +13,5 @@ Share Icon Search Icon Settings Icon - Voice Search Icon User Location Icon \ No newline at end of file diff --git a/core/ui/src/androidMain/res/values/strings.xml b/core/ui/src/androidMain/res/values/strings.xml index faa9a6376..8c981e698 100644 --- a/core/ui/src/androidMain/res/values/strings.xml +++ b/core/ui/src/androidMain/res/values/strings.xml @@ -10,8 +10,6 @@ Retry No Image TMDB API key is null - Error while loading movies - Check Internet Connectivity Share via Movies Movies Tile added diff --git a/core/ui/src/commonMain/composeResources/values-ru/strings.xml b/core/ui/src/commonMain/composeResources/values-ru/strings.xml index 328e7eade..3cb3105c4 100644 --- a/core/ui/src/commonMain/composeResources/values-ru/strings.xml +++ b/core/ui/src/commonMain/composeResources/values-ru/strings.xml @@ -43,6 +43,7 @@ Предстоящие фильмы Список фильмов пуст Ошибка при авторизации. Неправильный токен или нет аппрува + Успешная авторизация Загрузка изображения... Постер diff --git a/core/ui/src/commonMain/composeResources/values/strings.xml b/core/ui/src/commonMain/composeResources/values/strings.xml index 1c6c6d7b6..688839714 100644 --- a/core/ui/src/commonMain/composeResources/values/strings.xml +++ b/core/ui/src/commonMain/composeResources/values/strings.xml @@ -43,6 +43,7 @@ Upcoming Movies Movie List is Empty Failure while signing in. Wrong token or no approval + Successful authorization Downloading Image... Poster diff --git a/core/ui/src/commonMain/kotlin/org/michaelbel/movies/ui/appicon/IconAlias.kt b/core/ui/src/commonMain/kotlin/org/michaelbel/movies/ui/appicon/IconAlias.kt index 8b779122e..ed1daa6be 100644 --- a/core/ui/src/commonMain/kotlin/org/michaelbel/movies/ui/appicon/IconAlias.kt +++ b/core/ui/src/commonMain/kotlin/org/michaelbel/movies/ui/appicon/IconAlias.kt @@ -9,23 +9,23 @@ sealed class IconAlias( ) { data object Red: IconAlias( - "RedIcon", - MoviesIcons.LauncherRed + key = "RedIcon", + iconRes = MoviesIcons.LauncherRed ) data object Purple: IconAlias( - "PurpleIcon", - MoviesIcons.LauncherPurple + key = "PurpleIcon", + iconRes = MoviesIcons.LauncherPurple ) data object Brown: IconAlias( - "BrownIcon", - MoviesIcons.LauncherBrown + key = "BrownIcon", + iconRes = MoviesIcons.LauncherBrown ) data object Amoled: IconAlias( - "AmoledIcon", - MoviesIcons.LauncherAmoled + key = "AmoledIcon", + iconRes = MoviesIcons.LauncherAmoled ) companion object { diff --git a/core/ui/src/commonMain/kotlin/org/michaelbel/movies/ui/compose/iconbutton/VoiceIcon.kt b/core/ui/src/commonMain/kotlin/org/michaelbel/movies/ui/compose/iconbutton/VoiceIcon.kt new file mode 100644 index 000000000..2e8d31a48 --- /dev/null +++ b/core/ui/src/commonMain/kotlin/org/michaelbel/movies/ui/compose/iconbutton/VoiceIcon.kt @@ -0,0 +1,42 @@ +package org.michaelbel.movies.ui.compose.iconbutton + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +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 org.jetbrains.compose.resources.stringResource +import org.jetbrains.compose.ui.tooling.preview.Preview +import org.michaelbel.movies.ui.accessibility.MoviesContentDescriptionCommon +import org.michaelbel.movies.ui.icons.MoviesIcons +import org.michaelbel.movies.ui.theme.MoviesTheme + +@Composable +fun VoiceIcon( + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + IconButton( + onClick = onClick, + modifier = modifier + ) { + Image( + imageVector = MoviesIcons.KeyboardVoice, + contentDescription = stringResource(MoviesContentDescriptionCommon.VoiceIcon), + colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onPrimaryContainer) + ) + } +} + +@Preview +@Composable +private fun VoiceIconPreview() { + MoviesTheme { + VoiceIcon( + onClick = {}, + modifier = Modifier.background(MaterialTheme.colorScheme.primaryContainer) + ) + } +} \ No newline at end of file diff --git a/core/ui/src/androidMain/kotlin/org/michaelbel/movies/ui/compose/page/PageFailure.android.kt b/core/ui/src/commonMain/kotlin/org/michaelbel/movies/ui/compose/page/PageFailure2.kt similarity index 71% rename from core/ui/src/androidMain/kotlin/org/michaelbel/movies/ui/compose/page/PageFailure.android.kt rename to core/ui/src/commonMain/kotlin/org/michaelbel/movies/ui/compose/page/PageFailure2.kt index 13063b863..c76bdf495 100644 --- a/core/ui/src/androidMain/kotlin/org/michaelbel/movies/ui/compose/page/PageFailure.android.kt +++ b/core/ui/src/commonMain/kotlin/org/michaelbel/movies/ui/compose/page/PageFailure2.kt @@ -1,10 +1,5 @@ package org.michaelbel.movies.ui.compose.page -import android.content.Intent -import android.os.Build -import android.provider.Settings -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -21,21 +16,21 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.ui.tooling.preview.Preview -import org.michaelbel.movies.ui.R -import org.michaelbel.movies.ui.accessibility.MoviesContentDescription +import org.michaelbel.movies.ui.accessibility.MoviesContentDescriptionCommon import org.michaelbel.movies.ui.icons.MoviesIcons +import org.michaelbel.movies.ui.strings.MoviesStrings import org.michaelbel.movies.ui.theme.MoviesTheme @Composable fun PageFailure( - modifier: Modifier + modifier: Modifier, + isButtonVisible: Boolean, + onButtonClick: () -> Unit ) { - val settingsPanelContract = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {} - Column( modifier = modifier, verticalArrangement = Arrangement.Center, @@ -43,13 +38,13 @@ fun PageFailure( ) { Icon( imageVector = MoviesIcons.Info, - contentDescription = MoviesContentDescription.None, + contentDescription = MoviesContentDescriptionCommon.None, modifier = Modifier.size(36.dp), tint = MaterialTheme.colorScheme.error ) Text( - text = stringResource(R.string.error_loading), + text = stringResource(MoviesStrings.error_loading), modifier = Modifier .fillMaxWidth() .wrapContentHeight() @@ -58,19 +53,15 @@ fun PageFailure( style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onPrimaryContainer) ) - if (Build.VERSION.SDK_INT >= 29) { - val onCheckConnectivityClick: () -> Unit = { - settingsPanelContract.launch(Intent(Settings.Panel.ACTION_INTERNET_CONNECTIVITY)) - } - + if (isButtonVisible) { OutlinedButton( - onClick = onCheckConnectivityClick, + onClick = onButtonClick, modifier = Modifier .wrapContentSize() .padding(start = 16.dp, top = 8.dp, end = 16.dp) ) { Text( - text = stringResource(R.string.error_check_internet_connectivity) + text = stringResource(MoviesStrings.error_check_internet_connectivity) ) } } @@ -84,7 +75,9 @@ private fun PageFailurePreview() { PageFailure( modifier = Modifier .fillMaxSize() - .background(MaterialTheme.colorScheme.primaryContainer) + .background(MaterialTheme.colorScheme.primaryContainer), + isButtonVisible = true, + onButtonClick = {} ) } } \ No newline at end of file diff --git a/core/ui/src/commonMain/kotlin/org/michaelbel/movies/ui/ktx/FlowKtx.kt b/core/ui/src/commonMain/kotlin/org/michaelbel/movies/ui/ktx/FlowKtx.kt new file mode 100644 index 000000000..ecf0a76d4 --- /dev/null +++ b/core/ui/src/commonMain/kotlin/org/michaelbel/movies/ui/ktx/FlowKtx.kt @@ -0,0 +1,27 @@ +package org.michaelbel.movies.ui.ktx + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.LocalLifecycleOwner +import androidx.lifecycle.repeatOnLifecycle +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.withContext + +@Composable +fun ObserveAsEvents( + flow: Flow, + key1: Any? = null, + key2: Any? = null, + onEvent: (T) -> Unit +) { + val lifecycleOwner = LocalLifecycleOwner.current + LaunchedEffect(lifecycleOwner.lifecycle, key1, key2, flow) { + lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + withContext(Dispatchers.Main.immediate) { + flow.collect(onEvent) + } + } + } +} \ No newline at end of file diff --git a/core/ui/src/commonMain/kotlin/org/michaelbel/movies/ui/strings/MoviesStrings.kt b/core/ui/src/commonMain/kotlin/org/michaelbel/movies/ui/strings/MoviesStrings.kt index af0ea4bdd..5d4edb996 100644 --- a/core/ui/src/commonMain/kotlin/org/michaelbel/movies/ui/strings/MoviesStrings.kt +++ b/core/ui/src/commonMain/kotlin/org/michaelbel/movies/ui/strings/MoviesStrings.kt @@ -30,6 +30,7 @@ import movies.core.ui.generated.resources.error_api_key_null import movies.core.ui.generated.resources.error_check_internet_connectivity import movies.core.ui.generated.resources.error_loading import movies.core.ui.generated.resources.feed_auth_failure +import movies.core.ui.generated.resources.feed_auth_success import movies.core.ui.generated.resources.feed_error_empty import movies.core.ui.generated.resources.feed_title_now_playing import movies.core.ui.generated.resources.feed_title_popular @@ -160,6 +161,7 @@ object MoviesStrings { val feed_title_upcoming = Res.string.feed_title_upcoming val feed_error_empty = Res.string.feed_error_empty val feed_auth_failure = Res.string.feed_auth_failure + val feed_auth_success = Res.string.feed_auth_success val gallery_downloading_image = Res.string.gallery_downloading_image val gallery_poster = Res.string.gallery_poster diff --git a/core/widget/src/androidMain/kotlin/org/michaelbel/movies/widget/configure/AppWidgetConfigureViewModel.kt b/core/widget/src/androidMain/kotlin/org/michaelbel/movies/widget/configure/AppWidgetConfigureViewModel.kt index 44518bee1..c45d412cd 100644 --- a/core/widget/src/androidMain/kotlin/org/michaelbel/movies/widget/configure/AppWidgetConfigureViewModel.kt +++ b/core/widget/src/androidMain/kotlin/org/michaelbel/movies/widget/configure/AppWidgetConfigureViewModel.kt @@ -13,7 +13,7 @@ internal class AppWidgetConfigureViewModel( val themeData: StateFlow = interactor.themeData .stateIn( - scope = this, + scope = scope, started = SharingStarted.Lazily, initialValue = ThemeData.Default ) diff --git a/core/widget/src/androidMain/kotlin/org/michaelbel/movies/widget/ktx/AppWidgetManagerKtx.kt b/core/widget/src/androidMain/kotlin/org/michaelbel/movies/widget/ktx/AppWidgetManagerKtx.kt new file mode 100644 index 000000000..6394d0cad --- /dev/null +++ b/core/widget/src/androidMain/kotlin/org/michaelbel/movies/widget/ktx/AppWidgetManagerKtx.kt @@ -0,0 +1,16 @@ +package org.michaelbel.movies.widget.ktx + +import android.appwidget.AppWidgetManager +import android.os.Build +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext + +@Composable +fun rememberAndPinAppWidgetProvider(): () -> Unit { + if (Build.VERSION.SDK_INT < 26) return {} + val context = LocalContext.current + val appWidgetManager = remember { AppWidgetManager.getInstance(context) } + val appWidgetProvider = remember { appWidgetManager.getInstalledProvidersForPackage(context.packageName, null).firstOrNull() } + return { appWidgetProvider?.pin(context) ?: run {} } +} \ No newline at end of file diff --git a/core/widget/src/androidMain/kotlin/org/michaelbel/movies/widget/ktx/AppWidgetProviderInfoKtx.kt b/core/widget/src/androidMain/kotlin/org/michaelbel/movies/widget/ktx/AppWidgetProviderInfoKtx.kt index e55550118..affdc58bb 100644 --- a/core/widget/src/androidMain/kotlin/org/michaelbel/movies/widget/ktx/AppWidgetProviderInfoKtx.kt +++ b/core/widget/src/androidMain/kotlin/org/michaelbel/movies/widget/ktx/AppWidgetProviderInfoKtx.kt @@ -1,21 +1,21 @@ -@file:SuppressLint("NewApi") - package org.michaelbel.movies.widget.ktx -import android.annotation.SuppressLint import android.app.PendingIntent import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetProviderInfo import android.content.Context import android.content.Intent +import android.os.Build import org.michaelbel.movies.widget.configure.AppWidgetPinnedReceiver -fun AppWidgetProviderInfo.pin(context: Context) { +internal fun AppWidgetProviderInfo.pin(context: Context) { val successCallback = PendingIntent.getBroadcast( context, 0, Intent(context, AppWidgetPinnedReceiver::class.java), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) - AppWidgetManager.getInstance(context).requestPinAppWidget(provider, null, successCallback) + if (Build.VERSION.SDK_INT >= 26) { + AppWidgetManager.getInstance(context).requestPinAppWidget(provider, null, successCallback) + } } \ No newline at end of file diff --git a/feature/account-impl/src/androidMain/kotlin/org/michaelbel/movies/account/AccountViewModel.kt b/feature/account-impl/src/androidMain/kotlin/org/michaelbel/movies/account/AccountViewModel.kt index 6fa7bebff..21ae171f1 100644 --- a/feature/account-impl/src/androidMain/kotlin/org/michaelbel/movies/account/AccountViewModel.kt +++ b/feature/account-impl/src/androidMain/kotlin/org/michaelbel/movies/account/AccountViewModel.kt @@ -20,7 +20,7 @@ class AccountViewModel( val account: StateFlow = interactor.account .stateIn( - scope = this, + scope = scope, started = SharingStarted.Lazily, initialValue = AccountPojo.Empty ) @@ -32,7 +32,7 @@ class AccountViewModel( } } - fun onLogoutClick(onResult: () -> Unit) = launch { + fun onLogoutClick(onResult: () -> Unit) = scope.launch { loading = true interactor.deleteSession() diff --git a/feature/account-impl/src/commonMain/kotlin/org/michaelbel/movies/account/ui/AccountScreenContent.kt b/feature/account-impl/src/commonMain/kotlin/org/michaelbel/movies/account/ui/AccountScreenContent.kt index 792f54b53..7750c8111 100644 --- a/feature/account-impl/src/commonMain/kotlin/org/michaelbel/movies/account/ui/AccountScreenContent.kt +++ b/feature/account-impl/src/commonMain/kotlin/org/michaelbel/movies/account/ui/AccountScreenContent.kt @@ -61,7 +61,9 @@ internal fun AccountScreenContent( .padding(top = 8.dp), verticalAlignment = Alignment.CenterVertically ) { - Box { + Box( + modifier = Modifier.padding(start = 16.dp) + ) { AccountAvatar( account = account, fontSize = account.lettersTextFontSizeLarge, diff --git a/feature/account-impl/src/iosMain/kotlin/org/michaelbel/movies/account/AccountViewModel.kt b/feature/account-impl/src/iosMain/kotlin/org/michaelbel/movies/account/AccountViewModel.kt index 7fe39f525..105db675b 100644 --- a/feature/account-impl/src/iosMain/kotlin/org/michaelbel/movies/account/AccountViewModel.kt +++ b/feature/account-impl/src/iosMain/kotlin/org/michaelbel/movies/account/AccountViewModel.kt @@ -3,7 +3,6 @@ package org.michaelbel.movies.account import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue -import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.stateIn @@ -21,7 +20,7 @@ class AccountViewModel( val account: StateFlow = interactor.account .stateIn( - scope = viewModelScope, + scope = scope, started = SharingStarted.Lazily, initialValue = AccountPojo.Empty ) @@ -33,9 +32,8 @@ class AccountViewModel( } } - fun onLogoutClick(onResult: () -> Unit) = viewModelScope.launch { + fun onLogoutClick(onResult: () -> Unit) = scope.launch { loading = true - interactor.deleteSession() onResult() } diff --git a/feature/account-impl/src/jvmMain/kotlin/org/michaelbel/movies/account/AccountViewModel.kt b/feature/account-impl/src/jvmMain/kotlin/org/michaelbel/movies/account/AccountViewModel.kt index 7fe39f525..105db675b 100644 --- a/feature/account-impl/src/jvmMain/kotlin/org/michaelbel/movies/account/AccountViewModel.kt +++ b/feature/account-impl/src/jvmMain/kotlin/org/michaelbel/movies/account/AccountViewModel.kt @@ -3,7 +3,6 @@ package org.michaelbel.movies.account import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue -import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.stateIn @@ -21,7 +20,7 @@ class AccountViewModel( val account: StateFlow = interactor.account .stateIn( - scope = viewModelScope, + scope = scope, started = SharingStarted.Lazily, initialValue = AccountPojo.Empty ) @@ -33,9 +32,8 @@ class AccountViewModel( } } - fun onLogoutClick(onResult: () -> Unit) = viewModelScope.launch { + fun onLogoutClick(onResult: () -> Unit) = scope.launch { loading = true - interactor.deleteSession() onResult() } diff --git a/feature/account/src/androidMain/kotlin/org/michaelbel/movies/account/AccountNavigation.android.kt b/feature/account/src/androidMain/kotlin/org/michaelbel/movies/account/AccountNavigation.android.kt index 1eb9fdf6b..bd364b14f 100644 --- a/feature/account/src/androidMain/kotlin/org/michaelbel/movies/account/AccountNavigation.android.kt +++ b/feature/account/src/androidMain/kotlin/org/michaelbel/movies/account/AccountNavigation.android.kt @@ -6,10 +6,6 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.dialog import org.michaelbel.movies.account.ui.AccountRoute -fun NavController.navigateToAccount() { - navigate(AccountDestination) -} - fun NavGraphBuilder.accountGraph( navigateBack: () -> Unit ) { diff --git a/feature/account/src/commonMain/kotlin/org/michaelbel/movies/account/AccountNavigation.kt b/feature/account/src/commonMain/kotlin/org/michaelbel/movies/account/AccountNavigation.kt new file mode 100644 index 000000000..3cbac2305 --- /dev/null +++ b/feature/account/src/commonMain/kotlin/org/michaelbel/movies/account/AccountNavigation.kt @@ -0,0 +1,7 @@ +package org.michaelbel.movies.account + +import androidx.navigation.NavController + +fun NavController.navigateToAccount() { + navigate(AccountDestination) +} \ No newline at end of file diff --git a/feature/account/src/iosMain/kotlin/org/michaelbel/movies/account/AccountNavigation.ios.kt b/feature/account/src/iosMain/kotlin/org/michaelbel/movies/account/AccountNavigation.ios.kt index 852cd7068..38389862f 100644 --- a/feature/account/src/iosMain/kotlin/org/michaelbel/movies/account/AccountNavigation.ios.kt +++ b/feature/account/src/iosMain/kotlin/org/michaelbel/movies/account/AccountNavigation.ios.kt @@ -1,15 +1,10 @@ package org.michaelbel.movies.account import androidx.compose.ui.window.DialogProperties -import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.dialog import org.michaelbel.movies.account.ui.AccountRoute -fun NavController.navigateToAccount() { - navigate(AccountDestination) -} - fun NavGraphBuilder.accountGraph( navigateBack: () -> Unit ) { diff --git a/feature/account/src/jvmMain/kotlin/org/michaelbel/movies/account/AccountNavigation.desktop.kt b/feature/account/src/jvmMain/kotlin/org/michaelbel/movies/account/AccountNavigation.desktop.kt index 852cd7068..38389862f 100644 --- a/feature/account/src/jvmMain/kotlin/org/michaelbel/movies/account/AccountNavigation.desktop.kt +++ b/feature/account/src/jvmMain/kotlin/org/michaelbel/movies/account/AccountNavigation.desktop.kt @@ -1,15 +1,10 @@ package org.michaelbel.movies.account import androidx.compose.ui.window.DialogProperties -import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.dialog import org.michaelbel.movies.account.ui.AccountRoute -fun NavController.navigateToAccount() { - navigate(AccountDestination) -} - fun NavGraphBuilder.accountGraph( navigateBack: () -> Unit ) { diff --git a/feature/auth-impl/src/androidMain/kotlin/org/michaelbel/movies/auth/AuthViewModel.kt b/feature/auth-impl/src/androidMain/kotlin/org/michaelbel/movies/auth/AuthViewModel.kt index a0cbfcbfc..901ed053f 100644 --- a/feature/auth-impl/src/androidMain/kotlin/org/michaelbel/movies/auth/AuthViewModel.kt +++ b/feature/auth-impl/src/androidMain/kotlin/org/michaelbel/movies/auth/AuthViewModel.kt @@ -39,7 +39,7 @@ class AuthViewModel( } } - fun onSignInClick(username: Username, password: Password, onResult: () -> Unit) = launch { + fun onSignInClick(username: Username, password: Password, onResult: () -> Unit) = scope.launch { error = null signInLoading = true val token = interactor.createRequestToken(loginViaTmdb = false) @@ -51,7 +51,7 @@ class AuthViewModel( onResult() } - fun onLoginClick() = launch { + fun onLoginClick() = scope.launch { error = null loginLoading = true requestToken = interactor.createRequestToken(loginViaTmdb = true).requestToken diff --git a/feature/auth-impl/src/iosMain/kotlin/org/michaelbel/movies/auth/AuthViewModel.kt b/feature/auth-impl/src/iosMain/kotlin/org/michaelbel/movies/auth/AuthViewModel.kt index 7fcd317b1..6cef60760 100644 --- a/feature/auth-impl/src/iosMain/kotlin/org/michaelbel/movies/auth/AuthViewModel.kt +++ b/feature/auth-impl/src/iosMain/kotlin/org/michaelbel/movies/auth/AuthViewModel.kt @@ -5,6 +5,4 @@ import org.michaelbel.movies.interactor.Interactor class AuthViewModel( private val interactor: Interactor -): BaseViewModel() { - -} \ No newline at end of file +): BaseViewModel() \ No newline at end of file diff --git a/feature/auth-impl/src/jvmMain/kotlin/org/michaelbel/movies/auth/AuthViewModel.kt b/feature/auth-impl/src/jvmMain/kotlin/org/michaelbel/movies/auth/AuthViewModel.kt index 7fcd317b1..6cef60760 100644 --- a/feature/auth-impl/src/jvmMain/kotlin/org/michaelbel/movies/auth/AuthViewModel.kt +++ b/feature/auth-impl/src/jvmMain/kotlin/org/michaelbel/movies/auth/AuthViewModel.kt @@ -5,6 +5,4 @@ import org.michaelbel.movies.interactor.Interactor class AuthViewModel( private val interactor: Interactor -): BaseViewModel() { - -} \ No newline at end of file +): BaseViewModel() \ No newline at end of file diff --git a/feature/debug-impl/src/androidMain/kotlin/org/michaelbel/movies/debug/DebugViewModel.kt b/feature/debug-impl/src/androidMain/kotlin/org/michaelbel/movies/debug/DebugViewModel.kt index 738e4d401..1b389eb3b 100644 --- a/feature/debug-impl/src/androidMain/kotlin/org/michaelbel/movies/debug/DebugViewModel.kt +++ b/feature/debug-impl/src/androidMain/kotlin/org/michaelbel/movies/debug/DebugViewModel.kt @@ -21,14 +21,14 @@ internal class DebugViewModel( val themeDataFlow: StateFlow = interactor.themeData .stateIn( - scope = this, + scope = scope, started = SharingStarted.Lazily, initialValue = ThemeData.Default ) val firebaseTokenFlow: StateFlow = flow { emit(messagingService.awaitToken()) } .stateIn( - scope = this, + scope = scope, started = SharingStarted.Lazily, initialValue = "" ) diff --git a/feature/debug/build.gradle.kts b/feature/debug/build.gradle.kts index c12fcf824..76e8a3047 100644 --- a/feature/debug/build.gradle.kts +++ b/feature/debug/build.gradle.kts @@ -13,7 +13,7 @@ kotlin { androidTarget() sourceSets { - commonMain.dependencies { + androidMain.dependencies { api(project(":feature:debug-impl")) } } diff --git a/feature/details-impl/src/androidMain/kotlin/org/michaelbel/movies/details/DetailsViewModel.kt b/feature/details-impl/src/androidMain/kotlin/org/michaelbel/movies/details/DetailsViewModel.kt index 11e309435..e684daeb0 100644 --- a/feature/details-impl/src/androidMain/kotlin/org/michaelbel/movies/details/DetailsViewModel.kt +++ b/feature/details-impl/src/androidMain/kotlin/org/michaelbel/movies/details/DetailsViewModel.kt @@ -33,14 +33,14 @@ class DetailsViewModel( val networkStatus: StateFlow = networkManager.status .stateIn( - scope = this, + scope = scope, started = SharingStarted.Lazily, initialValue = NetworkStatus.Unavailable ) val currentTheme: StateFlow = interactor.currentTheme .stateIn( - scope = this, + scope = scope, started = SharingStarted.Lazily, initialValue = AppTheme.FollowSystem ) @@ -58,7 +58,7 @@ class DetailsViewModel( fun retry() = loadMovie() - fun onGenerateColors(movieId: MovieId, palette: Palette) = launch { + fun onGenerateColors(movieId: MovieId, palette: Palette) = scope.launch { val containerColor = palette.vibrantSwatch?.rgb val onContainerColor = palette.vibrantSwatch?.bodyTextColor if (containerColor != null && onContainerColor != null) { @@ -69,7 +69,7 @@ class DetailsViewModel( } } - private fun loadMovie() = launch { + private fun loadMovie() = scope.launch { val movieDb = interactor.movieDetails(movieList.orEmpty(), movieId) _detailsState.value = ScreenState.Content(movieDb) } diff --git a/feature/details-impl/src/androidMain/kotlin/org/michaelbel/movies/details/di/DetailsKoinModule.android.kt b/feature/details-impl/src/androidMain/kotlin/org/michaelbel/movies/details/di/DetailsKoinModule.android.kt index 7f88d2d44..002b3b2a0 100644 --- a/feature/details-impl/src/androidMain/kotlin/org/michaelbel/movies/details/di/DetailsKoinModule.android.kt +++ b/feature/details-impl/src/androidMain/kotlin/org/michaelbel/movies/details/di/DetailsKoinModule.android.kt @@ -1,6 +1,6 @@ package org.michaelbel.movies.details.di -import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.core.module.dsl.viewModel import org.koin.dsl.module import org.michaelbel.movies.details.DetailsViewModel import org.michaelbel.movies.interactor.di.interactorKoinModule diff --git a/feature/details-impl/src/iosMain/kotlin/org/michaelbel/movies/details/DetailsViewModel.kt b/feature/details-impl/src/iosMain/kotlin/org/michaelbel/movies/details/DetailsViewModel.kt index fce4cfabe..4cc2fc611 100644 --- a/feature/details-impl/src/iosMain/kotlin/org/michaelbel/movies/details/DetailsViewModel.kt +++ b/feature/details-impl/src/iosMain/kotlin/org/michaelbel/movies/details/DetailsViewModel.kt @@ -1,7 +1,6 @@ package org.michaelbel.movies.details import androidx.lifecycle.SavedStateHandle -import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -30,7 +29,7 @@ class DetailsViewModel( fun retry() = loadMovie() - private fun loadMovie() = viewModelScope.launch { + private fun loadMovie() = scope.launch { val movieDb = interactor.movieDetails(movieList.orEmpty(), movieId) _detailsState.value = ScreenState.Content(movieDb) } diff --git a/feature/details-impl/src/jvmMain/kotlin/org/michaelbel/movies/details/DetailsViewModel.kt b/feature/details-impl/src/jvmMain/kotlin/org/michaelbel/movies/details/DetailsViewModel.kt index 6202b0b67..79730c270 100644 --- a/feature/details-impl/src/jvmMain/kotlin/org/michaelbel/movies/details/DetailsViewModel.kt +++ b/feature/details-impl/src/jvmMain/kotlin/org/michaelbel/movies/details/DetailsViewModel.kt @@ -1,7 +1,6 @@ package org.michaelbel.movies.details import androidx.lifecycle.SavedStateHandle -import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -30,7 +29,7 @@ class DetailsViewModel( fun retry() = loadMovie() - private fun loadMovie() = viewModelScope.launch { + private fun loadMovie() = scope.launch { val movieDb = interactor.movieDetails(movieList.orEmpty(), movieId) _detailsState.value = ScreenState.Content(movieDb) } diff --git a/feature/details/src/androidMain/kotlin/org/michaelbel/movies/details/DetailsNavigation.android.kt b/feature/details/src/androidMain/kotlin/org/michaelbel/movies/details/DetailsNavigation.android.kt index bc238b926..6c8cd502f 100644 --- a/feature/details/src/androidMain/kotlin/org/michaelbel/movies/details/DetailsNavigation.android.kt +++ b/feature/details/src/androidMain/kotlin/org/michaelbel/movies/details/DetailsNavigation.android.kt @@ -1,16 +1,9 @@ package org.michaelbel.movies.details -import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable import androidx.navigation.navDeepLink import org.michaelbel.movies.details.ui.DetailsRoute -import org.michaelbel.movies.persistence.database.typealiases.MovieId -import org.michaelbel.movies.persistence.database.typealiases.PagingKey - -fun NavController.navigateToDetails(pagingKey: PagingKey, movieId: MovieId) { - navigate(DetailsDestination(pagingKey, movieId)) -} fun NavGraphBuilder.detailsGraph( navigateBack: () -> Unit, diff --git a/feature/details/src/commonMain/kotlin/org/michaelbel/movies/details/DetailsNavigation.kt b/feature/details/src/commonMain/kotlin/org/michaelbel/movies/details/DetailsNavigation.kt new file mode 100644 index 000000000..69ece6413 --- /dev/null +++ b/feature/details/src/commonMain/kotlin/org/michaelbel/movies/details/DetailsNavigation.kt @@ -0,0 +1,9 @@ +package org.michaelbel.movies.details + +import androidx.navigation.NavController +import org.michaelbel.movies.persistence.database.typealiases.MovieId +import org.michaelbel.movies.persistence.database.typealiases.PagingKey + +fun NavController.navigateToDetails(pagingKey: PagingKey, movieId: MovieId) { + navigate(DetailsDestination(pagingKey, movieId)) +} \ No newline at end of file diff --git a/feature/details/src/iosMain/kotlin/org/michaelbel/movies/details/DetailsNavigation.ios.kt b/feature/details/src/iosMain/kotlin/org/michaelbel/movies/details/DetailsNavigation.ios.kt index 8b4c8e6d3..adeb5565d 100644 --- a/feature/details/src/iosMain/kotlin/org/michaelbel/movies/details/DetailsNavigation.ios.kt +++ b/feature/details/src/iosMain/kotlin/org/michaelbel/movies/details/DetailsNavigation.ios.kt @@ -1,15 +1,9 @@ package org.michaelbel.movies.details -import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable import org.michaelbel.movies.details.ui.DetailsRoute import org.michaelbel.movies.persistence.database.typealiases.MovieId -import org.michaelbel.movies.persistence.database.typealiases.PagingKey - -fun NavController.navigateToDetails(pagingKey: PagingKey, movieId: MovieId) { - navigate(DetailsDestination(pagingKey, movieId)) -} fun NavGraphBuilder.detailsGraph( navigateBack: () -> Unit, diff --git a/feature/details/src/jvmMain/kotlin/org/michaelbel/movies/details/DetailsNavigation.desktop.kt b/feature/details/src/jvmMain/kotlin/org/michaelbel/movies/details/DetailsNavigation.desktop.kt index 8b4c8e6d3..adeb5565d 100644 --- a/feature/details/src/jvmMain/kotlin/org/michaelbel/movies/details/DetailsNavigation.desktop.kt +++ b/feature/details/src/jvmMain/kotlin/org/michaelbel/movies/details/DetailsNavigation.desktop.kt @@ -1,15 +1,9 @@ package org.michaelbel.movies.details -import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable import org.michaelbel.movies.details.ui.DetailsRoute import org.michaelbel.movies.persistence.database.typealiases.MovieId -import org.michaelbel.movies.persistence.database.typealiases.PagingKey - -fun NavController.navigateToDetails(pagingKey: PagingKey, movieId: MovieId) { - navigate(DetailsDestination(pagingKey, movieId)) -} fun NavGraphBuilder.detailsGraph( navigateBack: () -> Unit, diff --git a/feature/feed-impl-web/src/jsMain/kotlin/org/michaelbel/movies/feed/FeedViewModel.kt b/feature/feed-impl-web/src/jsMain/kotlin/org/michaelbel/movies/feed/FeedViewModel.kt index ee04e976d..fb4633d22 100644 --- a/feature/feed-impl-web/src/jsMain/kotlin/org/michaelbel/movies/feed/FeedViewModel.kt +++ b/feature/feed-impl-web/src/jsMain/kotlin/org/michaelbel/movies/feed/FeedViewModel.kt @@ -1,9 +1,5 @@ -@file:OptIn(ExperimentalCoroutinesApi::class) - package org.michaelbel.movies.feed -import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.flowOf @@ -16,14 +12,14 @@ class FeedViewModel: BaseViewModel() { val currentFeedView: StateFlow = flowOf(FeedView.FeedList) .stateIn( - scope = viewModelScope, + scope = scope, started = SharingStarted.Lazily, initialValue = FeedView.FeedList ) val currentMovieList: StateFlow = flowOf(MovieList.NowPlaying()) .stateIn( - scope = viewModelScope, + scope = scope, started = SharingStarted.Lazily, initialValue = MovieList.NowPlaying() ) diff --git a/feature/feed-impl/src/androidMain/kotlin/org/michaelbel/movies/feed/FeedViewModel.kt b/feature/feed-impl/src/androidMain/kotlin/org/michaelbel/movies/feed/FeedViewModel.kt index 3cf95b9cb..031886fda 100644 --- a/feature/feed-impl/src/androidMain/kotlin/org/michaelbel/movies/feed/FeedViewModel.kt +++ b/feature/feed-impl/src/androidMain/kotlin/org/michaelbel/movies/feed/FeedViewModel.kt @@ -2,10 +2,6 @@ package org.michaelbel.movies.feed -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue -import androidx.lifecycle.SavedStateHandle import androidx.paging.PagingData import androidx.paging.cachedIn import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -20,8 +16,6 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import org.michaelbel.movies.common.appearance.FeedView -import org.michaelbel.movies.common.exceptions.AccountDetailsException -import org.michaelbel.movies.common.exceptions.CreateSessionException import org.michaelbel.movies.common.list.MovieList import org.michaelbel.movies.common.viewmodel.BaseViewModel import org.michaelbel.movies.interactor.Interactor @@ -32,85 +26,56 @@ import org.michaelbel.movies.persistence.database.entity.pojo.AccountPojo import org.michaelbel.movies.persistence.database.entity.pojo.MoviePojo class FeedViewModel( - savedStateHandle: SavedStateHandle, private val interactor: Interactor, private val notificationClient: NotificationClient, networkManager: NetworkManager ): BaseViewModel() { - private val requestToken: String? = savedStateHandle["request_token"] - private val approved: Boolean? = savedStateHandle["approved"] - val account: StateFlow = interactor.account .stateIn( - scope = this, + scope = scope, started = SharingStarted.Lazily, initialValue = AccountPojo.Empty ) val networkStatus: StateFlow = networkManager.status .stateIn( - scope = this, + scope = scope, started = SharingStarted.Lazily, initialValue = NetworkStatus.Unavailable ) val currentFeedView: StateFlow = interactor.currentFeedView .stateIn( - scope = this, + scope = scope, started = SharingStarted.Lazily, initialValue = runBlocking { interactor.currentFeedView.first() } ) val currentMovieList: StateFlow = interactor.currentMovieList .stateIn( - scope = this, + scope = scope, started = SharingStarted.Lazily, initialValue = runBlocking { interactor.currentMovieList.first() } ) val pagingDataFlow: Flow> = currentMovieList .flatMapLatest { movieList -> interactor.moviesPagingData(movieList) } - .cachedIn(this) + .cachedIn(scope) private var _notificationsPermissionRequired: MutableStateFlow = MutableStateFlow(false) val notificationsPermissionRequired: StateFlow get() = _notificationsPermissionRequired.asStateFlow() - var isAuthFailureSnackbarShowed: Boolean by mutableStateOf(false) - init { - authorizeAccount(requestToken, approved) subscribeNotificationsPermissionRequired() } - override fun handleError(throwable: Throwable) { - when (throwable) { - is CreateSessionException -> isAuthFailureSnackbarShowed = true - is AccountDetailsException -> isAuthFailureSnackbarShowed = true - else -> super.handleError(throwable) - } - } - - fun onNotificationBottomSheetHide() = launch { + fun onNotificationBottomSheetHide() = scope.launch { _notificationsPermissionRequired.tryEmit(false) notificationClient.updateNotificationExpireTime() } - fun onSnackbarDismissed() { - isAuthFailureSnackbarShowed = false - } - - private fun authorizeAccount(requestToken: String?, approved: Boolean?) { - if (requestToken == null || approved == null) return - launch { - interactor.run { - createSession(requestToken) - accountDetails() - } - } - } - - private fun subscribeNotificationsPermissionRequired() = launch { + private fun subscribeNotificationsPermissionRequired() = scope.launch { _notificationsPermissionRequired.tryEmit( notificationClient.notificationsPermissionRequired(NOTIFICATIONS_PERMISSION_DELAY) ) diff --git a/feature/feed-impl/src/androidMain/kotlin/org/michaelbel/movies/feed/di/FeedKoinModule.android.kt b/feature/feed-impl/src/androidMain/kotlin/org/michaelbel/movies/feed/di/FeedKoinModule.android.kt index 1e45fc5d7..03f319480 100644 --- a/feature/feed-impl/src/androidMain/kotlin/org/michaelbel/movies/feed/di/FeedKoinModule.android.kt +++ b/feature/feed-impl/src/androidMain/kotlin/org/michaelbel/movies/feed/di/FeedKoinModule.android.kt @@ -1,6 +1,6 @@ package org.michaelbel.movies.feed.di -import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.core.module.dsl.viewModel import org.koin.dsl.module import org.michaelbel.movies.feed.FeedViewModel import org.michaelbel.movies.interactor.di.interactorKoinModule @@ -13,5 +13,5 @@ actual val feedKoinModule = module { notificationClientKoinModule, networkManagerKoinModule ) - viewModel { FeedViewModel(get(), get(), get(), get()) } + viewModel { FeedViewModel(get(), get(), get()) } } \ No newline at end of file diff --git a/feature/feed-impl/src/androidMain/kotlin/org/michaelbel/movies/feed/ui/FeedRoute.android.kt b/feature/feed-impl/src/androidMain/kotlin/org/michaelbel/movies/feed/ui/FeedRoute.android.kt index 694c24977..b3abb5e23 100644 --- a/feature/feed-impl/src/androidMain/kotlin/org/michaelbel/movies/feed/ui/FeedRoute.android.kt +++ b/feature/feed-impl/src/androidMain/kotlin/org/michaelbel/movies/feed/ui/FeedRoute.android.kt @@ -25,7 +25,6 @@ fun FeedRoute( val currentMovieList by viewModel.currentMovieList.collectAsStateCommon() val notificationsPermissionRequired by viewModel.notificationsPermissionRequired.collectAsStateCommon() val networkStatus by viewModel.networkStatus.collectAsStateCommon() - val isAuthFailureSnackbarShowed = viewModel.isAuthFailureSnackbarShowed FeedScreenContent( pagingItems = pagingItems, @@ -34,14 +33,12 @@ fun FeedRoute( currentFeedView = currentFeedView, currentMovieList = currentMovieList, notificationsPermissionRequired = notificationsPermissionRequired, - isAuthFailureSnackbarShowed = isAuthFailureSnackbarShowed, onNavigateToSearch = onNavigateToSearch, onNavigateToAuth = onNavigateToAuth, onNavigateToAccount = onNavigateToAccount, onNavigateToSettings = onNavigateToSettings, onNavigateToDetails = onNavigateToDetails, onNotificationBottomSheetHideClick = viewModel::onNotificationBottomSheetHide, - onSnackbarDismissed = viewModel::onSnackbarDismissed, modifier = modifier ) } \ No newline at end of file diff --git a/feature/feed-impl/src/androidMain/kotlin/org/michaelbel/movies/feed/ui/FeedScreenContent.android.kt b/feature/feed-impl/src/androidMain/kotlin/org/michaelbel/movies/feed/ui/FeedScreenContent.android.kt index 3a0c72781..933836989 100644 --- a/feature/feed-impl/src/androidMain/kotlin/org/michaelbel/movies/feed/ui/FeedScreenContent.android.kt +++ b/feature/feed-impl/src/androidMain/kotlin/org/michaelbel/movies/feed/ui/FeedScreenContent.android.kt @@ -2,6 +2,7 @@ package org.michaelbel.movies.feed.ui +import android.os.Build import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -15,7 +16,6 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.SnackbarResult import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -33,7 +33,6 @@ import org.michaelbel.movies.common.exceptions.ApiKeyNotNullException import org.michaelbel.movies.common.exceptions.PageEmptyException import org.michaelbel.movies.common.list.MovieList import org.michaelbel.movies.feed.ktx.titleText -import org.michaelbel.movies.feed_impl.R import org.michaelbel.movies.network.config.isTmdbApiKeyEmpty import org.michaelbel.movies.network.connectivity.NetworkStatus import org.michaelbel.movies.persistence.database.entity.pojo.AccountPojo @@ -47,6 +46,7 @@ import org.michaelbel.movies.ui.ktx.displayCutoutWindowInsets import org.michaelbel.movies.ui.ktx.isFailure import org.michaelbel.movies.ui.ktx.isLoading import org.michaelbel.movies.ui.ktx.refreshThrowable +import org.michaelbel.movies.ui.ktx.rememberConnectivityClickHandler import java.net.UnknownHostException import org.michaelbel.movies.ui.R as UiR @@ -58,14 +58,12 @@ internal fun FeedScreenContent( currentFeedView: FeedView, currentMovieList: MovieList, notificationsPermissionRequired: Boolean, - isAuthFailureSnackbarShowed: Boolean, onNavigateToSearch: () -> Unit, onNavigateToAuth: () -> Unit, onNavigateToAccount: () -> Unit, onNavigateToSettings: () -> Unit, onNavigateToDetails: (String, Int) -> Unit, onNotificationBottomSheetHideClick: () -> Unit, - onSnackbarDismissed: () -> Unit, modifier: Modifier = Modifier ) { val scope = rememberCoroutineScope() @@ -83,13 +81,10 @@ internal fun FeedScreenContent( val onShowSnackbar: (String, SnackbarDuration) -> Unit = { message, snackbarDuration -> scope.launch { snackbarHostState.currentSnackbarData?.dismiss() - val snackbarResult = snackbarHostState.showSnackbar( + snackbarHostState.showSnackbar( message = message, duration = snackbarDuration ) - if (snackbarResult == SnackbarResult.Dismissed) { - onSnackbarDismissed() - } } } @@ -113,10 +108,6 @@ internal fun FeedScreenContent( ) } - if (isAuthFailureSnackbarShowed) { - onShowSnackbar(stringResource(R.string.feed_auth_failure), SnackbarDuration.Short) - } - val topAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() Scaffold( @@ -133,6 +124,7 @@ internal fun FeedScreenContent( onAuthIconClick = onNavigateToAuth, onAccountIconClick = onNavigateToAccount, topAppBarScrollBehavior = topAppBarScrollBehavior, + isSettingsIconVisible = false, onSettingsIconClick = onNavigateToSettings ) }, @@ -165,7 +157,9 @@ internal fun FeedScreenContent( .padding(innerPadding) .windowInsetsPadding(displayCutoutWindowInsets) .fillMaxSize() - .clickableWithoutRipple(pagingItems::retry) + .clickableWithoutRipple(pagingItems::retry), + isButtonVisible = Build.VERSION.SDK_INT >= 29, + onButtonClick = rememberConnectivityClickHandler() ) } } @@ -178,7 +172,7 @@ internal fun FeedScreenContent( pagingItems = pagingItems, onMovieClick = onNavigateToDetails, contentPadding = innerPadding, - modifier = Modifier.windowInsetsPadding(displayCutoutWindowInsets) + modifier = Modifier.windowInsetsPadding(displayCutoutWindowInsets), ) } } diff --git a/feature/feed-impl/src/androidMain/res/values-ru/strings.xml b/feature/feed-impl/src/androidMain/res/values-ru/strings.xml index e61a2e732..793152d04 100644 --- a/feature/feed-impl/src/androidMain/res/values-ru/strings.xml +++ b/feature/feed-impl/src/androidMain/res/values-ru/strings.xml @@ -1,5 +1,4 @@ Список фильмов пуст - Ошибка при авторизации. Неправильный токен или нет аппрува \ No newline at end of file diff --git a/feature/feed-impl/src/androidMain/res/values/strings.xml b/feature/feed-impl/src/androidMain/res/values/strings.xml index cdd3a8d3f..cb8a5a764 100644 --- a/feature/feed-impl/src/androidMain/res/values/strings.xml +++ b/feature/feed-impl/src/androidMain/res/values/strings.xml @@ -1,5 +1,4 @@ Movie List is Empty - Failure while signing in. Wrong token or no approval \ No newline at end of file diff --git a/feature/feed-impl/src/commonMain/kotlin/org/michaelbel/movies/feed/ui/FeedToolbar.kt b/feature/feed-impl/src/commonMain/kotlin/org/michaelbel/movies/feed/ui/FeedToolbar.kt index f2197fe84..0438a8d5b 100644 --- a/feature/feed-impl/src/commonMain/kotlin/org/michaelbel/movies/feed/ui/FeedToolbar.kt +++ b/feature/feed-impl/src/commonMain/kotlin/org/michaelbel/movies/feed/ui/FeedToolbar.kt @@ -47,6 +47,7 @@ internal fun FeedToolbar( isAuthIconVisible: Boolean, onAuthIconClick: () -> Unit, onAccountIconClick: () -> Unit, + isSettingsIconVisible: Boolean, onSettingsIconClick: () -> Unit, topAppBarScrollBehavior: TopAppBarScrollBehavior, modifier: Modifier = Modifier @@ -78,9 +79,11 @@ internal fun FeedToolbar( Row( modifier = Modifier.then(modifierDisplayCutoutWindowInsets) ) { - SettingsIcon( - onClick = onSettingsIconClick - ) + if (isSettingsIconVisible) { + SettingsIcon( + onClick = onSettingsIconClick + ) + } if (isAuthIconVisible) { IconButton( @@ -131,6 +134,7 @@ private fun FeedToolbarPreview() { onAccountIconClick = {}, isAuthIconVisible = true, onAuthIconClick = {}, + isSettingsIconVisible = true, onSettingsIconClick = {}, topAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(), modifier = Modifier.statusBarsPadding() diff --git a/feature/feed-impl/src/iosMain/kotlin/org/michaelbel/movies/feed/FeedViewModel.kt b/feature/feed-impl/src/iosMain/kotlin/org/michaelbel/movies/feed/FeedViewModel.kt index a893261d9..fcb49df9c 100644 --- a/feature/feed-impl/src/iosMain/kotlin/org/michaelbel/movies/feed/FeedViewModel.kt +++ b/feature/feed-impl/src/iosMain/kotlin/org/michaelbel/movies/feed/FeedViewModel.kt @@ -2,7 +2,6 @@ package org.michaelbel.movies.feed -import androidx.lifecycle.viewModelScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -25,19 +24,19 @@ class FeedViewModel( val currentFeedView: StateFlow = interactor.currentFeedView .stateIn( - scope = viewModelScope, + scope = scope, started = SharingStarted.Lazily, initialValue = runBlocking { interactor.currentFeedView.first() } ) val currentMovieList: StateFlow = interactor.currentMovieList .stateIn( - scope = viewModelScope, + scope = scope, started = SharingStarted.Lazily, initialValue = runBlocking { interactor.currentMovieList.first() } ) val pagingDataFlow: StateFlow> = currentMovieList.flatMapLatest { movieList -> flowOf(interactor.moviesResult(movieList.nameOrLocalList)) - }.catch { emptyList>() }.stateIn(scope = viewModelScope, started = SharingStarted.Lazily, initialValue = emptyList()) + }.catch { emptyList>() }.stateIn(scope = scope, started = SharingStarted.Lazily, initialValue = emptyList()) } \ No newline at end of file diff --git a/feature/feed-impl/src/iosMain/kotlin/org/michaelbel/movies/feed/ui/FeedScreenContent.ios.kt b/feature/feed-impl/src/iosMain/kotlin/org/michaelbel/movies/feed/ui/FeedScreenContent.ios.kt index cc4171819..164fa0864 100644 --- a/feature/feed-impl/src/iosMain/kotlin/org/michaelbel/movies/feed/ui/FeedScreenContent.ios.kt +++ b/feature/feed-impl/src/iosMain/kotlin/org/michaelbel/movies/feed/ui/FeedScreenContent.ios.kt @@ -45,6 +45,7 @@ internal fun FeedScreenContent( isAuthIconVisible = false, onAuthIconClick = onNavigateToAuth, onAccountIconClick = onNavigateToAccount, + isSettingsIconVisible = true, onSettingsIconClick = onNavigateToSettings ) }, diff --git a/feature/feed-impl/src/jvmMain/kotlin/org/michaelbel/movies/feed/FeedViewModel.kt b/feature/feed-impl/src/jvmMain/kotlin/org/michaelbel/movies/feed/FeedViewModel.kt index 3cd453023..58ece746c 100644 --- a/feature/feed-impl/src/jvmMain/kotlin/org/michaelbel/movies/feed/FeedViewModel.kt +++ b/feature/feed-impl/src/jvmMain/kotlin/org/michaelbel/movies/feed/FeedViewModel.kt @@ -2,7 +2,6 @@ package org.michaelbel.movies.feed -import androidx.lifecycle.viewModelScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -24,14 +23,14 @@ class FeedViewModel( val currentFeedView: StateFlow = interactor.currentFeedView .stateIn( - scope = viewModelScope, + scope = scope, started = SharingStarted.Lazily, initialValue = runBlocking { interactor.currentFeedView.first() } ) val currentMovieList: StateFlow = interactor.currentMovieList .stateIn( - scope = viewModelScope, + scope = scope, started = SharingStarted.Lazily, initialValue = runBlocking { interactor.currentMovieList.first() } ) @@ -39,7 +38,7 @@ class FeedViewModel( val pagingDataFlow: StateFlow> = currentMovieList.flatMapLatest { movieList -> flowOf(interactor.moviesResult(movieList.nameOrLocalList)) }.stateIn( - scope = viewModelScope, + scope = scope, started = SharingStarted.Lazily, initialValue = emptyList() ) diff --git a/feature/feed-impl/src/jvmMain/kotlin/org/michaelbel/movies/feed/ui/FeedScreenContent.desktop.kt b/feature/feed-impl/src/jvmMain/kotlin/org/michaelbel/movies/feed/ui/FeedScreenContent.desktop.kt index cc4171819..164fa0864 100644 --- a/feature/feed-impl/src/jvmMain/kotlin/org/michaelbel/movies/feed/ui/FeedScreenContent.desktop.kt +++ b/feature/feed-impl/src/jvmMain/kotlin/org/michaelbel/movies/feed/ui/FeedScreenContent.desktop.kt @@ -45,6 +45,7 @@ internal fun FeedScreenContent( isAuthIconVisible = false, onAuthIconClick = onNavigateToAuth, onAccountIconClick = onNavigateToAccount, + isSettingsIconVisible = true, onSettingsIconClick = onNavigateToSettings ) }, diff --git a/feature/feed/src/androidMain/kotlin/org/michaelbel/movies/feed/FeedNavigation.android.kt b/feature/feed/src/androidMain/kotlin/org/michaelbel/movies/feed/FeedNavigation.android.kt index 4e7373cbd..a1800a0df 100644 --- a/feature/feed/src/androidMain/kotlin/org/michaelbel/movies/feed/FeedNavigation.android.kt +++ b/feature/feed/src/androidMain/kotlin/org/michaelbel/movies/feed/FeedNavigation.android.kt @@ -2,7 +2,6 @@ package org.michaelbel.movies.feed import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable -import androidx.navigation.navDeepLink import org.michaelbel.movies.feed.ui.FeedRoute fun NavGraphBuilder.feedGraph( @@ -12,11 +11,7 @@ fun NavGraphBuilder.feedGraph( navigateToSettings: () -> Unit, navigateToDetails: (String, Int) -> Unit ) { - composable( - deepLinks = listOf( - navDeepLink { uriPattern = "movies://redirect_url?request_token={requestToken}&approved={approved}" } - ) - ) { + composable { FeedRoute( onNavigateToSearch = navigateToSearch, onNavigateToAccount = navigateToAccount, diff --git a/feature/gallery-impl/src/androidMain/kotlin/org/michaelbel/movies/gallery/GalleryViewModel.kt b/feature/gallery-impl/src/androidMain/kotlin/org/michaelbel/movies/gallery/GalleryViewModel.kt index 17cab23d3..6b41ea4ba 100644 --- a/feature/gallery-impl/src/androidMain/kotlin/org/michaelbel/movies/gallery/GalleryViewModel.kt +++ b/feature/gallery-impl/src/androidMain/kotlin/org/michaelbel/movies/gallery/GalleryViewModel.kt @@ -34,7 +34,7 @@ class GalleryViewModel( val movieImagesFlow: StateFlow> = interactor.imagesFlow(movieId) .stateIn( - scope = this, + scope = scope, started = SharingStarted.Lazily, initialValue = emptyList() ) @@ -46,7 +46,7 @@ class GalleryViewModel( loadMovieImages(movieId) } - fun downloadImage(image: ImagePojo) = launch { + fun downloadImage(image: ImagePojo) = scope.launch { val workData = Data.Builder() .putString(DownloadImageWorker.KEY_IMAGE_URL, image.original) .putInt(DownloadImageWorker.KEY_CONTENT_TITLE, R.string.gallery_downloading_image) @@ -70,7 +70,7 @@ class GalleryViewModel( } } - private fun loadMovieImages(movieId: MovieId) = launch { + private fun loadMovieImages(movieId: MovieId) = scope.launch { interactor.images(movieId) } } \ No newline at end of file diff --git a/feature/gallery-impl/src/androidMain/kotlin/org/michaelbel/movies/gallery/di/GalleryKoinModule.android.kt b/feature/gallery-impl/src/androidMain/kotlin/org/michaelbel/movies/gallery/di/GalleryKoinModule.android.kt index 08cda2510..6b4a0f388 100644 --- a/feature/gallery-impl/src/androidMain/kotlin/org/michaelbel/movies/gallery/di/GalleryKoinModule.android.kt +++ b/feature/gallery-impl/src/androidMain/kotlin/org/michaelbel/movies/gallery/di/GalleryKoinModule.android.kt @@ -1,6 +1,6 @@ package org.michaelbel.movies.gallery.di -import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.core.module.dsl.viewModel import org.koin.dsl.module import org.michaelbel.movies.gallery.GalleryViewModel import org.michaelbel.movies.interactor.di.interactorKoinModule diff --git a/feature/gallery-impl/src/iosMain/kotlin/org/michaelbel/movies/gallery/GalleryViewModel.kt b/feature/gallery-impl/src/iosMain/kotlin/org/michaelbel/movies/gallery/GalleryViewModel.kt index d0a42be0e..cc78c4826 100644 --- a/feature/gallery-impl/src/iosMain/kotlin/org/michaelbel/movies/gallery/GalleryViewModel.kt +++ b/feature/gallery-impl/src/iosMain/kotlin/org/michaelbel/movies/gallery/GalleryViewModel.kt @@ -5,6 +5,4 @@ import org.michaelbel.movies.interactor.Interactor class GalleryViewModel( private val interactor: Interactor -): BaseViewModel() { - -} \ No newline at end of file +): BaseViewModel() \ No newline at end of file diff --git a/feature/gallery-impl/src/jvmMain/kotlin/org/michaelbel/movies/gallery/GalleryViewModel.kt b/feature/gallery-impl/src/jvmMain/kotlin/org/michaelbel/movies/gallery/GalleryViewModel.kt index d0a42be0e..cc78c4826 100644 --- a/feature/gallery-impl/src/jvmMain/kotlin/org/michaelbel/movies/gallery/GalleryViewModel.kt +++ b/feature/gallery-impl/src/jvmMain/kotlin/org/michaelbel/movies/gallery/GalleryViewModel.kt @@ -5,6 +5,4 @@ import org.michaelbel.movies.interactor.Interactor class GalleryViewModel( private val interactor: Interactor -): BaseViewModel() { - -} \ No newline at end of file +): BaseViewModel() \ No newline at end of file diff --git a/feature/gallery/src/androidMain/kotlin/org/michaelbel/movies/gallery/GalleryNavigation.android.kt b/feature/gallery/src/androidMain/kotlin/org/michaelbel/movies/gallery/GalleryNavigation.android.kt index 1414255b9..4bce12c4c 100644 --- a/feature/gallery/src/androidMain/kotlin/org/michaelbel/movies/gallery/GalleryNavigation.android.kt +++ b/feature/gallery/src/androidMain/kotlin/org/michaelbel/movies/gallery/GalleryNavigation.android.kt @@ -1,14 +1,8 @@ package org.michaelbel.movies.gallery -import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable import org.michaelbel.movies.gallery.ui.GalleryRoute -import org.michaelbel.movies.persistence.database.typealiases.MovieId - -fun NavController.navigateToGallery(movieId: MovieId) { - navigate(GalleryDestination(movieId)) -} fun NavGraphBuilder.galleryGraph( navigateBack: () -> Unit, diff --git a/feature/gallery/src/commonMain/kotlin/org/michaelbel/movies/gallery/GalleryNavigation.kt b/feature/gallery/src/commonMain/kotlin/org/michaelbel/movies/gallery/GalleryNavigation.kt new file mode 100644 index 000000000..1feb8fb88 --- /dev/null +++ b/feature/gallery/src/commonMain/kotlin/org/michaelbel/movies/gallery/GalleryNavigation.kt @@ -0,0 +1,8 @@ +package org.michaelbel.movies.gallery + +import androidx.navigation.NavController +import org.michaelbel.movies.persistence.database.typealiases.MovieId + +fun NavController.navigateToGallery(movieId: MovieId) { + navigate(GalleryDestination(movieId)) +} \ No newline at end of file diff --git a/feature/gallery/src/iosMain/kotlin/org/michaelbel/movies/gallery/GalleryNavigation.ios.kt b/feature/gallery/src/iosMain/kotlin/org/michaelbel/movies/gallery/GalleryNavigation.ios.kt index 1414255b9..4bce12c4c 100644 --- a/feature/gallery/src/iosMain/kotlin/org/michaelbel/movies/gallery/GalleryNavigation.ios.kt +++ b/feature/gallery/src/iosMain/kotlin/org/michaelbel/movies/gallery/GalleryNavigation.ios.kt @@ -1,14 +1,8 @@ package org.michaelbel.movies.gallery -import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable import org.michaelbel.movies.gallery.ui.GalleryRoute -import org.michaelbel.movies.persistence.database.typealiases.MovieId - -fun NavController.navigateToGallery(movieId: MovieId) { - navigate(GalleryDestination(movieId)) -} fun NavGraphBuilder.galleryGraph( navigateBack: () -> Unit, diff --git a/feature/gallery/src/jvmMain/kotlin/org/michaelbel/movies/gallery/GalleryNavigation.desktop.kt b/feature/gallery/src/jvmMain/kotlin/org/michaelbel/movies/gallery/GalleryNavigation.desktop.kt index 1414255b9..4bce12c4c 100644 --- a/feature/gallery/src/jvmMain/kotlin/org/michaelbel/movies/gallery/GalleryNavigation.desktop.kt +++ b/feature/gallery/src/jvmMain/kotlin/org/michaelbel/movies/gallery/GalleryNavigation.desktop.kt @@ -1,14 +1,8 @@ package org.michaelbel.movies.gallery -import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable import org.michaelbel.movies.gallery.ui.GalleryRoute -import org.michaelbel.movies.persistence.database.typealiases.MovieId - -fun NavController.navigateToGallery(movieId: MovieId) { - navigate(GalleryDestination(movieId)) -} fun NavGraphBuilder.galleryGraph( navigateBack: () -> Unit, diff --git a/feature/main-impl-web/src/jsMain/kotlin/org/michaelbel/movies/main/MainViewModel.kt b/feature/main-impl-web/src/jsMain/kotlin/org/michaelbel/movies/main/MainViewModel.kt index d158ff2e4..a03464534 100644 --- a/feature/main-impl-web/src/jsMain/kotlin/org/michaelbel/movies/main/MainViewModel.kt +++ b/feature/main-impl-web/src/jsMain/kotlin/org/michaelbel/movies/main/MainViewModel.kt @@ -1,6 +1,5 @@ package org.michaelbel.movies.main -import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.flowOf @@ -13,7 +12,7 @@ class MainViewModel: BaseViewModel() { val themeData: StateFlow = flowOf(ThemeData.Companion.Default.copy(appTheme = AppTheme.NightYes)) .stateIn( - scope = viewModelScope, + scope = scope, started = SharingStarted.Companion.Lazily, initialValue = ThemeData.Companion.Default ) diff --git a/feature/main-impl/build.gradle.kts b/feature/main-impl/build.gradle.kts index 8867b2740..1ca767c4b 100644 --- a/feature/main-impl/build.gradle.kts +++ b/feature/main-impl/build.gradle.kts @@ -4,6 +4,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi plugins { alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.kotlin.serialization) alias(libs.plugins.kotlin.compose) alias(libs.plugins.compose) alias(libs.plugins.android.library) diff --git a/feature/main-impl/src/androidMain/kotlin/org/michaelbel/movies/main/MainNavigationContent.kt b/feature/main-impl/src/androidMain/kotlin/org/michaelbel/movies/main/MainNavigationContent.kt index 986761187..47a684d11 100644 --- a/feature/main-impl/src/androidMain/kotlin/org/michaelbel/movies/main/MainNavigationContent.kt +++ b/feature/main-impl/src/androidMain/kotlin/org/michaelbel/movies/main/MainNavigationContent.kt @@ -1,6 +1,8 @@ package org.michaelbel.movies.main +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier import androidx.navigation.compose.NavHost import androidx.navigation.compose.rememberNavController import org.michaelbel.movies.account.accountGraph @@ -10,14 +12,13 @@ import org.michaelbel.movies.auth.ktx.navigateToAuth import org.michaelbel.movies.common.ThemeData import org.michaelbel.movies.details.detailsGraph import org.michaelbel.movies.details.navigateToDetails -import org.michaelbel.movies.feed.FeedDestination -import org.michaelbel.movies.feed.feedGraph import org.michaelbel.movies.gallery.galleryGraph import org.michaelbel.movies.gallery.navigateToGallery +import org.michaelbel.movies.main.navigation.MainDestination +import org.michaelbel.movies.main.navigation.mainGraph import org.michaelbel.movies.search.navigateToSearch import org.michaelbel.movies.search.searchGraph import org.michaelbel.movies.settings.navigateToSettings -import org.michaelbel.movies.settings.settingsGraph import org.michaelbel.movies.ui.theme.MoviesTheme @Composable @@ -25,9 +26,7 @@ fun MainNavigationContent( themeData: ThemeData, enableEdgeToEdge: (Any, Any) -> Unit ) { - val navHostController = rememberNavController().apply { - //addOnDestinationChangedListener(viewModel::analyticsTrackDestination) - } + val navHostController = rememberNavController() MoviesTheme( themeData = themeData, @@ -35,7 +34,8 @@ fun MainNavigationContent( ) { NavHost( navController = navHostController, - startDestination = FeedDestination() + startDestination = MainDestination, + modifier = Modifier.fillMaxSize() ) { authGraph( navigateBack = navHostController::popBackStack @@ -43,7 +43,7 @@ fun MainNavigationContent( accountGraph( navigateBack = navHostController::popBackStack ) - feedGraph( + mainGraph( navigateToSearch = navHostController::navigateToSearch, navigateToAuth = navHostController::navigateToAuth, navigateToAccount = navHostController::navigateToAccount, @@ -61,9 +61,6 @@ fun MainNavigationContent( navigateBack = navHostController::popBackStack, navigateToDetails = navHostController::navigateToDetails, ) - settingsGraph( - navigateBack = navHostController::popBackStack - ) } } } \ No newline at end of file diff --git a/feature/main-impl/src/androidMain/kotlin/org/michaelbel/movies/main/MainViewModel.kt b/feature/main-impl/src/androidMain/kotlin/org/michaelbel/movies/main/MainViewModel.kt index d008d4984..41b4b5555 100644 --- a/feature/main-impl/src/androidMain/kotlin/org/michaelbel/movies/main/MainViewModel.kt +++ b/feature/main-impl/src/androidMain/kotlin/org/michaelbel/movies/main/MainViewModel.kt @@ -38,10 +38,10 @@ class MainViewModel( private val configService: ConfigService ): BaseViewModel() { - private val _authenticateFlow = Channel(Channel.BUFFERED) + private val _authenticateFlow = Channel() val authenticateFlow: Flow get() = _authenticateFlow.receiveAsFlow() - private val _cancelFlow = Channel(Channel.BUFFERED) + private val _cancelFlow = Channel() val cancelFlow: Flow get() = _cancelFlow.receiveAsFlow() private val _splashLoading = MutableStateFlow(true) @@ -49,14 +49,14 @@ class MainViewModel( val themeData: StateFlow = interactor.themeData .stateIn( - scope = this, + scope = scope, started = SharingStarted.Lazily, initialValue = ThemeData.Default ) val isScreenshotBlockEnabled: StateFlow = interactor.isScreenshotBlockEnabled .stateIn( - scope = this, + scope = scope, started = SharingStarted.Lazily, initialValue = false ) @@ -85,13 +85,13 @@ class MainViewModel( } override fun onCancel() { - launch { _cancelFlow.send(Unit) } + scope.launch { _cancelFlow.send(Unit) } } } biometricController.authenticate(activity, biometricListener) } - private fun fetchBiometric() = launch { + private fun fetchBiometric() = scope.launch { val isBiometricEnabled = interactor.isBiometricEnabledAsync() _splashLoading.value = isBiometricEnabled if (isBiometricEnabled) { @@ -99,7 +99,7 @@ class MainViewModel( } } - private fun fetchRemoteConfig() = launch { + private fun fetchRemoteConfig() = scope.launch { configService.fetchAndActivate() } diff --git a/feature/main-impl/src/androidMain/kotlin/org/michaelbel/movies/main/di/MainKoinModule.android.kt b/feature/main-impl/src/androidMain/kotlin/org/michaelbel/movies/main/di/MainKoinModule.android.kt index 2971ba316..3fdf00bed 100644 --- a/feature/main-impl/src/androidMain/kotlin/org/michaelbel/movies/main/di/MainKoinModule.android.kt +++ b/feature/main-impl/src/androidMain/kotlin/org/michaelbel/movies/main/di/MainKoinModule.android.kt @@ -1,12 +1,14 @@ package org.michaelbel.movies.main.di -import org.koin.androidx.viewmodel.dsl.viewModelOf +import org.koin.core.module.dsl.viewModel +import org.koin.core.module.dsl.viewModelOf import org.koin.dsl.module import org.michaelbel.movies.analytics.di.moviesAnalyticsKoinModule import org.michaelbel.movies.common.biometric.di.biometricKoinModule import org.michaelbel.movies.debug.di.debugNotificationClientKoinModule import org.michaelbel.movies.interactor.di.interactorKoinModule import org.michaelbel.movies.main.MainViewModel +import org.michaelbel.movies.main.navigation.MainNavViewModel import org.michaelbel.movies.platform.inject.flavorServiceKtorModule import org.michaelbel.movies.work.di.workKoinModule @@ -20,4 +22,5 @@ actual val mainKoinModule = module { debugNotificationClientKoinModule ) viewModelOf(::MainViewModel) + viewModel { MainNavViewModel(get(), get()) } } \ No newline at end of file diff --git a/feature/main-impl/src/androidMain/kotlin/org/michaelbel/movies/main/navigation/MainDestination.kt b/feature/main-impl/src/androidMain/kotlin/org/michaelbel/movies/main/navigation/MainDestination.kt new file mode 100644 index 000000000..2cba3fc00 --- /dev/null +++ b/feature/main-impl/src/androidMain/kotlin/org/michaelbel/movies/main/navigation/MainDestination.kt @@ -0,0 +1,6 @@ +package org.michaelbel.movies.main.navigation + +import kotlinx.serialization.Serializable + +@Serializable +object MainDestination \ No newline at end of file diff --git a/feature/main-impl/src/androidMain/kotlin/org/michaelbel/movies/main/navigation/MainNavViewModel.kt b/feature/main-impl/src/androidMain/kotlin/org/michaelbel/movies/main/navigation/MainNavViewModel.kt new file mode 100644 index 000000000..9ec64c3da --- /dev/null +++ b/feature/main-impl/src/androidMain/kotlin/org/michaelbel/movies/main/navigation/MainNavViewModel.kt @@ -0,0 +1,50 @@ +package org.michaelbel.movies.main.navigation + +import androidx.lifecycle.SavedStateHandle +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.launch +import org.michaelbel.movies.common.exceptions.AccountDetailsException +import org.michaelbel.movies.common.exceptions.CreateSessionException +import org.michaelbel.movies.common.viewmodel.BaseViewModel +import org.michaelbel.movies.interactor.Interactor + +class MainNavViewModel( + savedStateHandle: SavedStateHandle, + private val interactor: Interactor +): BaseViewModel() { + + private val requestToken: String? = savedStateHandle["requestToken"] + private val approved: String? = savedStateHandle["approved"] + + private val _snackbarMessage = Channel() + val snackbarMessage: Flow = _snackbarMessage.receiveAsFlow() + + init { + authorizeAccount(requestToken, approved.toBoolean()) + } + + override fun handleError(throwable: Throwable) { + when (throwable) { + is CreateSessionException -> { + scope.launch { _snackbarMessage.send("Failure while signing in. Wrong token or no approval") } + } + is AccountDetailsException -> { + scope.launch { _snackbarMessage.send("Failure while signing in. Wrong token or no approval") } + } + else -> super.handleError(throwable) + } + } + + private fun authorizeAccount(requestToken: String?, approved: Boolean?) { + if (requestToken == null || approved == null) return + scope.launch { + interactor.run { + createSession(requestToken) + accountDetails() + _snackbarMessage.send("Successful authorization") + } + } + } +} \ No newline at end of file diff --git a/feature/main-impl/src/androidMain/kotlin/org/michaelbel/movies/main/navigation/MainNavigation.android.kt b/feature/main-impl/src/androidMain/kotlin/org/michaelbel/movies/main/navigation/MainNavigation.android.kt new file mode 100644 index 000000000..a8ba94d50 --- /dev/null +++ b/feature/main-impl/src/androidMain/kotlin/org/michaelbel/movies/main/navigation/MainNavigation.android.kt @@ -0,0 +1,138 @@ +package org.michaelbel.movies.main.navigation + +import androidx.compose.foundation.layout.calculateEndPadding +import androidx.compose.foundation.layout.calculateStartPadding +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.unit.dp +import androidx.navigation.NavGraphBuilder +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import androidx.navigation.navDeepLink +import kotlinx.coroutines.launch +import org.koin.androidx.compose.koinViewModel +import org.michaelbel.movies.feed.FeedDestination +import org.michaelbel.movies.feed.feedGraph +import org.michaelbel.movies.settings.SettingsDestination +import org.michaelbel.movies.settings.settingsGraph +import org.michaelbel.movies.ui.icons.MoviesIcons +import org.michaelbel.movies.ui.ktx.ObserveAsEvents + +fun NavGraphBuilder.mainGraph( + navigateToSearch: () -> Unit, + navigateToAuth: () -> Unit, + navigateToAccount: () -> Unit, + navigateToSettings: () -> Unit, + navigateToDetails: (String, Int) -> Unit +) { + composable( + deepLinks = listOf( + navDeepLink { uriPattern = "movies://redirect_url?request_token={requestToken}&approved={approved}" } + ) + ) { + val viewModel: MainNavViewModel = koinViewModel() + + val navHostController = rememberNavController() + val layoutDirection = LocalLayoutDirection.current + var selectedTab: Any by remember { mutableStateOf(FeedDestination()) } + val scope = rememberCoroutineScope() + val snackbarHostState = remember { SnackbarHostState() } + + Scaffold( + modifier = Modifier.fillMaxSize(), + bottomBar = { + NavigationBar( + modifier = Modifier, + containerColor = MaterialTheme.colorScheme.inversePrimary + ) { + NavigationBarItem( + selected = selectedTab is FeedDestination, + onClick = { selectedTab = FeedDestination() }, + icon = { + Icon( + imageVector = MoviesIcons.GridView, + contentDescription = null + ) + }, + label = { + Text( + text = "Feed" + ) + } + ) + + NavigationBarItem( + selected = selectedTab is SettingsDestination, + onClick = { selectedTab = SettingsDestination }, + icon = { + Icon( + imageVector = MoviesIcons.Settings, + contentDescription = null + ) + }, + label = { + Text( + text = "Settings" + ) + } + ) + } + }, + snackbarHost = { + SnackbarHost( + hostState = snackbarHostState + ) + } + ) { innerPadding -> + NavHost( + navController = navHostController, + startDestination = selectedTab, + modifier = Modifier.padding( + start = innerPadding.calculateStartPadding(layoutDirection), + top = 0.dp, + end = innerPadding.calculateEndPadding(layoutDirection), + bottom = innerPadding.calculateBottomPadding() + ) + ) { + feedGraph( + navigateToSearch = navigateToSearch, + navigateToAuth = navigateToAuth, + navigateToAccount = navigateToAccount, + navigateToSettings = navigateToSettings, + navigateToDetails = navigateToDetails + ) + settingsGraph( + navigateBack = {} + ) + } + } + + ObserveAsEvents( + flow = viewModel.snackbarMessage, + key1 = snackbarHostState + ) { message -> + scope.launch { + snackbarHostState.run { + currentSnackbarData?.dismiss() + showSnackbar(message) + } + } + } + } +} \ No newline at end of file diff --git a/feature/main-impl/src/iosMain/kotlin/org/michaelbel/movies/main/MainViewModel.kt b/feature/main-impl/src/iosMain/kotlin/org/michaelbel/movies/main/MainViewModel.kt index 4d6aa23cd..78ed3b120 100644 --- a/feature/main-impl/src/iosMain/kotlin/org/michaelbel/movies/main/MainViewModel.kt +++ b/feature/main-impl/src/iosMain/kotlin/org/michaelbel/movies/main/MainViewModel.kt @@ -1,6 +1,5 @@ package org.michaelbel.movies.main -import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.stateIn @@ -14,7 +13,7 @@ class MainViewModel( val themeData: StateFlow = interactor.themeData .stateIn( - scope = viewModelScope, + scope = scope, started = SharingStarted.Lazily, initialValue = ThemeData.Default ) diff --git a/feature/main-impl/src/jvmMain/kotlin/org/michaelbel/movies/main/MainViewModel.kt b/feature/main-impl/src/jvmMain/kotlin/org/michaelbel/movies/main/MainViewModel.kt index 4d6aa23cd..78ed3b120 100644 --- a/feature/main-impl/src/jvmMain/kotlin/org/michaelbel/movies/main/MainViewModel.kt +++ b/feature/main-impl/src/jvmMain/kotlin/org/michaelbel/movies/main/MainViewModel.kt @@ -1,6 +1,5 @@ package org.michaelbel.movies.main -import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.stateIn @@ -14,7 +13,7 @@ class MainViewModel( val themeData: StateFlow = interactor.themeData .stateIn( - scope = viewModelScope, + scope = scope, started = SharingStarted.Lazily, initialValue = ThemeData.Default ) diff --git a/feature/main/build.gradle.kts b/feature/main/build.gradle.kts new file mode 100644 index 000000000..e7e5323f1 --- /dev/null +++ b/feature/main/build.gradle.kts @@ -0,0 +1,44 @@ +@file:OptIn(ExperimentalKotlinGradlePluginApi::class) + +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi + +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.kotlin.compose) + alias(libs.plugins.compose) + alias(libs.plugins.android.library) +} + +kotlin { + androidTarget() + jvm() + iosX64() + iosArm64() + iosSimulatorArm64() + + sourceSets { + commonMain.dependencies { + api(project(":feature:main-impl")) + } + } + + compilerOptions { + jvmToolchain(libs.versions.jdk.get().toInt()) + } +} + +android { + namespace = "org.michaelbel.movies.main" + flavorDimensions += "version" + + defaultConfig { + minSdk = libs.versions.min.sdk.get().toInt() + compileSdk = libs.versions.compile.sdk.get().toInt() + } + + buildFeatures { + buildConfig = true + compose = true + } +} \ No newline at end of file diff --git a/feature/search-impl/src/androidMain/kotlin/org/michaelbel/movies/search/SearchViewModel.kt b/feature/search-impl/src/androidMain/kotlin/org/michaelbel/movies/search/SearchViewModel.kt index b00ddfc6b..3702737da 100644 --- a/feature/search-impl/src/androidMain/kotlin/org/michaelbel/movies/search/SearchViewModel.kt +++ b/feature/search-impl/src/androidMain/kotlin/org/michaelbel/movies/search/SearchViewModel.kt @@ -34,28 +34,28 @@ class SearchViewModel( val networkStatus: StateFlow = networkManager.status .stateIn( - scope = this, + scope = scope, started = SharingStarted.Lazily, initialValue = NetworkStatus.Unavailable ) val currentFeedView: StateFlow = interactor.currentFeedView .stateIn( - scope = this, + scope = scope, started = SharingStarted.Lazily, initialValue = runBlocking { interactor.currentFeedView.first() } ) val suggestionsFlow: StateFlow> = interactor.suggestions() .stateIn( - scope = this, + scope = scope, started = SharingStarted.Lazily, initialValue = emptyList() ) val searchHistoryMoviesFlow: StateFlow> = interactor.moviesFlow(MoviePojo.MOVIES_SEARCH_HISTORY, Int.MAX_VALUE) .stateIn( - scope = this, + scope = scope, started = SharingStarted.Lazily, initialValue = emptyList() ) @@ -67,7 +67,7 @@ class SearchViewModel( val pagingDataFlow: Flow> = query .flatMapLatest(movieInteractor::moviesPagingData) - .cachedIn(this) + .cachedIn(scope) init { loadSuggestions() @@ -81,20 +81,20 @@ class SearchViewModel( interactor.setSearchActive(state) } - fun onSaveToHistory(movieId: MovieId) = launch { + fun onSaveToHistory(movieId: MovieId) = scope.launch { val movie = interactor.movie(query.value, movieId) interactor.insertMovie(MoviePojo.MOVIES_SEARCH_HISTORY, movie) } - fun onRemoveFromHistory(movieId: MovieId) = launch { + fun onRemoveFromHistory(movieId: MovieId) = scope.launch { interactor.removeMovie(MoviePojo.MOVIES_SEARCH_HISTORY, movieId) } - fun onClearSearchHistory() = launch { + fun onClearSearchHistory() = scope.launch { interactor.removeMovies(MoviePojo.MOVIES_SEARCH_HISTORY) } - private fun loadSuggestions() = launch { + private fun loadSuggestions() = scope.launch { interactor.updateSuggestions() } } \ No newline at end of file diff --git a/feature/search-impl/src/androidMain/kotlin/org/michaelbel/movies/search/ui/SearchScreenContent.kt b/feature/search-impl/src/androidMain/kotlin/org/michaelbel/movies/search/ui/SearchScreenContent.kt index 18f6b1cdc..9fa8f8d39 100644 --- a/feature/search-impl/src/androidMain/kotlin/org/michaelbel/movies/search/ui/SearchScreenContent.kt +++ b/feature/search-impl/src/androidMain/kotlin/org/michaelbel/movies/search/ui/SearchScreenContent.kt @@ -1,5 +1,6 @@ package org.michaelbel.movies.search.ui +import android.os.Build import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues @@ -47,6 +48,7 @@ import org.michaelbel.movies.ui.ktx.displayCutoutWindowInsets import org.michaelbel.movies.ui.ktx.isFailure import org.michaelbel.movies.ui.ktx.isLoading import org.michaelbel.movies.ui.ktx.refreshThrowable +import org.michaelbel.movies.ui.ktx.rememberConnectivityClickHandler import java.net.UnknownHostException import org.michaelbel.movies.ui.R as UiR @@ -158,7 +160,9 @@ internal fun SearchScreenContent( .padding(innerPadding) .windowInsetsPadding(displayCutoutWindowInsets) .fillMaxSize() - .clickableWithoutRipple(pagingItems::retry) + .clickableWithoutRipple(pagingItems::retry), + isButtonVisible = Build.VERSION.SDK_INT >= 29, + onButtonClick = rememberConnectivityClickHandler() ) } } diff --git a/feature/search-impl/src/androidMain/kotlin/org/michaelbel/movies/search/ui/SearchToolbar.kt b/feature/search-impl/src/androidMain/kotlin/org/michaelbel/movies/search/ui/SearchToolbar.kt index 806c6671a..6fbfd02b3 100644 --- a/feature/search-impl/src/androidMain/kotlin/org/michaelbel/movies/search/ui/SearchToolbar.kt +++ b/feature/search-impl/src/androidMain/kotlin/org/michaelbel/movies/search/ui/SearchToolbar.kt @@ -31,6 +31,7 @@ import org.michaelbel.movies.search_impl.R import org.michaelbel.movies.ui.compose.iconbutton.BackIcon import org.michaelbel.movies.ui.compose.iconbutton.CloseIcon import org.michaelbel.movies.ui.compose.iconbutton.VoiceIcon +import org.michaelbel.movies.ui.ktx.rememberSpeechRecognitionLauncher import org.michaelbel.movies.ui.preview.SuggestionDbPreviewParameterProvider import org.michaelbel.movies.ui.theme.MoviesTheme @@ -74,7 +75,7 @@ internal fun SearchToolbar( ) } else { VoiceIcon( - onInputText = onInputText + onClick = rememberSpeechRecognitionLauncher(onInputText) ) } }, diff --git a/feature/search-impl/src/iosMain/kotlin/org/michaelbel/movies/search/SearchViewModel.kt b/feature/search-impl/src/iosMain/kotlin/org/michaelbel/movies/search/SearchViewModel.kt index 93db7ebe9..8a0521b8f 100644 --- a/feature/search-impl/src/iosMain/kotlin/org/michaelbel/movies/search/SearchViewModel.kt +++ b/feature/search-impl/src/iosMain/kotlin/org/michaelbel/movies/search/SearchViewModel.kt @@ -5,6 +5,4 @@ import org.michaelbel.movies.interactor.Interactor class SearchViewModel( private val interactor: Interactor -): BaseViewModel() { - -} \ No newline at end of file +): BaseViewModel() \ No newline at end of file diff --git a/feature/search-impl/src/jvmMain/kotlin/org/michaelbel/movies/search/SearchViewModel.kt b/feature/search-impl/src/jvmMain/kotlin/org/michaelbel/movies/search/SearchViewModel.kt index 93db7ebe9..8a0521b8f 100644 --- a/feature/search-impl/src/jvmMain/kotlin/org/michaelbel/movies/search/SearchViewModel.kt +++ b/feature/search-impl/src/jvmMain/kotlin/org/michaelbel/movies/search/SearchViewModel.kt @@ -5,6 +5,4 @@ import org.michaelbel.movies.interactor.Interactor class SearchViewModel( private val interactor: Interactor -): BaseViewModel() { - -} \ No newline at end of file +): BaseViewModel() \ No newline at end of file diff --git a/feature/search/src/androidMain/kotlin/org/michaelbel/movies/search/SearchNavigation.android.kt b/feature/search/src/androidMain/kotlin/org/michaelbel/movies/search/SearchNavigation.android.kt index 61eab1c5d..020c9ddad 100644 --- a/feature/search/src/androidMain/kotlin/org/michaelbel/movies/search/SearchNavigation.android.kt +++ b/feature/search/src/androidMain/kotlin/org/michaelbel/movies/search/SearchNavigation.android.kt @@ -1,6 +1,5 @@ package org.michaelbel.movies.search -import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable import androidx.navigation.navDeepLink @@ -9,10 +8,6 @@ import org.michaelbel.movies.persistence.database.typealiases.PagingKey import org.michaelbel.movies.search.ui.SearchRoute import org.michaelbel.movies.ui.shortcuts.INTENT_ACTION_SEARCH -fun NavController.navigateToSearch() { - navigate(SearchDestination) -} - fun NavGraphBuilder.searchGraph( navigateBack: () -> Unit, navigateToDetails: (PagingKey, MovieId) -> Unit, diff --git a/feature/search/src/commonMain/kotlin/org/michaelbel/movies/search/SearchNavigation.kt b/feature/search/src/commonMain/kotlin/org/michaelbel/movies/search/SearchNavigation.kt new file mode 100644 index 000000000..990f8ed44 --- /dev/null +++ b/feature/search/src/commonMain/kotlin/org/michaelbel/movies/search/SearchNavigation.kt @@ -0,0 +1,7 @@ +package org.michaelbel.movies.search + +import androidx.navigation.NavController + +fun NavController.navigateToSearch() { + navigate(SearchDestination) +} \ No newline at end of file diff --git a/feature/search/src/iosMain/kotlin/org/michaelbel/movies/search/SearchNavigation.ios.kt b/feature/search/src/iosMain/kotlin/org/michaelbel/movies/search/SearchNavigation.ios.kt index dcbc39e38..8211adcbf 100644 --- a/feature/search/src/iosMain/kotlin/org/michaelbel/movies/search/SearchNavigation.ios.kt +++ b/feature/search/src/iosMain/kotlin/org/michaelbel/movies/search/SearchNavigation.ios.kt @@ -1,14 +1,9 @@ package org.michaelbel.movies.search -import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable import org.michaelbel.movies.search.ui.SearchRoute -fun NavController.navigateToSearch() { - navigate(SearchDestination) -} - fun NavGraphBuilder.searchGraph( navigateBack: () -> Unit, navigateToDetails: (String, Int) -> Unit, diff --git a/feature/search/src/jvmMain/kotlin/org/michaelbel/movies/search/SearchNavigation.desktop.kt b/feature/search/src/jvmMain/kotlin/org/michaelbel/movies/search/SearchNavigation.desktop.kt index dcbc39e38..8211adcbf 100644 --- a/feature/search/src/jvmMain/kotlin/org/michaelbel/movies/search/SearchNavigation.desktop.kt +++ b/feature/search/src/jvmMain/kotlin/org/michaelbel/movies/search/SearchNavigation.desktop.kt @@ -1,14 +1,9 @@ package org.michaelbel.movies.search -import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable import org.michaelbel.movies.search.ui.SearchRoute -fun NavController.navigateToSearch() { - navigate(SearchDestination) -} - fun NavGraphBuilder.searchGraph( navigateBack: () -> Unit, navigateToDetails: (String, Int) -> Unit, diff --git a/feature/settings-impl/build.gradle.kts b/feature/settings-impl/build.gradle.kts index fee923e81..4033c0396 100644 --- a/feature/settings-impl/build.gradle.kts +++ b/feature/settings-impl/build.gradle.kts @@ -22,6 +22,7 @@ kotlin { api(project(":core:interactor")) api(project(":core:widget")) api(project(":core:platform-services:interactor")) + implementation(project(":core:notifications")) } } diff --git a/feature/settings-impl/src/androidMain/kotlin/org/michaelbel/movies/settings/di/SettingsKoinModule.android.kt b/feature/settings-impl/src/androidMain/kotlin/org/michaelbel/movies/settings/di/SettingsKoinModule.android.kt deleted file mode 100644 index f1cfd1b7b..000000000 --- a/feature/settings-impl/src/androidMain/kotlin/org/michaelbel/movies/settings/di/SettingsKoinModule.android.kt +++ /dev/null @@ -1,17 +0,0 @@ -package org.michaelbel.movies.settings.di - -import org.koin.androidx.viewmodel.dsl.viewModel -import org.koin.dsl.module -import org.michaelbel.movies.common.biometric.di.biometricKoinModule -import org.michaelbel.movies.interactor.di.interactorKoinModule -import org.michaelbel.movies.network.connectivity.di.networkManagerKoinModule -import org.michaelbel.movies.settings.SettingsViewModel - -actual val settingsKoinModule = module { - includes( - biometricKoinModule, - interactorKoinModule, - networkManagerKoinModule - ) - viewModel { SettingsViewModel(get(), get(), get(), get(), get()) } -} \ No newline at end of file diff --git a/feature/settings-impl/src/androidMain/kotlin/org/michaelbel/movies/settings/ktx/TileKtx.kt b/feature/settings-impl/src/androidMain/kotlin/org/michaelbel/movies/settings/ktx/TileKtx.kt new file mode 100644 index 000000000..95d4a4fcc --- /dev/null +++ b/feature/settings-impl/src/androidMain/kotlin/org/michaelbel/movies/settings/ktx/TileKtx.kt @@ -0,0 +1,37 @@ +package org.michaelbel.movies.settings.ktx + +import android.app.StatusBarManager +import android.content.ComponentName +import android.graphics.drawable.Icon +import android.os.Build +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext +import androidx.core.content.ContextCompat +import org.jetbrains.compose.resources.stringResource +import org.michaelbel.movies.ui.icons.MoviesAndroidIcons +import org.michaelbel.movies.ui.strings.MoviesStrings +import org.michaelbel.movies.ui.tile.MoviesTileService + +@Composable +fun requestTileService(onSnackbarShow: (String) -> Unit): () -> Unit { + val context = LocalContext.current + val tileTitleLabel = stringResource(MoviesStrings.tile_title) + val tileMessage = stringResource(MoviesStrings.settings_tile_error_already_added) + return { + if (Build.VERSION.SDK_INT >= 33) { + val statusBarManager = ContextCompat.getSystemService(context, StatusBarManager::class.java) + statusBarManager?.requestAddTileService( + ComponentName(context, MoviesTileService::class.java), + tileTitleLabel, + Icon.createWithResource(context, MoviesAndroidIcons.MovieFilter24), + context.mainExecutor + ) { result -> + when (result) { + StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ALREADY_ADDED -> { + onSnackbarShow(tileMessage) + } + } + } + } + } +} \ No newline at end of file diff --git a/feature/settings-impl/src/androidMain/kotlin/org/michaelbel/movies/settings/ui/SettingsRoute.android.kt b/feature/settings-impl/src/androidMain/kotlin/org/michaelbel/movies/settings/ui/SettingsRoute.android.kt index 274a5bb21..34af45220 100644 --- a/feature/settings-impl/src/androidMain/kotlin/org/michaelbel/movies/settings/ui/SettingsRoute.android.kt +++ b/feature/settings-impl/src/androidMain/kotlin/org/michaelbel/movies/settings/ui/SettingsRoute.android.kt @@ -1,11 +1,6 @@ package org.michaelbel.movies.settings.ui -import android.Manifest import android.app.Activity -import android.app.StatusBarManager -import android.appwidget.AppWidgetManager -import android.content.ComponentName -import android.graphics.drawable.Icon import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.material3.MaterialTheme @@ -21,7 +16,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext -import androidx.core.content.ContextCompat import kotlinx.coroutines.launch import org.jetbrains.compose.resources.stringResource import org.koin.androidx.compose.koinViewModel @@ -30,10 +24,12 @@ import org.michaelbel.movies.common.browser.openUrl import org.michaelbel.movies.common.gender.GrammaticalGender import org.michaelbel.movies.common.ktx.notificationManager import org.michaelbel.movies.interactor.entity.AppLanguage +import org.michaelbel.movies.notifications.ktx.rememberPostNotificationsPermissionHandler import org.michaelbel.movies.settings.SettingsViewModel import org.michaelbel.movies.settings.ktx.currentGrammaticalGender import org.michaelbel.movies.settings.ktx.iconSnackbarTextRes import org.michaelbel.movies.settings.ktx.isDebug +import org.michaelbel.movies.settings.ktx.requestTileService import org.michaelbel.movies.settings.ktx.supportSetRequestedApplicationGrammaticalGender import org.michaelbel.movies.settings.ktx.versionCode import org.michaelbel.movies.settings.ktx.versionName @@ -57,14 +53,12 @@ import org.michaelbel.movies.settings.model.isWidgetFeatureEnabled import org.michaelbel.movies.ui.appicon.IconAlias import org.michaelbel.movies.ui.appicon.enabledIcon import org.michaelbel.movies.ui.appicon.setIcon -import org.michaelbel.movies.ui.icons.MoviesAndroidIcons import org.michaelbel.movies.ui.ktx.appNotificationSettingsIntent import org.michaelbel.movies.ui.ktx.collectAsStateCommon import org.michaelbel.movies.ui.ktx.displayCutoutWindowInsets import org.michaelbel.movies.ui.lifecycle.OnResume import org.michaelbel.movies.ui.strings.MoviesStrings -import org.michaelbel.movies.ui.tile.MoviesTileService -import org.michaelbel.movies.widget.ktx.pin +import org.michaelbel.movies.widget.ktx.rememberAndPinAppWidgetProvider @Composable fun SettingsRoute( @@ -84,11 +78,6 @@ fun SettingsRoute( val context = LocalContext.current val resultContract = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {} val toolbarColor = MaterialTheme.colorScheme.primary.toArgb() - val tileTitleLabel = stringResource(MoviesStrings.tile_title) - val tileMessage = stringResource(MoviesStrings.settings_tile_error_already_added) - - val appWidgetManager by remember { mutableStateOf(AppWidgetManager.getInstance(context)) } - val appWidgetProvider by remember { mutableStateOf(appWidgetManager.getInstalledProvidersForPackage(context.packageName, null).first()) } val notificationManager by remember { mutableStateOf(context.notificationManager) } var areNotificationsEnabled by remember { mutableStateOf(notificationManager.areNotificationsEnabled()) } @@ -110,19 +99,6 @@ fun SettingsRoute( } } - val postNotificationsPermission = rememberLauncherForActivityResult( - ActivityResultContracts.RequestPermission() - ) { granted -> - if (granted) { - areNotificationsEnabled = notificationManager.areNotificationsEnabled() - } else { - val shouldRequest = (context as Activity).shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS) - if (!shouldRequest) { - onShowPermissionSnackbar() - } - } - } - OnResume { areNotificationsEnabled = notificationManager.areNotificationsEnabled() } @@ -194,16 +170,11 @@ fun SettingsRoute( notificationsData = SettingsData.NotificationsData( isFeatureEnabled = isNotificationsFeatureEnabled, isEnabled = areNotificationsEnabled, - onClick = { - if (areNotificationsEnabled) { - resultContract.launch(context.appNotificationSettingsIntent) - } else { - postNotificationsPermission.launch(Manifest.permission.POST_NOTIFICATIONS) - } - }, - onNavigateToAppNotificationSettings = { - resultContract.launch(context.appNotificationSettingsIntent) - } + onClick = rememberPostNotificationsPermissionHandler( + areNotificationsEnabled = areNotificationsEnabled, + onPermissionGranted = { areNotificationsEnabled = notificationManager.areNotificationsEnabled() }, + onPermissionDenied = onShowPermissionSnackbar + ) ), biometricData = SettingsData.ChangedData( isFeatureEnabled = isBiometricFeatureEnabled && isBiometricFeatureAvailable, @@ -212,25 +183,11 @@ fun SettingsRoute( ), widgetData = SettingsData.RequestedData( isFeatureEnabled = isWidgetFeatureEnabled, - onRequest = { appWidgetProvider.pin(context) } + onRequest = rememberAndPinAppWidgetProvider() ), tileData = SettingsData.RequestedData( isFeatureEnabled = isTileFeatureEnabled, - onRequest = { - val statusBarManager = ContextCompat.getSystemService(context, StatusBarManager::class.java) - statusBarManager?.requestAddTileService( - ComponentName(context, MoviesTileService::class.java), - tileTitleLabel, - Icon.createWithResource(context, MoviesAndroidIcons.MovieFilter24), - context.mainExecutor - ) { result -> - when (result) { - StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ALREADY_ADDED -> { - onShowSnackbar(tileMessage) - } - } - } - } + onRequest = requestTileService(onShowSnackbar) ), appIconData = SettingsData.ListData( isFeatureEnabled = isAppIconFeatureEnabled, @@ -273,6 +230,7 @@ fun SettingsRoute( ), windowInsets = displayCutoutWindowInsets, snackbarHostState = snackbarHostState, + isNavigationIconVisible = false, modifier = modifier ) } \ No newline at end of file diff --git a/feature/settings-impl/src/androidMain/kotlin/org/michaelbel/movies/settings/SettingsViewModel.kt b/feature/settings-impl/src/commonMain/kotlin/org/michaelbel/movies/settings/SettingsViewModel.kt similarity index 80% rename from feature/settings-impl/src/androidMain/kotlin/org/michaelbel/movies/settings/SettingsViewModel.kt rename to feature/settings-impl/src/commonMain/kotlin/org/michaelbel/movies/settings/SettingsViewModel.kt index db98a0980..325c2a582 100644 --- a/feature/settings-impl/src/androidMain/kotlin/org/michaelbel/movies/settings/SettingsViewModel.kt +++ b/feature/settings-impl/src/commonMain/kotlin/org/michaelbel/movies/settings/SettingsViewModel.kt @@ -1,6 +1,5 @@ package org.michaelbel.movies.settings -import android.app.Activity import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue @@ -12,7 +11,7 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import org.michaelbel.movies.common.ThemeData import org.michaelbel.movies.common.appearance.FeedView -import org.michaelbel.movies.common.biometric.BiometricController +import org.michaelbel.movies.common.biometric.BiometricController2 import org.michaelbel.movies.common.list.MovieList import org.michaelbel.movies.common.theme.AppTheme import org.michaelbel.movies.common.version.AppVersionData @@ -24,10 +23,9 @@ import org.michaelbel.movies.platform.app.AppService import org.michaelbel.movies.platform.review.ReviewService import org.michaelbel.movies.platform.update.UpdateListener import org.michaelbel.movies.platform.update.UpdateService -import org.michaelbel.movies.settings_impl.BuildConfig class SettingsViewModel( - biometricController: BiometricController, + biometricController: BiometricController2, private val interactor: Interactor, private val reviewService: ReviewService, private val updateService: UpdateService, @@ -40,49 +38,49 @@ class SettingsViewModel( val themeData: StateFlow = interactor.themeData .stateIn( - scope = this, + scope = scope, started = SharingStarted.Lazily, initialValue = ThemeData.Default ) val currentFeedView: StateFlow = interactor.currentFeedView .stateIn( - scope = this, + scope = scope, started = SharingStarted.Lazily, initialValue = FeedView.FeedList ) val currentMovieList: StateFlow = interactor.currentMovieList .stateIn( - scope = this, + scope = scope, started = SharingStarted.Lazily, initialValue = MovieList.NowPlaying() ) val isBiometricFeatureEnabled: StateFlow = biometricController.isBiometricAvailable .stateIn( - scope = this, + scope = scope, started = SharingStarted.Lazily, initialValue = false ) val isBiometricEnabled: StateFlow = interactor.isBiometricEnabled .stateIn( - scope = this, + scope = scope, started = SharingStarted.Lazily, initialValue = false ) val isScreenshotBlockEnabled: StateFlow = interactor.isScreenshotBlockEnabled .stateIn( - scope = this, + scope = scope, started = SharingStarted.Lazily, initialValue = false ) val appVersionData: StateFlow = flowOf(AppVersionData(appService.flavor.name)) .stateIn( - scope = this, + scope = scope, started = SharingStarted.Lazily, initialValue = AppVersionData.Empty ) @@ -93,52 +91,52 @@ class SettingsViewModel( fetchUpdateAvailable() } - fun selectLanguage(language: AppLanguage) = launch { + fun selectLanguage(language: AppLanguage) = scope.launch { interactor.selectLanguage(language) } - fun selectTheme(theme: AppTheme) = launch { + fun selectTheme(theme: AppTheme) = scope.launch { interactor.selectTheme(theme) } - fun selectFeedView(feedView: FeedView) = launch { + fun selectFeedView(feedView: FeedView) = scope.launch { interactor.selectFeedView(feedView) } - fun selectMovieList(movieList: MovieList) = launch { + fun selectMovieList(movieList: MovieList) = scope.launch { interactor.selectMovieList(movieList) } - fun setDynamicColors(value: Boolean) = launch { + fun setDynamicColors(value: Boolean) = scope.launch { interactor.setDynamicColors(value) } - fun setPaletteKey(paletteKey: Int) = launch { + fun setPaletteKey(paletteKey: Int) = scope.launch { interactor.setPaletteKey(paletteKey) } - fun setSeedColor(seedColor: Int) = launch { + fun setSeedColor(seedColor: Int) = scope.launch { interactor.setSeedColor(seedColor) } - fun setBiometricEnabled(enabled: Boolean) = launch { + fun setBiometricEnabled(enabled: Boolean) = scope.launch { interactor.setBiometricEnabled(enabled) } - fun setScreenshotBlockEnabled(enabled: Boolean) = launch { + fun setScreenshotBlockEnabled(enabled: Boolean) = scope.launch { interactor.setScreenshotBlockEnabled(enabled) } - fun requestReview(activity: Activity) { + fun requestReview(activity: Any) { reviewService.requestReview(activity) } - fun requestUpdate(activity: Activity) { + fun requestUpdate(activity: Any) { updateService.startUpdate(activity) } private fun fetchUpdateAvailable() { - isUpdateAvailable = BuildConfig.DEBUG + isUpdateAvailable = true updateService.setUpdateAvailableListener(object: UpdateListener { override fun onAvailable(result: Boolean) { isUpdateAvailable = result diff --git a/feature/settings-impl/src/commonMain/kotlin/org/michaelbel/movies/settings/di/SettingsKoinModule.kt b/feature/settings-impl/src/commonMain/kotlin/org/michaelbel/movies/settings/di/SettingsKoinModule.kt index 00de511b3..cfc238c94 100644 --- a/feature/settings-impl/src/commonMain/kotlin/org/michaelbel/movies/settings/di/SettingsKoinModule.kt +++ b/feature/settings-impl/src/commonMain/kotlin/org/michaelbel/movies/settings/di/SettingsKoinModule.kt @@ -1,5 +1,15 @@ package org.michaelbel.movies.settings.di -import org.koin.core.module.Module +import org.koin.core.module.dsl.viewModel +import org.koin.dsl.module +import org.michaelbel.movies.common.biometric.di.biometricKoinModule2 +import org.michaelbel.movies.interactor.di.interactorKoinModule +import org.michaelbel.movies.settings.SettingsViewModel -expect val settingsKoinModule: Module \ No newline at end of file +val settingsKoinModule = module { + includes( + biometricKoinModule2, + interactorKoinModule + ) + viewModel { SettingsViewModel(get(), get(), get(), get(), get()) } +} \ No newline at end of file diff --git a/feature/settings-impl/src/commonMain/kotlin/org/michaelbel/movies/settings/model/SettingsData.kt b/feature/settings-impl/src/commonMain/kotlin/org/michaelbel/movies/settings/model/SettingsData.kt index e960eed1b..39c288dab 100644 --- a/feature/settings-impl/src/commonMain/kotlin/org/michaelbel/movies/settings/model/SettingsData.kt +++ b/feature/settings-impl/src/commonMain/kotlin/org/michaelbel/movies/settings/model/SettingsData.kt @@ -48,18 +48,18 @@ data class SettingsData( data class ListData( override val isFeatureEnabled: Boolean, override val current: T, - override val onSelect: (T) -> Unit + override val onSelect: (T) -> Unit = {} ): Listed data class ChangedData( override val isFeatureEnabled: Boolean, - override val isEnabled: Boolean, - override val onChange: (Boolean) -> Unit + override val isEnabled: Boolean = false, + override val onChange: (Boolean) -> Unit = {} ): Changed data class RequestedData( override val isFeatureEnabled: Boolean, - override val onRequest: () -> Unit + override val onRequest: () -> Unit = {} ): Requested data class PaletteColorsData( @@ -73,8 +73,7 @@ data class SettingsData( data class NotificationsData( override val isFeatureEnabled: Boolean, val isEnabled: Boolean, - val onClick: () -> Unit, - val onNavigateToAppNotificationSettings: () -> Unit + val onClick: () -> Unit = {} ): Featured data class AboutData( diff --git a/feature/settings-impl/src/commonMain/kotlin/org/michaelbel/movies/settings/ui/SettingsScreenContent.kt b/feature/settings-impl/src/commonMain/kotlin/org/michaelbel/movies/settings/ui/SettingsScreenContent.kt index e11b97065..57642b832 100644 --- a/feature/settings-impl/src/commonMain/kotlin/org/michaelbel/movies/settings/ui/SettingsScreenContent.kt +++ b/feature/settings-impl/src/commonMain/kotlin/org/michaelbel/movies/settings/ui/SettingsScreenContent.kt @@ -52,6 +52,7 @@ internal fun SettingsScreenContent( settingsData: SettingsData, windowInsets: WindowInsets, snackbarHostState: SnackbarHostState, + isNavigationIconVisible: Boolean, modifier: Modifier = Modifier ) { val scope = rememberCoroutineScope() @@ -71,6 +72,7 @@ internal fun SettingsScreenContent( topBar = { SettingsToolbar( topAppBarScrollBehavior = topAppBarScrollBehavior, + isNavigationIconVisible = isNavigationIconVisible, onNavigationIconClick = settingsData.onBackClick, onClick = onScrollToTop ) @@ -79,9 +81,7 @@ internal fun SettingsScreenContent( if (settingsData.aboutData.isFeatureEnabled) { SettingsVersionBox( aboutData = settingsData.aboutData, - modifier = Modifier - .navigationBarsPadding() - .windowInsetsPadding(windowInsets) + modifier = Modifier.windowInsetsPadding(windowInsets) ) } }, diff --git a/feature/settings-impl/src/commonMain/kotlin/org/michaelbel/movies/settings/ui/SettingsToolbar.kt b/feature/settings-impl/src/commonMain/kotlin/org/michaelbel/movies/settings/ui/SettingsToolbar.kt index ca43bd937..3ec31ff28 100644 --- a/feature/settings-impl/src/commonMain/kotlin/org/michaelbel/movies/settings/ui/SettingsToolbar.kt +++ b/feature/settings-impl/src/commonMain/kotlin/org/michaelbel/movies/settings/ui/SettingsToolbar.kt @@ -23,6 +23,7 @@ import org.michaelbel.movies.ui.theme.MoviesTheme internal fun SettingsToolbar( modifier: Modifier = Modifier, topAppBarScrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(), + isNavigationIconVisible: Boolean, onNavigationIconClick: () -> Unit, onClick: () -> Unit, ) { @@ -34,11 +35,15 @@ internal fun SettingsToolbar( ) }, modifier = modifier.clickableWithoutRipple { onClick() }, - navigationIcon = { - BackIcon( - onClick = onNavigationIconClick, - modifier = Modifier.then(modifierDisplayCutoutWindowInsets) - ) + navigationIcon = if (isNavigationIconVisible) { + { + BackIcon( + onClick = onNavigationIconClick, + modifier = Modifier.then(modifierDisplayCutoutWindowInsets) + ) + } + } else { + {} }, colors = TopAppBarDefaults.topAppBarColors( containerColor = MaterialTheme.colorScheme.primaryContainer, @@ -54,6 +59,7 @@ private fun SettingsToolbarPreview() { MoviesTheme { SettingsToolbar( modifier = Modifier.statusBarsPadding(), + isNavigationIconVisible = true, onNavigationIconClick = {}, onClick = {} ) diff --git a/feature/settings-impl/src/iosMain/kotlin/org/michaelbel/movies/settings/SettingsViewModel.kt b/feature/settings-impl/src/iosMain/kotlin/org/michaelbel/movies/settings/SettingsViewModel.kt deleted file mode 100644 index d2cf33f51..000000000 --- a/feature/settings-impl/src/iosMain/kotlin/org/michaelbel/movies/settings/SettingsViewModel.kt +++ /dev/null @@ -1,70 +0,0 @@ -package org.michaelbel.movies.settings - -import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.launch -import org.michaelbel.movies.common.MOVIES_GITHUB_URL -import org.michaelbel.movies.common.ThemeData -import org.michaelbel.movies.common.appearance.FeedView -import org.michaelbel.movies.common.browser.openUrl -import org.michaelbel.movies.common.list.MovieList -import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.common.viewmodel.BaseViewModel -import org.michaelbel.movies.interactor.Interactor -import org.michaelbel.movies.interactor.entity.AppLanguage - -class SettingsViewModel( - private val interactor: Interactor -): BaseViewModel() { - - val themeData: StateFlow = interactor.themeData - .stateIn( - scope = viewModelScope, - started = SharingStarted.Lazily, - initialValue = ThemeData.Default - ) - - val currentFeedView: StateFlow = interactor.currentFeedView - .stateIn( - scope = viewModelScope, - started = SharingStarted.Lazily, - initialValue = FeedView.FeedList - ) - - val currentMovieList: StateFlow = interactor.currentMovieList - .stateIn( - scope = viewModelScope, - started = SharingStarted.Lazily, - initialValue = MovieList.NowPlaying() - ) - - fun selectLanguage(language: AppLanguage) = viewModelScope.launch { - interactor.selectLanguage(language) - } - - fun selectTheme(theme: AppTheme) = viewModelScope.launch { - interactor.selectTheme(theme) - } - - fun selectFeedView(feedView: FeedView) = viewModelScope.launch { - interactor.selectFeedView(feedView) - } - - fun selectMovieList(movieList: MovieList) = viewModelScope.launch { - interactor.selectMovieList(movieList) - } - - fun setPaletteKey(paletteKey: Int) = viewModelScope.launch { - interactor.setPaletteKey(paletteKey) - } - - fun setSeedColor(seedColor: Int) = viewModelScope.launch { - interactor.setSeedColor(seedColor) - } - - fun navigateToGithubUrl() { - openUrl(MOVIES_GITHUB_URL) - } -} \ No newline at end of file diff --git a/feature/settings-impl/src/iosMain/kotlin/org/michaelbel/movies/settings/di/SettingsKoinModule.ios.kt b/feature/settings-impl/src/iosMain/kotlin/org/michaelbel/movies/settings/di/SettingsKoinModule.ios.kt deleted file mode 100644 index c4ebb47df..000000000 --- a/feature/settings-impl/src/iosMain/kotlin/org/michaelbel/movies/settings/di/SettingsKoinModule.ios.kt +++ /dev/null @@ -1,12 +0,0 @@ -package org.michaelbel.movies.settings.di - -import org.koin.dsl.module -import org.michaelbel.movies.interactor.di.interactorKoinModule -import org.michaelbel.movies.settings.SettingsViewModel - -actual val settingsKoinModule = module { - includes( - interactorKoinModule - ) - single { SettingsViewModel(get()) } -} \ No newline at end of file diff --git a/feature/settings-impl/src/iosMain/kotlin/org/michaelbel/movies/settings/ui/SettingsRoute.ios.kt b/feature/settings-impl/src/iosMain/kotlin/org/michaelbel/movies/settings/ui/SettingsRoute.ios.kt index f6c0dfff9..52f8e9058 100644 --- a/feature/settings-impl/src/iosMain/kotlin/org/michaelbel/movies/settings/ui/SettingsRoute.ios.kt +++ b/feature/settings-impl/src/iosMain/kotlin/org/michaelbel/movies/settings/ui/SettingsRoute.ios.kt @@ -9,6 +9,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import org.jetbrains.compose.resources.stringResource import org.koin.compose.koinInject +import org.michaelbel.movies.common.MOVIES_GITHUB_URL +import org.michaelbel.movies.common.browser.openUrl import org.michaelbel.movies.common.gender.GrammaticalGender import org.michaelbel.movies.interactor.entity.AppLanguage import org.michaelbel.movies.settings.SettingsViewModel @@ -37,7 +39,7 @@ import org.michaelbel.movies.ui.strings.MoviesStrings fun SettingsRoute( onBackClick: () -> Unit, modifier: Modifier = Modifier, - viewModel: SettingsViewModel = koinInject() + viewModel: SettingsViewModel = koinInject() ) { val currentLanguage = AppLanguage.transform(stringResource(MoviesStrings.language_code)) val themeData by viewModel.themeData.collectAsStateCommon() @@ -70,13 +72,10 @@ fun SettingsRoute( ), genderData = SettingsData.ListData( isFeatureEnabled = isGenderFeatureEnabled, - current = GrammaticalGender.NotSpecified(), - onSelect = {} + current = GrammaticalGender.NotSpecified() ), dynamicColorsData = SettingsData.ChangedData( - isFeatureEnabled = isDynamicColorsFeatureEnabled, - isEnabled = false, - onChange = {} + isFeatureEnabled = isDynamicColorsFeatureEnabled ), paletteColorsData = SettingsData.PaletteColorsData( isFeatureEnabled = true, @@ -92,44 +91,33 @@ fun SettingsRoute( ), notificationsData = SettingsData.NotificationsData( isFeatureEnabled = isNotificationsFeatureEnabled, - isEnabled = false, - onClick = {}, - onNavigateToAppNotificationSettings = {} + isEnabled = false ), biometricData = SettingsData.ChangedData( - isFeatureEnabled = isBiometricFeatureEnabled, - isEnabled = false, - onChange = {} + isFeatureEnabled = isBiometricFeatureEnabled ), widgetData = SettingsData.RequestedData( - isFeatureEnabled = isWidgetFeatureEnabled, - onRequest = {} + isFeatureEnabled = isWidgetFeatureEnabled ), tileData = SettingsData.RequestedData( - isFeatureEnabled = isTileFeatureEnabled, - onRequest = {} + isFeatureEnabled = isTileFeatureEnabled ), appIconData = SettingsData.ListData( isFeatureEnabled = isAppIconFeatureEnabled, - current = IconAlias.Red, - onSelect = {} + current = IconAlias.Red ), screenshotData = SettingsData.ChangedData( - isFeatureEnabled = isScreenshotFeatureEnabled, - isEnabled = false, - onChange = {} + isFeatureEnabled = isScreenshotFeatureEnabled ), githubData = SettingsData.RequestedData( isFeatureEnabled = isGithubFeatureEnabled, - onRequest = viewModel::navigateToGithubUrl + onRequest = { openUrl(MOVIES_GITHUB_URL) } ), reviewAppData = SettingsData.RequestedData( - isFeatureEnabled = isReviewAppFeatureEnabled, - onRequest = {} + isFeatureEnabled = isReviewAppFeatureEnabled ), updateAppData = SettingsData.RequestedData( - isFeatureEnabled = isUpdateAppFeatureEnabled, - onRequest = {} + isFeatureEnabled = isUpdateAppFeatureEnabled ), aboutData = SettingsData.AboutData( isFeatureEnabled = isAboutFeatureEnabled, @@ -141,6 +129,7 @@ fun SettingsRoute( ), windowInsets = WindowInsets(0.dp, 0.dp, 0.dp, 0.dp), snackbarHostState = snackbarHostState, + isNavigationIconVisible = true, modifier = modifier ) } \ No newline at end of file diff --git a/feature/settings-impl/src/jvmMain/kotlin/org/michaelbel/movies/settings/SettingsViewModel.kt b/feature/settings-impl/src/jvmMain/kotlin/org/michaelbel/movies/settings/SettingsViewModel.kt deleted file mode 100644 index d2cf33f51..000000000 --- a/feature/settings-impl/src/jvmMain/kotlin/org/michaelbel/movies/settings/SettingsViewModel.kt +++ /dev/null @@ -1,70 +0,0 @@ -package org.michaelbel.movies.settings - -import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.launch -import org.michaelbel.movies.common.MOVIES_GITHUB_URL -import org.michaelbel.movies.common.ThemeData -import org.michaelbel.movies.common.appearance.FeedView -import org.michaelbel.movies.common.browser.openUrl -import org.michaelbel.movies.common.list.MovieList -import org.michaelbel.movies.common.theme.AppTheme -import org.michaelbel.movies.common.viewmodel.BaseViewModel -import org.michaelbel.movies.interactor.Interactor -import org.michaelbel.movies.interactor.entity.AppLanguage - -class SettingsViewModel( - private val interactor: Interactor -): BaseViewModel() { - - val themeData: StateFlow = interactor.themeData - .stateIn( - scope = viewModelScope, - started = SharingStarted.Lazily, - initialValue = ThemeData.Default - ) - - val currentFeedView: StateFlow = interactor.currentFeedView - .stateIn( - scope = viewModelScope, - started = SharingStarted.Lazily, - initialValue = FeedView.FeedList - ) - - val currentMovieList: StateFlow = interactor.currentMovieList - .stateIn( - scope = viewModelScope, - started = SharingStarted.Lazily, - initialValue = MovieList.NowPlaying() - ) - - fun selectLanguage(language: AppLanguage) = viewModelScope.launch { - interactor.selectLanguage(language) - } - - fun selectTheme(theme: AppTheme) = viewModelScope.launch { - interactor.selectTheme(theme) - } - - fun selectFeedView(feedView: FeedView) = viewModelScope.launch { - interactor.selectFeedView(feedView) - } - - fun selectMovieList(movieList: MovieList) = viewModelScope.launch { - interactor.selectMovieList(movieList) - } - - fun setPaletteKey(paletteKey: Int) = viewModelScope.launch { - interactor.setPaletteKey(paletteKey) - } - - fun setSeedColor(seedColor: Int) = viewModelScope.launch { - interactor.setSeedColor(seedColor) - } - - fun navigateToGithubUrl() { - openUrl(MOVIES_GITHUB_URL) - } -} \ No newline at end of file diff --git a/feature/settings-impl/src/jvmMain/kotlin/org/michaelbel/movies/settings/di/SettingsKoinModule.desktop.kt b/feature/settings-impl/src/jvmMain/kotlin/org/michaelbel/movies/settings/di/SettingsKoinModule.desktop.kt deleted file mode 100644 index c4ebb47df..000000000 --- a/feature/settings-impl/src/jvmMain/kotlin/org/michaelbel/movies/settings/di/SettingsKoinModule.desktop.kt +++ /dev/null @@ -1,12 +0,0 @@ -package org.michaelbel.movies.settings.di - -import org.koin.dsl.module -import org.michaelbel.movies.interactor.di.interactorKoinModule -import org.michaelbel.movies.settings.SettingsViewModel - -actual val settingsKoinModule = module { - includes( - interactorKoinModule - ) - single { SettingsViewModel(get()) } -} \ No newline at end of file diff --git a/feature/settings-impl/src/jvmMain/kotlin/org/michaelbel/movies/settings/ui/SettingsRoute.desktop.kt b/feature/settings-impl/src/jvmMain/kotlin/org/michaelbel/movies/settings/ui/SettingsRoute.desktop.kt index f6c0dfff9..52f8e9058 100644 --- a/feature/settings-impl/src/jvmMain/kotlin/org/michaelbel/movies/settings/ui/SettingsRoute.desktop.kt +++ b/feature/settings-impl/src/jvmMain/kotlin/org/michaelbel/movies/settings/ui/SettingsRoute.desktop.kt @@ -9,6 +9,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import org.jetbrains.compose.resources.stringResource import org.koin.compose.koinInject +import org.michaelbel.movies.common.MOVIES_GITHUB_URL +import org.michaelbel.movies.common.browser.openUrl import org.michaelbel.movies.common.gender.GrammaticalGender import org.michaelbel.movies.interactor.entity.AppLanguage import org.michaelbel.movies.settings.SettingsViewModel @@ -37,7 +39,7 @@ import org.michaelbel.movies.ui.strings.MoviesStrings fun SettingsRoute( onBackClick: () -> Unit, modifier: Modifier = Modifier, - viewModel: SettingsViewModel = koinInject() + viewModel: SettingsViewModel = koinInject() ) { val currentLanguage = AppLanguage.transform(stringResource(MoviesStrings.language_code)) val themeData by viewModel.themeData.collectAsStateCommon() @@ -70,13 +72,10 @@ fun SettingsRoute( ), genderData = SettingsData.ListData( isFeatureEnabled = isGenderFeatureEnabled, - current = GrammaticalGender.NotSpecified(), - onSelect = {} + current = GrammaticalGender.NotSpecified() ), dynamicColorsData = SettingsData.ChangedData( - isFeatureEnabled = isDynamicColorsFeatureEnabled, - isEnabled = false, - onChange = {} + isFeatureEnabled = isDynamicColorsFeatureEnabled ), paletteColorsData = SettingsData.PaletteColorsData( isFeatureEnabled = true, @@ -92,44 +91,33 @@ fun SettingsRoute( ), notificationsData = SettingsData.NotificationsData( isFeatureEnabled = isNotificationsFeatureEnabled, - isEnabled = false, - onClick = {}, - onNavigateToAppNotificationSettings = {} + isEnabled = false ), biometricData = SettingsData.ChangedData( - isFeatureEnabled = isBiometricFeatureEnabled, - isEnabled = false, - onChange = {} + isFeatureEnabled = isBiometricFeatureEnabled ), widgetData = SettingsData.RequestedData( - isFeatureEnabled = isWidgetFeatureEnabled, - onRequest = {} + isFeatureEnabled = isWidgetFeatureEnabled ), tileData = SettingsData.RequestedData( - isFeatureEnabled = isTileFeatureEnabled, - onRequest = {} + isFeatureEnabled = isTileFeatureEnabled ), appIconData = SettingsData.ListData( isFeatureEnabled = isAppIconFeatureEnabled, - current = IconAlias.Red, - onSelect = {} + current = IconAlias.Red ), screenshotData = SettingsData.ChangedData( - isFeatureEnabled = isScreenshotFeatureEnabled, - isEnabled = false, - onChange = {} + isFeatureEnabled = isScreenshotFeatureEnabled ), githubData = SettingsData.RequestedData( isFeatureEnabled = isGithubFeatureEnabled, - onRequest = viewModel::navigateToGithubUrl + onRequest = { openUrl(MOVIES_GITHUB_URL) } ), reviewAppData = SettingsData.RequestedData( - isFeatureEnabled = isReviewAppFeatureEnabled, - onRequest = {} + isFeatureEnabled = isReviewAppFeatureEnabled ), updateAppData = SettingsData.RequestedData( - isFeatureEnabled = isUpdateAppFeatureEnabled, - onRequest = {} + isFeatureEnabled = isUpdateAppFeatureEnabled ), aboutData = SettingsData.AboutData( isFeatureEnabled = isAboutFeatureEnabled, @@ -141,6 +129,7 @@ fun SettingsRoute( ), windowInsets = WindowInsets(0.dp, 0.dp, 0.dp, 0.dp), snackbarHostState = snackbarHostState, + isNavigationIconVisible = true, modifier = modifier ) } \ No newline at end of file diff --git a/feature/settings/src/androidMain/kotlin/org/michaelbel/movies/settings/SettingsNavigation.android.kt b/feature/settings/src/androidMain/kotlin/org/michaelbel/movies/settings/SettingsNavigation.android.kt index 5d983b381..f1a330258 100644 --- a/feature/settings/src/androidMain/kotlin/org/michaelbel/movies/settings/SettingsNavigation.android.kt +++ b/feature/settings/src/androidMain/kotlin/org/michaelbel/movies/settings/SettingsNavigation.android.kt @@ -1,16 +1,11 @@ package org.michaelbel.movies.settings -import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable import androidx.navigation.navDeepLink import org.michaelbel.movies.settings.ui.SettingsRoute import org.michaelbel.movies.ui.shortcuts.INTENT_ACTION_SETTINGS -fun NavController.navigateToSettings() { - navigate(SettingsDestination) -} - fun NavGraphBuilder.settingsGraph( navigateBack: () -> Unit ) { diff --git a/feature/settings/src/commonMain/kotlin/org/michaelbel/movies/settings/SettingsDestination.kt b/feature/settings/src/commonMain/kotlin/org/michaelbel/movies/settings/SettingsDestination.kt index 119da7c6e..b79511e05 100644 --- a/feature/settings/src/commonMain/kotlin/org/michaelbel/movies/settings/SettingsDestination.kt +++ b/feature/settings/src/commonMain/kotlin/org/michaelbel/movies/settings/SettingsDestination.kt @@ -3,4 +3,4 @@ package org.michaelbel.movies.settings import kotlinx.serialization.Serializable @Serializable -internal object SettingsDestination \ No newline at end of file +object SettingsDestination \ No newline at end of file diff --git a/feature/settings/src/commonMain/kotlin/org/michaelbel/movies/settings/SettingsNavigation.kt b/feature/settings/src/commonMain/kotlin/org/michaelbel/movies/settings/SettingsNavigation.kt new file mode 100644 index 000000000..b9b1f726e --- /dev/null +++ b/feature/settings/src/commonMain/kotlin/org/michaelbel/movies/settings/SettingsNavigation.kt @@ -0,0 +1,7 @@ +package org.michaelbel.movies.settings + +import androidx.navigation.NavController + +fun NavController.navigateToSettings() { + navigate(SettingsDestination) +} \ No newline at end of file diff --git a/feature/settings/src/iosMain/kotlin/org/michaelbel/movies/settings/SettingsNavigation.ios.kt b/feature/settings/src/iosMain/kotlin/org/michaelbel/movies/settings/SettingsNavigation.ios.kt index 05dc77974..bfe9a5c38 100644 --- a/feature/settings/src/iosMain/kotlin/org/michaelbel/movies/settings/SettingsNavigation.ios.kt +++ b/feature/settings/src/iosMain/kotlin/org/michaelbel/movies/settings/SettingsNavigation.ios.kt @@ -1,14 +1,9 @@ package org.michaelbel.movies.settings -import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable import org.michaelbel.movies.settings.ui.SettingsRoute -fun NavController.navigateToSettings() { - navigate(SettingsDestination) -} - fun NavGraphBuilder.settingsGraph( navigateBack: () -> Unit ) { diff --git a/feature/settings/src/jvmMain/kotlin/org/michaelbel/movies/settings/SettingsNavigation.desktop.kt b/feature/settings/src/jvmMain/kotlin/org/michaelbel/movies/settings/SettingsNavigation.desktop.kt index 05dc77974..bfe9a5c38 100644 --- a/feature/settings/src/jvmMain/kotlin/org/michaelbel/movies/settings/SettingsNavigation.desktop.kt +++ b/feature/settings/src/jvmMain/kotlin/org/michaelbel/movies/settings/SettingsNavigation.desktop.kt @@ -1,14 +1,9 @@ package org.michaelbel.movies.settings -import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable import org.michaelbel.movies.settings.ui.SettingsRoute -fun NavController.navigateToSettings() { - navigate(SettingsDestination) -} - fun NavGraphBuilder.settingsGraph( navigateBack: () -> Unit ) { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 835465bd2..e4b7f83f6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -136,7 +136,7 @@ androidx-test-espresso-idling-resource = { module = "androidx.test.espresso:espr androidx-test-uiautomator = { module = "androidx.test.uiautomator:uiautomator", version.ref = "androidx-test-uiautomator" } androidx-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "androidx-work" } jetbrains-androidx-navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "jetbrains-androidx-navigation-compose" } -jetbrains-androidx-lifecycle-viewmodel-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "jetbrains-androidx-lifecycle-viewmodel-compose" } +#jetbrains-androidx-lifecycle-viewmodel-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "jetbrains-androidx-lifecycle-viewmodel-compose" } jetbrains-androidx-core-bundle = { module = "org.jetbrains.androidx.core:core-bundle", version.ref = "jetbrains-androidx-core-bundle" } coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" } coil3-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil3" } @@ -181,9 +181,6 @@ kotlinx-serialization-common = [ jetbrains-androidx-navigation-compose-common = [ "jetbrains-androidx-navigation-compose" ] -jetbrains-androidx-lifecycle-viewmodel-compose-common = [ - "jetbrains-androidx-lifecycle-viewmodel-compose" -] jetbrains-androidx-core-bundle-common = [ "jetbrains-androidx-core-bundle" ] diff --git a/settings.gradle.kts b/settings.gradle.kts index 47fab1f13..0ebb02bd1 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -55,6 +55,7 @@ include( ":core:widget", ":core:work", + ":feature:main", ":feature:main-impl", ":feature:account", ":feature:account-impl",