From 8c0712850971a0767a70262ce634932d493ad688 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 14 Feb 2024 18:21:47 +0300 Subject: [PATCH] Updated on 2026-08-14 --- core/res/src/main/res/values/strings.xml | 4 + .../src/main/res/drawable/ic_settings_24.xml | 9 ++ features/qr-scanning/impl/build.gradle.kts | 1 + .../feature/qrscanning/QrScanningFragment.kt | 16 +++- .../CameraDeniedBottomSheetConfig.kt | 8 ++ .../QrScanningCameraDeniedBottomSheet.kt | 82 +++++++++++++++++++ .../presentation/QrScanningContent.kt | 26 +++--- .../presentation/QrScanningState.kt | 4 +- .../presentation/QrScanningStateController.kt | 35 ++++++++ .../DismissBottomSheetTransformer.kt | 11 +++ .../InitializeQrScanningStateTransformer.kt} | 17 ++-- .../transformers/QrScanningTransformer.kt | 8 ++ .../ShowCameraDeniedBottomSheetTransformer.kt | 24 ++++++ .../viewmodel/BaseQrScanningClickIntents.kt | 33 ++++++++ .../viewmodel/QrScanningClickIntents.kt | 43 ++++++++++ .../viewmodel/QrScanningViewModel.kt | 56 +++++-------- 16 files changed, 316 insertions(+), 61 deletions(-) create mode 100644 core/ui/src/main/res/drawable/ic_settings_24.xml create mode 100644 features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/CameraDeniedBottomSheetConfig.kt create mode 100644 features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/QrScanningCameraDeniedBottomSheet.kt create mode 100644 features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/QrScanningStateController.kt create mode 100644 features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/transformers/DismissBottomSheetTransformer.kt rename features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/{QrScanningStateFactory.kt => transformers/InitializeQrScanningStateTransformer.kt} (57%) create mode 100644 features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/transformers/QrScanningTransformer.kt create mode 100644 features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/transformers/ShowCameraDeniedBottomSheetTransformer.kt create mode 100644 features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/BaseQrScanningClickIntents.kt diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 052973065a..23e26fd59a 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -393,6 +393,10 @@ By balance Organize tokens Ungroup + Select from the gallery + Settings + Camera access denied + You have not given access to your camera %1$s %2$s address on %3$s network %1$s (%2$s) on %3$s network Send only %s to this address. Sending any other currency will result in its irreversible loss. diff --git a/core/ui/src/main/res/drawable/ic_settings_24.xml b/core/ui/src/main/res/drawable/ic_settings_24.xml new file mode 100644 index 0000000000..480a877aa2 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_settings_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/features/qr-scanning/impl/build.gradle.kts b/features/qr-scanning/impl/build.gradle.kts index 712e2a79ce..e1d37506b4 100644 --- a/features/qr-scanning/impl/build.gradle.kts +++ b/features/qr-scanning/impl/build.gradle.kts @@ -20,6 +20,7 @@ dependencies { implementation(deps.androidx.fragment.ktx) implementation(deps.androidx.activity.compose) + implementation(deps.lifecycle.compose) /** Camera */ implementation(deps.camera.camera2) diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt index 5805460bc2..a3ba7da2d9 100644 --- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningFragment.kt @@ -2,6 +2,7 @@ package com.tangem.feature.qrscanning import android.Manifest import android.content.pm.PackageManager +import android.net.Uri import android.os.Bundle import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.runtime.Composable @@ -10,6 +11,8 @@ import androidx.compose.ui.graphics.Color import androidx.core.content.ContextCompat import androidx.core.view.WindowCompat import androidx.fragment.app.viewModels +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.google.mlkit.vision.common.InputImage import com.tangem.core.ui.components.SystemBarsEffect import com.tangem.core.ui.screen.ComposeFragment import com.tangem.core.ui.theme.AppThemeModeHolder @@ -45,12 +48,19 @@ internal class QrScanningFragment : ComposeFragment() { } private val cameraPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { - if (!it) parentFragmentManager.popBackStack() + if (!it) viewModel.onCameraDeniedState() + } + private val galleryLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { + val selectedImage = it ?: Uri.EMPTY + if (selectedImage != Uri.EMPTY) { + val image = InputImage.fromFilePath(requireContext(), selectedImage) + analyzer.analyze(image) + } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - viewModel.router = innerRouter + viewModel.setRouter(innerRouter, galleryLauncher) cameraExecutor = Executors.newSingleThreadExecutor() requestCameraPermission() } @@ -70,7 +80,7 @@ internal class QrScanningFragment : ComposeFragment() { QrScanningContent( executor = { cameraExecutor }, analyzer = { analyzer }, - uiState = viewModel.uiState, + uiState = viewModel.uiState.collectAsStateWithLifecycle().value, ) setFitSystemWindows(fit = false) } diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/CameraDeniedBottomSheetConfig.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/CameraDeniedBottomSheetConfig.kt new file mode 100644 index 0000000000..3d23eb1813 --- /dev/null +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/CameraDeniedBottomSheetConfig.kt @@ -0,0 +1,8 @@ +package com.tangem.feature.qrscanning.presentation + +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent + +data class CameraDeniedBottomSheetConfig( + val onGalleryClick: () -> Unit, + val onCancelClick: () -> Unit, +) : TangemBottomSheetConfigContent \ No newline at end of file diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/QrScanningCameraDeniedBottomSheet.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/QrScanningCameraDeniedBottomSheet.kt new file mode 100644 index 0000000000..a4ca7198dc --- /dev/null +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/QrScanningCameraDeniedBottomSheet.kt @@ -0,0 +1,82 @@ +package com.tangem.feature.qrscanning.presentation + +import android.content.Intent +import android.net.Uri +import android.provider.Settings +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.core.content.ContextCompat +import com.tangem.core.ui.components.SimpleSettingsRow +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.qrscanning.impl.R + +@Composable +fun CameraDeniedBottomSheet(config: TangemBottomSheetConfig) { + TangemBottomSheet(config) { content: CameraDeniedBottomSheetConfig -> + CameraDeniedBottomSheet(content = content) + } +} + +@Composable +private fun CameraDeniedBottomSheet(content: CameraDeniedBottomSheetConfig) { + val context = LocalContext.current + Column { + CameraDeniedBottomSheetHeader() + SimpleSettingsRow( + title = stringResource(id = R.string.qr_scanner_camera_denied_settings_button), + icon = R.drawable.ic_settings_24, + onItemsClick = { + val intent: Intent = Intent( + Settings.ACTION_APPLICATION_DETAILS_SETTINGS, + Uri.fromParts("package", context.packageName, null), + ).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + ContextCompat.startActivity(context, intent, null) + }, + ) + SimpleSettingsRow( + title = stringResource(id = R.string.qr_scanner_camera_denied_gallery_button), + icon = R.drawable.ic_gallery_24, + onItemsClick = content.onGalleryClick, + ) + SimpleSettingsRow( + title = stringResource(id = R.string.common_close), + icon = R.drawable.ic_close, + onItemsClick = content.onCancelClick, + modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing16), + ) + } +} + +@Composable +private fun CameraDeniedBottomSheetHeader() { + Text( + text = stringResource(id = R.string.qr_scanner_camera_denied_title), + style = TangemTheme.typography.subtitle1, + color = TangemTheme.colors.text.primary1, + modifier = Modifier + .padding( + start = TangemTheme.dimens.spacing20, + end = TangemTheme.dimens.spacing20, + top = TangemTheme.dimens.spacing16, + ), + ) + Text( + text = stringResource(id = R.string.qr_scanner_camera_denied_text), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.secondary, + modifier = Modifier + .padding( + start = TangemTheme.dimens.spacing20, + end = TangemTheme.dimens.spacing20, + top = TangemTheme.dimens.spacing3, + bottom = TangemTheme.dimens.spacing16, + ), + ) +} \ No newline at end of file diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/QrScanningContent.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/QrScanningContent.kt index 8d169703a6..6dc9d3592b 100644 --- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/QrScanningContent.kt +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/QrScanningContent.kt @@ -1,9 +1,10 @@ package com.tangem.feature.qrscanning.presentation -import android.net.Uri -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.animation.* +import androidx.activity.compose.BackHandler +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.togetherWith import androidx.compose.foundation.Canvas import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource @@ -18,14 +19,12 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathEffect import androidx.compose.ui.graphics.StrokeJoin import androidx.compose.ui.graphics.drawscope.Stroke -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.drawText import androidx.compose.ui.text.rememberTextMeasurer import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Constraints -import com.google.mlkit.vision.common.InputImage import com.tangem.core.ui.components.appbar.AppBarWithBackButtonAndIconContent import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference @@ -44,15 +43,9 @@ internal fun QrScanningContent( analyzer: () -> MLKitBarcodeAnalyzer, uiState: QrScanningState, ) { - val context = LocalContext.current var isFlash by remember { mutableStateOf(false) } - val galleryLauncher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { - val selectedImage = it ?: Uri.EMPTY - if (selectedImage != Uri.EMPTY) { - val image = InputImage.fromFilePath(context, selectedImage) - analyzer().analyze(image) - } - } + + BackHandler(onBack = uiState.onBackClick) Box( modifier = Modifier @@ -101,13 +94,16 @@ internal fun QrScanningContent( .clickable( interactionSource = remember { MutableInteractionSource() }, indication = rememberRipple(bounded = false), - onClick = { galleryLauncher.launch("image/*") }, + onClick = uiState.onGalleryClick, ), tint = TangemColorPalette.White, ) } }, ) + if (uiState.bottomSheetConfig != null) { + CameraDeniedBottomSheet(uiState.bottomSheetConfig) + } } } diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/QrScanningState.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/QrScanningState.kt index d9eb27b155..e9ca30f4e1 100644 --- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/QrScanningState.kt +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/QrScanningState.kt @@ -1,6 +1,7 @@ package com.tangem.feature.qrscanning.presentation import androidx.compose.runtime.Immutable +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.extensions.TextReference @Immutable @@ -8,5 +9,6 @@ data class QrScanningState( val message: TextReference?, val onQrScanned: (String) -> Unit, val onBackClick: () -> Unit, - val onGalleryClicked: () -> Unit, + val onGalleryClick: () -> Unit, + val bottomSheetConfig: TangemBottomSheetConfig? = null, ) \ No newline at end of file diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/QrScanningStateController.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/QrScanningStateController.kt new file mode 100644 index 0000000000..efad8e7984 --- /dev/null +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/QrScanningStateController.kt @@ -0,0 +1,35 @@ +package com.tangem.feature.qrscanning.presentation + +import com.tangem.feature.qrscanning.presentation.transformers.QrScanningTransformer +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +internal class QrScanningStateController @Inject constructor() { + + val uiState: StateFlow get() = mutableUiState + + val value: QrScanningState get() = uiState.value + + private val mutableUiState: MutableStateFlow = MutableStateFlow(value = getInitialState()) + + fun update(function: (QrScanningState) -> QrScanningState) { + mutableUiState.update(function = function) + } + + fun update(transformer: QrScanningTransformer) { + mutableUiState.update(function = transformer::transform) + } + + private fun getInitialState(): QrScanningState { + return QrScanningState( + message = null, + onBackClick = {}, + onQrScanned = {}, + onGalleryClick = {}, + ) + } +} \ No newline at end of file diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/transformers/DismissBottomSheetTransformer.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/transformers/DismissBottomSheetTransformer.kt new file mode 100644 index 0000000000..bd3d2fb621 --- /dev/null +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/transformers/DismissBottomSheetTransformer.kt @@ -0,0 +1,11 @@ +package com.tangem.feature.qrscanning.presentation.transformers + +import com.tangem.feature.qrscanning.presentation.QrScanningState + +internal class DismissBottomSheetTransformer : QrScanningTransformer { + override fun transform(prevState: QrScanningState): QrScanningState { + return prevState.copy( + bottomSheetConfig = null, + ) + } +} \ No newline at end of file diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/QrScanningStateFactory.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/transformers/InitializeQrScanningStateTransformer.kt similarity index 57% rename from features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/QrScanningStateFactory.kt rename to features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/transformers/InitializeQrScanningStateTransformer.kt index 0b7ef2c4da..c4df653e5a 100644 --- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/QrScanningStateFactory.kt +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/transformers/InitializeQrScanningStateTransformer.kt @@ -1,16 +1,19 @@ -package com.tangem.feature.qrscanning.presentation +package com.tangem.feature.qrscanning.presentation.transformers import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.wrappedList -import com.tangem.feature.qrscanning.viewmodel.QrScanningClickIntents import com.tangem.feature.qrscanning.SourceType import com.tangem.feature.qrscanning.impl.R +import com.tangem.feature.qrscanning.presentation.QrScanningState +import com.tangem.feature.qrscanning.viewmodel.QrScanningClickIntents -internal class QrScanningStateFactory( - val clickIntents: QrScanningClickIntents, -) { +internal class InitializeQrScanningStateTransformer( + private val clickIntents: QrScanningClickIntents, + private val source: SourceType, + private val network: String?, +) : QrScanningTransformer { - fun getInitialState(source: SourceType, network: String?): QrScanningState { + override fun transform(prevState: QrScanningState): QrScanningState { val message = when (source) { SourceType.SEND -> network?.let { resourceReference(R.string.send_qrcode_scan_info, wrappedList(it)) } else -> null @@ -20,7 +23,7 @@ internal class QrScanningStateFactory( message = message, onBackClick = clickIntents::onBackClick, onQrScanned = clickIntents::onQrScanned, - onGalleryClicked = clickIntents::onGalleryClicked, + onGalleryClick = clickIntents::onGalleryClicked, ) } } \ No newline at end of file diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/transformers/QrScanningTransformer.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/transformers/QrScanningTransformer.kt new file mode 100644 index 0000000000..16dcdfbd77 --- /dev/null +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/transformers/QrScanningTransformer.kt @@ -0,0 +1,8 @@ +package com.tangem.feature.qrscanning.presentation.transformers + +import com.tangem.feature.qrscanning.presentation.QrScanningState + +internal interface QrScanningTransformer { + + fun transform(prevState: QrScanningState): QrScanningState +} \ No newline at end of file diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/transformers/ShowCameraDeniedBottomSheetTransformer.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/transformers/ShowCameraDeniedBottomSheetTransformer.kt new file mode 100644 index 0000000000..5677a023f8 --- /dev/null +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/transformers/ShowCameraDeniedBottomSheetTransformer.kt @@ -0,0 +1,24 @@ +package com.tangem.feature.qrscanning.presentation.transformers + +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.feature.qrscanning.presentation.CameraDeniedBottomSheetConfig +import com.tangem.feature.qrscanning.presentation.QrScanningState +import com.tangem.feature.qrscanning.viewmodel.QrScanningClickIntents + +internal class ShowCameraDeniedBottomSheetTransformer( + private val clickIntents: QrScanningClickIntents, +) : QrScanningTransformer { + + override fun transform(prevState: QrScanningState): QrScanningState { + return prevState.copy( + bottomSheetConfig = TangemBottomSheetConfig( + isShow = true, + onDismissRequest = clickIntents::onBackClick, + content = CameraDeniedBottomSheetConfig( + onCancelClick = clickIntents::onBackClick, + onGalleryClick = clickIntents::onGalleryClicked, + ), + ), + ) + } +} \ No newline at end of file diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/BaseQrScanningClickIntents.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/BaseQrScanningClickIntents.kt new file mode 100644 index 0000000000..3ae564a745 --- /dev/null +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/BaseQrScanningClickIntents.kt @@ -0,0 +1,33 @@ +package com.tangem.feature.qrscanning.viewmodel + +import androidx.activity.result.ActivityResultLauncher +import com.tangem.feature.qrscanning.SourceType +import com.tangem.feature.qrscanning.navigation.QrScanningInnerRouter +import kotlinx.coroutines.CoroutineScope +import kotlin.properties.Delegates + +internal open class BaseQrScanningClickIntents { + + protected val router: QrScanningInnerRouter get() = _router + protected val viewModelScope: CoroutineScope get() = _viewModelScope + protected val source: SourceType get() = _source + protected val galleryLauncher: ActivityResultLauncher get() = _galleryLauncher + + private var _router: QrScanningInnerRouter by Delegates.notNull() + private var _viewModelScope: CoroutineScope by Delegates.notNull() + private var _source: SourceType by Delegates.notNull() + + private var _galleryLauncher: ActivityResultLauncher by Delegates.notNull() + + open fun initialize( + router: QrScanningInnerRouter, + source: SourceType, + galleryLauncher: ActivityResultLauncher, + coroutineScope: CoroutineScope, + ) { + _router = router + _viewModelScope = coroutineScope + _source = source + _galleryLauncher = galleryLauncher + } +} \ No newline at end of file diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningClickIntents.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningClickIntents.kt index 38219460a6..7ecbdae562 100644 --- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningClickIntents.kt +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningClickIntents.kt @@ -1,5 +1,13 @@ package com.tangem.feature.qrscanning.viewmodel +import com.tangem.feature.qrscanning.presentation.QrScanningStateController +import com.tangem.feature.qrscanning.presentation.transformers.DismissBottomSheetTransformer +import com.tangem.feature.qrscanning.usecase.EmitQrScannedEventUseCase +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import dagger.hilt.android.scopes.ViewModelScoped +import kotlinx.coroutines.launch +import javax.inject.Inject + interface QrScanningClickIntents { fun onBackClick() @@ -7,4 +15,39 @@ interface QrScanningClickIntents { fun onQrScanned(qrCode: String) fun onGalleryClicked() +} + +@ViewModelScoped +internal class QrScanningClickIntentsImplementor @Inject constructor( + private val stateHolder: QrScanningStateController, + private val emitQrScannedEventUseCase: EmitQrScannedEventUseCase, + private val dispatcher: CoroutineDispatcherProvider, +) : BaseQrScanningClickIntents(), QrScanningClickIntents { + + private var isScanned = false + + override fun onBackClick() = router.popBackStack() + + override fun onQrScanned(qrCode: String) { + if (qrCode.isNotBlank()) { + if (!isScanned) { + router.popBackStack() + isScanned = true + } + viewModelScope.launch(dispatcher.main) { + emitQrScannedEventUseCase.invoke(source, qrCode) + } + } + } + + override fun onGalleryClicked() { + galleryLauncher.launch(GALLERY_IMAGE_FILTER) + if (stateHolder.value.bottomSheetConfig != null) { + stateHolder.update(DismissBottomSheetTransformer()) + } + } + + companion object { + private const val GALLERY_IMAGE_FILTER = "image/*" + } } \ No newline at end of file diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningViewModel.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningViewModel.kt index b0eef6d35c..5402f469b0 100644 --- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningViewModel.kt +++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningViewModel.kt @@ -1,8 +1,6 @@ package com.tangem.feature.qrscanning.viewmodel -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue +import androidx.activity.result.ActivityResultLauncher import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -11,50 +9,38 @@ import com.tangem.feature.qrscanning.QrScanningRouter.Companion.SOURCE_KEY import com.tangem.feature.qrscanning.SourceType import com.tangem.feature.qrscanning.navigation.QrScanningInnerRouter import com.tangem.feature.qrscanning.presentation.QrScanningState -import com.tangem.feature.qrscanning.presentation.QrScanningStateFactory -import com.tangem.feature.qrscanning.usecase.EmitQrScannedEventUseCase -import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.feature.qrscanning.presentation.QrScanningStateController +import com.tangem.feature.qrscanning.presentation.transformers.ShowCameraDeniedBottomSheetTransformer +import com.tangem.feature.qrscanning.presentation.transformers.InitializeQrScanningStateTransformer import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.launch +import kotlinx.coroutines.flow.StateFlow import javax.inject.Inject -import kotlin.properties.Delegates @HiltViewModel internal class QrScanningViewModel @Inject constructor( - private val emitQrScannedEventUseCase: EmitQrScannedEventUseCase, - private val dispatcher: CoroutineDispatcherProvider, + private val stateHolder: QrScanningStateController, + private val clickIntents: QrScanningClickIntentsImplementor, savedStateHandle: SavedStateHandle, -) : ViewModel(), QrScanningClickIntents { +) : ViewModel() { private val source: SourceType = savedStateHandle[SOURCE_KEY] ?: error("Source is mandatory") private val network: String? = savedStateHandle[NETWORK_KEY] - private val factory = QrScanningStateFactory( - clickIntents = this, - ) + val uiState: StateFlow = stateHolder.uiState - var router: QrScanningInnerRouter by Delegates.notNull() - - var uiState: QrScanningState by mutableStateOf(factory.getInitialState(source, network)) - private set - - private var isScanned = false - - override fun onBackClick() = router.popBackStack() - - override fun onQrScanned(qrCode: String) { - if (qrCode.isNotBlank()) { - if (!isScanned) { - router.popBackStack() - isScanned = true - } - viewModelScope.launch(dispatcher.main) { - emitQrScannedEventUseCase.invoke(source, qrCode) - } - } + fun setRouter(router: QrScanningInnerRouter, galleryLauncher: ActivityResultLauncher) { + clickIntents.initialize( + router = router, + source = source, + galleryLauncher = galleryLauncher, + coroutineScope = viewModelScope, + ) + stateHolder.update(InitializeQrScanningStateTransformer(clickIntents, source, network)) } - override fun onGalleryClicked() { - // [REDACTED_JIRA] + fun onQrScanned(qrCode: String) = clickIntents.onQrScanned(qrCode) + + fun onCameraDeniedState() { + stateHolder.update(ShowCameraDeniedBottomSheetTransformer(clickIntents)) } } \ No newline at end of file