diff --git a/core/ui/src/main/res/drawable/ic_gallery_24.xml b/core/ui/src/main/res/drawable/ic_gallery_24.xml
new file mode 100644
index 0000000000..b71095523e
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_gallery_24.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/features/qr-scanning/api/src/main/java/com/tangem/feature/qrscanning/QrScanningRouter.kt b/features/qr-scanning/api/src/main/java/com/tangem/feature/qrscanning/QrScanningRouter.kt
index ce7ea43935..7f5f0352b7 100644
--- a/features/qr-scanning/api/src/main/java/com/tangem/feature/qrscanning/QrScanningRouter.kt
+++ b/features/qr-scanning/api/src/main/java/com/tangem/feature/qrscanning/QrScanningRouter.kt
@@ -8,5 +8,6 @@ interface QrScanningRouter {
companion object {
const val SOURCE_KEY = "source"
+ const val NETWORK_KEY = "network"
}
}
\ No newline at end of file
diff --git a/features/qr-scanning/impl/build.gradle.kts b/features/qr-scanning/impl/build.gradle.kts
index 22fd460d7f..1d6c8db728 100644
--- a/features/qr-scanning/impl/build.gradle.kts
+++ b/features/qr-scanning/impl/build.gradle.kts
@@ -8,17 +8,17 @@ plugins {
android {
namespace = "com.tangem.feature.qrscanning.impl"
-
- viewBinding {
- enable = true
- }
}
dependencies {
+ /** Core */
+ implementation(projects.core.ui)
+ implementation(projects.core.utils)
+ implementation(projects.core.navigation)
+
implementation(deps.androidx.fragment.ktx)
- implementation(deps.viewBindingDelegate)
/** Camera */
implementation(deps.camera.camera2)
@@ -42,6 +42,12 @@ dependencies {
/** Compose */
implementation(deps.compose.foundation)
+ implementation(deps.compose.ui)
+ implementation(deps.compose.ui.tooling)
+ implementation(deps.compose.material3)
+ implementation(deps.compose.accompanist.systemUiController)
+ /** Other dependencies */
implementation(deps.arrow.core)
+ implementation(deps.timber)
}
\ No newline at end of file
diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/Const.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/Const.kt
deleted file mode 100644
index e2e475f8e9..0000000000
--- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/Const.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package com.tangem.feature.qrscanning
-
-const val SCAN_QR_REQUEST_CODE = 1001
-const val SCAN_RESULT = "scanResult"
-const val PERMISSION_REQUEST_CODE = 16
\ No newline at end of file
diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/DefaultQrScanningRouter.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/DefaultQrScanningRouter.kt
deleted file mode 100644
index 0e53af0a78..0000000000
--- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/DefaultQrScanningRouter.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.tangem.feature.qrscanning
-
-import androidx.fragment.app.Fragment
-
-internal class DefaultQrScanningRouter : QrScanningRouter {
-
- override fun getEntryFragment(): Fragment = QrScanningFragment.create()
-}
\ No newline at end of file
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 8db65f98c2..9120d00cd2 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
@@ -3,98 +3,86 @@ package com.tangem.feature.qrscanning
import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
-import android.view.*
-import androidx.camera.lifecycle.ProcessCameraProvider
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
import androidx.core.content.ContextCompat
import androidx.core.view.WindowCompat
-import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
-import by.kirich1409.viewbindingdelegate.viewBinding
-import com.google.common.util.concurrent.ListenableFuture
-import com.tangem.feature.qrscanning.impl.R
-import com.tangem.feature.qrscanning.impl.databinding.LayoutQrScanningBinding
-import com.tangem.feature.qrscanning.inner.PreviewBinder
+import com.tangem.core.ui.components.SystemBarsEffect
+import com.tangem.core.ui.screen.ComposeFragment
+import com.tangem.core.ui.theme.AppThemeModeHolder
+import com.tangem.feature.qrscanning.inner.MLKitBarcodeAnalyzer
+import com.tangem.feature.qrscanning.navigation.QrScanningInnerRouter
+import com.tangem.feature.qrscanning.presentation.QrScanningContent
+import com.tangem.feature.qrscanning.viewmodel.QrScanningViewModel
import dagger.hilt.android.AndroidEntryPoint
-// import com.tangem.core.navigation.NavigationAction
-// import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
-// import com.tangem.tap.features.details.ui.walletconnect.dialogs.PreviewBinder
-// import com.tangem.tap.store
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
+import javax.inject.Inject
+import kotlin.properties.Delegates
@AndroidEntryPoint
-internal class QrScanningFragment : Fragment(R.layout.layout_qr_scanning) {
+internal class QrScanningFragment : ComposeFragment() {
- private val binding: LayoutQrScanningBinding by viewBinding(LayoutQrScanningBinding::bind)
+ @Inject
+ override lateinit var appThemeModeHolder: AppThemeModeHolder
- private val binder = PreviewBinder()
+ @Inject
+ lateinit var router: QrScanningRouter
+
+ private val innerRouter: QrScanningInnerRouter
+ get() = requireNotNull(router as? QrScanningInnerRouter) {
+ "innerRouter should be instance of QrScanningInnerRouter"
+ }
private val viewModel by viewModels()
- private var cameraProviderFuture: ListenableFuture? = null
- private var cameraExecutor: ExecutorService? = null
+ private var cameraExecutor: ExecutorService by Delegates.notNull()
+
+ private val cameraPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) {
+ if (!it) parentFragmentManager.popBackStack()
+ }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- setFitSystemWindows(fit = true)
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- if (!permissionIsGranted()) requestPermission()
-
- cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
+ viewModel.router = innerRouter
cameraExecutor = Executors.newSingleThreadExecutor()
-
- cameraProviderFuture?.addListener(
- {
- val cameraProvider = cameraProviderFuture?.get()
- binder.bindPreview(
- context = requireContext(),
- binding = binding,
- lifecycleOwner = this,
- cameraProvider = requireNotNull(cameraProvider),
- cameraExecutor = requireNotNull(cameraExecutor),
- onScanned = { result ->
- parentFragmentManager.popBackStack()
- setFitSystemWindows(fit = false)
- if (result.isNotBlank()) {
- viewModel.onQrScanned(result)
- }
- },
- )
- },
- ContextCompat.getMainExecutor(requireContext()),
- )
-
- binding.overlay.post {
- binding.overlay.setViewFinder()
- }
+ requestCameraPermission()
}
override fun onDestroy() {
super.onDestroy()
+ setFitSystemWindows(fit = true)
+ cameraPermissionLauncher.unregister()
+ cameraExecutor.shutdown()
+ }
+
+ @Composable
+ override fun ScreenContent(modifier: Modifier) {
+ SystemBarsEffect {
+ setSystemBarsColor(Color.Transparent, darkIcons = false)
+ }
+ QrScanningContent(
+ executor = { cameraExecutor },
+ analyzer = { MLKitBarcodeAnalyzer(viewModel::onQrScanned) },
+ uiState = viewModel.uiState,
+ )
setFitSystemWindows(fit = false)
}
- override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
- if (requestCode != PERMISSION_REQUEST_CODE) return
-
- if (grantResults.isEmpty() || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
- parentFragmentManager.popBackStack()
+ private fun requestCameraPermission() {
+ if (
+ ContextCompat.checkSelfPermission(
+ requireContext(),
+ Manifest.permission.CAMERA,
+ ) == PackageManager.PERMISSION_DENIED
+ ) {
+ cameraPermissionLauncher.launch(Manifest.permission.CAMERA)
}
}
- private fun permissionIsGranted(): Boolean {
- val cameraPermission = ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA)
- return cameraPermission == PackageManager.PERMISSION_GRANTED
- }
-
- private fun requestPermission() {
- requestPermissions(arrayOf(Manifest.permission.CAMERA), PERMISSION_REQUEST_CODE)
- }
-
private fun setFitSystemWindows(fit: Boolean) {
activity?.window?.let {
WindowCompat.setDecorFitsSystemWindows(it, fit)
diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningRouterModule.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningRouterModule.kt
deleted file mode 100644
index 688c8f2cf9..0000000000
--- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningRouterModule.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.tangem.feature.qrscanning
-
-import dagger.Module
-import dagger.Provides
-import dagger.hilt.InstallIn
-import dagger.hilt.android.components.ActivityComponent
-import dagger.hilt.android.scopes.ActivityScoped
-
-@Module
-@InstallIn(ActivityComponent::class)
-internal object QrScanningRouterModule {
-
- @Provides
- @ActivityScoped
- fun provideQrScanRouter(): QrScanningRouter {
- return DefaultQrScanningRouter()
- }
-}
\ No newline at end of file
diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningViewModel.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningViewModel.kt
deleted file mode 100644
index b950318128..0000000000
--- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningViewModel.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.tangem.feature.qrscanning
-
-import androidx.lifecycle.SavedStateHandle
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
-import com.tangem.feature.qrscanning.QrScanningRouter.Companion.SOURCE_KEY
-import com.tangem.feature.qrscanning.usecase.EmitQrScannedEventUseCase
-import dagger.hilt.android.lifecycle.HiltViewModel
-import kotlinx.coroutines.launch
-import javax.inject.Inject
-
-@HiltViewModel
-internal class QrScanningViewModel @Inject constructor(
- private val emitQrScannedEventUseCase: EmitQrScannedEventUseCase,
- savedStateHandle: SavedStateHandle,
-) : ViewModel() {
-
- private val source: SourceType = savedStateHandle[SOURCE_KEY] ?: error("Source is mandatory")
-
- fun onQrScanned(qrCode: String) {
- viewModelScope.launch {
- emitQrScannedEventUseCase.invoke(source, qrCode)
- }
- }
-}
\ No newline at end of file
diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningModule.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/di/QrScanningModule.kt
similarity index 95%
rename from features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningModule.kt
rename to features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/di/QrScanningModule.kt
index 7cd0a6cde4..ad088e8c28 100644
--- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/QrScanningModule.kt
+++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/di/QrScanningModule.kt
@@ -1,4 +1,4 @@
-package com.tangem.feature.qrscanning
+package com.tangem.feature.qrscanning.di
import com.tangem.feature.qrscanning.repo.DefaultQrScanningEventsRepository
import com.tangem.feature.qrscanning.repo.QrScanningEventsRepository
diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/di/QrScanningRouterModule.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/di/QrScanningRouterModule.kt
new file mode 100644
index 0000000000..5bf6beee3f
--- /dev/null
+++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/di/QrScanningRouterModule.kt
@@ -0,0 +1,21 @@
+package com.tangem.feature.qrscanning.di
+
+import com.tangem.core.navigation.ReduxNavController
+import com.tangem.feature.qrscanning.QrScanningRouter
+import com.tangem.feature.qrscanning.navigation.DefaultQrScanningRouter
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.components.ActivityComponent
+import dagger.hilt.android.scopes.ActivityScoped
+
+@Module
+@InstallIn(ActivityComponent::class)
+internal object QrScanningRouterModule {
+
+ @Provides
+ @ActivityScoped
+ fun provideQrScanRouter(reduxNavController: ReduxNavController): QrScanningRouter {
+ return DefaultQrScanningRouter(reduxNavController)
+ }
+}
\ No newline at end of file
diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/inner/MLKitBarcodeAnalyzer.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/inner/MLKitBarcodeAnalyzer.kt
index 40a37a068f..ddd9d09857 100644
--- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/inner/MLKitBarcodeAnalyzer.kt
+++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/inner/MLKitBarcodeAnalyzer.kt
@@ -5,7 +5,9 @@ import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy
import com.google.mlkit.vision.barcode.BarcodeScanning
import com.google.mlkit.vision.common.InputImage
+import javax.annotation.concurrent.Immutable
+@Immutable
class MLKitBarcodeAnalyzer(private val onScanned: (String) -> Unit) : ImageAnalysis.Analyzer {
private var isScanning: Boolean = false
diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/inner/PreviewBinder.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/inner/PreviewBinder.kt
deleted file mode 100644
index 609a26688a..0000000000
--- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/inner/PreviewBinder.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-package com.tangem.feature.qrscanning.inner
-
-import android.content.Context
-import android.util.Size
-import android.view.OrientationEventListener
-import android.view.Surface
-import androidx.camera.core.CameraSelector
-import androidx.camera.core.ImageAnalysis
-import androidx.camera.core.Preview
-import androidx.camera.lifecycle.ProcessCameraProvider
-import androidx.lifecycle.LifecycleOwner
-import com.tangem.feature.qrscanning.impl.databinding.LayoutQrScanningBinding
-import java.util.concurrent.ExecutorService
-
-internal class PreviewBinder {
-
- @Suppress("LongParameterList")
- fun bindPreview(
- context: Context,
- binding: LayoutQrScanningBinding,
- lifecycleOwner: LifecycleOwner,
- cameraProvider: ProcessCameraProvider,
- cameraExecutor: ExecutorService,
- onScanned: (String) -> Unit,
- ) {
- cameraProvider.unbindAll()
-
- val preview: Preview = Preview.Builder()
- .build()
-
- val imageAnalysis = ImageAnalysis.Builder()
- .setTargetResolution(Size(binding.cameraPreview.width, binding.cameraPreview.height))
- .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
- .build()
-
- val orientationEventListener = object : OrientationEventListener(context) {
-
- @Suppress("MagicNumber")
- override fun onOrientationChanged(orientation: Int) {
- val rotation: Int = when (orientation) {
- in 45..134 -> Surface.ROTATION_270
- in 135..224 -> Surface.ROTATION_180
- in 225..314 -> Surface.ROTATION_90
- else -> Surface.ROTATION_0
- }
-
- imageAnalysis.targetRotation = rotation
- }
- }
- orientationEventListener.enable()
-
- val analyzer: ImageAnalysis.Analyzer = MLKitBarcodeAnalyzer {
- imageAnalysis.clearAnalyzer()
- onScanned.invoke(it)
- }
-
- cameraExecutor.let {
- imageAnalysis.setAnalyzer(it, analyzer)
- }
-
- preview.setSurfaceProvider(binding.cameraPreview.surfaceProvider)
-
- val cameraSelector: CameraSelector = CameraSelector.Builder()
- .requireLensFacing(CameraSelector.LENS_FACING_BACK)
- .build()
- cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, imageAnalysis, preview)
- }
-}
\ No newline at end of file
diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/inner/ViewFinderOverlay.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/inner/ViewFinderOverlay.kt
deleted file mode 100644
index febd5bbf06..0000000000
--- a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/inner/ViewFinderOverlay.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-package com.tangem.feature.qrscanning.inner
-
-import android.content.Context
-import android.graphics.*
-import android.util.AttributeSet
-import android.view.View
-import androidx.core.content.ContextCompat
-import com.tangem.feature.qrscanning.impl.R
-
-class ViewFinderOverlay(context: Context, attrs: AttributeSet) : View(context, attrs) {
-
- private val boxPaint: Paint = Paint().apply {
- color = ContextCompat.getColor(context, R.color.white)
- style = Paint.Style.STROKE
- strokeWidth = context.resources.getDimensionPixelOffset(R.dimen.qr_border_stroke_width).toFloat()
- }
-
- private val boxWidthRatio = 0.8F
- private val boxCornerRadius: Float =
- context.resources.getDimensionPixelOffset(R.dimen.qr_border_corner_radius).toFloat()
-
- private var boxRect: RectF? = null
-
- @Suppress("MagicNumber")
- fun setViewFinder() {
- val overlayWidth = width.toFloat()
- val overlayHeight = height.toFloat()
- val boxSize = overlayWidth * boxWidthRatio
- val cx = overlayWidth / 2
- val cy = overlayHeight / 2
- boxRect = RectF(cx - boxSize / 2, cy - boxSize / 2, cx + boxSize / 2, cy + boxSize / 2)
-
- invalidate()
- }
-
- override fun draw(canvas: Canvas) {
- super.draw(canvas)
- boxRect?.let {
- canvas.drawRoundRect(it, boxCornerRadius, boxCornerRadius, boxPaint)
- }
- }
-}
\ No newline at end of file
diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/navigation/DefaultQrScanningRouter.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/navigation/DefaultQrScanningRouter.kt
new file mode 100644
index 0000000000..17de053b79
--- /dev/null
+++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/navigation/DefaultQrScanningRouter.kt
@@ -0,0 +1,15 @@
+package com.tangem.feature.qrscanning.navigation
+
+import androidx.fragment.app.Fragment
+import com.tangem.core.navigation.ReduxNavController
+import com.tangem.feature.qrscanning.QrScanningFragment
+
+class DefaultQrScanningRouter(
+ private val reduxNavController: ReduxNavController,
+) : QrScanningInnerRouter {
+ override fun getEntryFragment(): Fragment = QrScanningFragment.create()
+
+ override fun popBackStack() {
+ reduxNavController.popBackStack()
+ }
+}
\ No newline at end of file
diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/navigation/QrScanningInnerRouter.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/navigation/QrScanningInnerRouter.kt
new file mode 100644
index 0000000000..cdfb4cf247
--- /dev/null
+++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/navigation/QrScanningInnerRouter.kt
@@ -0,0 +1,8 @@
+package com.tangem.feature.qrscanning.navigation
+
+import com.tangem.feature.qrscanning.QrScanningRouter
+
+interface QrScanningInnerRouter : QrScanningRouter {
+
+ fun popBackStack()
+}
\ No newline at end of file
diff --git a/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/CameraView.kt b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/CameraView.kt
new file mode 100644
index 0000000000..d3526d4e5a
--- /dev/null
+++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/CameraView.kt
@@ -0,0 +1,35 @@
+package com.tangem.feature.qrscanning.presentation
+
+import androidx.camera.core.CameraSelector
+import androidx.camera.core.ImageAnalysis
+import androidx.camera.view.LifecycleCameraController
+import androidx.camera.view.PreviewView
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.compose.ui.viewinterop.AndroidView
+import com.tangem.feature.qrscanning.inner.MLKitBarcodeAnalyzer
+import java.util.concurrent.ExecutorService
+
+@Composable
+internal fun CameraView(executor: () -> ExecutorService, analyzer: () -> MLKitBarcodeAnalyzer) {
+ val context = LocalContext.current
+ val lifecycleOwner = LocalLifecycleOwner.current
+
+ val previewView = remember { PreviewView(context) }
+ val cameraController = remember { LifecycleCameraController(context) }
+
+ cameraController.apply {
+ bindToLifecycle(lifecycleOwner)
+ cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
+ setImageAnalysisAnalyzer(executor(), analyzer())
+ imageAnalysisBackpressureStrategy = ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST
+ }
+
+ previewView.controller = cameraController
+
+ AndroidView({ previewView }, modifier = Modifier.fillMaxSize())
+}
\ 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
new file mode 100644
index 0000000000..0a7ef5a8ec
--- /dev/null
+++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/QrScanningContent.kt
@@ -0,0 +1,125 @@
+package com.tangem.feature.qrscanning.presentation
+
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.statusBarsPadding
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+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.LocalDensity
+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.tangem.core.ui.components.appbar.AppBarWithBackButtonAndIcon
+import com.tangem.core.ui.extensions.TextReference
+import com.tangem.core.ui.extensions.resolveReference
+import com.tangem.core.ui.res.TangemColorPalette
+import com.tangem.core.ui.res.TangemTheme
+import com.tangem.feature.qrscanning.impl.R
+import com.tangem.feature.qrscanning.inner.MLKitBarcodeAnalyzer
+import java.util.concurrent.ExecutorService
+import kotlin.math.roundToInt
+
+private const val QR_CODE_SIZE = 0.8f
+
+@Composable
+internal fun QrScanningContent(
+ executor: () -> ExecutorService,
+ analyzer: () -> MLKitBarcodeAnalyzer,
+ uiState: QrScanningState,
+) {
+ Box(
+ modifier = Modifier
+ .fillMaxSize(),
+ ) {
+ CameraView(
+ executor = executor,
+ analyzer = analyzer,
+ )
+ Overlay(
+ message = uiState.message,
+ )
+ AppBarWithBackButtonAndIcon(
+ onBackClick = uiState.onBackClick,
+ iconRes = R.drawable.ic_gallery_24,
+ onIconClick = uiState.onGalleryClicked,
+ backgroundColor = Color.Transparent,
+ modifier = Modifier.statusBarsPadding(),
+ )
+ }
+}
+
+@Composable
+private fun Overlay(message: TextReference?, overlayColor: Color = TangemColorPalette.Black.copy(alpha = 0.7f)) {
+ val whiteColor = TangemTheme.colors.text.constantWhite
+ val borderWidth = with(LocalDensity.current) { TangemTheme.dimens.spacing4.toPx() }
+ val borderLength = with(LocalDensity.current) { TangemTheme.dimens.size20.toPx() }
+
+ val textMeasurer = rememberTextMeasurer()
+ val textPadding = with(LocalDensity.current) { TangemTheme.dimens.spacing24.toPx() }
+ val style = TangemTheme.typography.caption2.copy(textAlign = TextAlign.Center)
+ val text = message?.resolveReference()
+
+ Canvas(modifier = Modifier.fillMaxSize()) {
+ val squareSize = size.minDimension * QR_CODE_SIZE
+ val verticalOffset = (size.height - squareSize) / 2f
+ val horizontalOffset = (size.width - squareSize) / 2f
+ drawRect(
+ color = overlayColor,
+ topLeft = Offset.Zero,
+ size = Size(size.width, verticalOffset),
+ )
+ drawRect(
+ color = overlayColor,
+ topLeft = Offset(0f, verticalOffset),
+ size = Size(horizontalOffset, squareSize),
+ )
+ drawRect(
+ color = overlayColor,
+ topLeft = Offset(horizontalOffset + squareSize, verticalOffset),
+ size = Size(horizontalOffset, squareSize),
+ )
+ drawRect(
+ color = overlayColor,
+ topLeft = Offset(0f, verticalOffset + squareSize),
+ size = Size(size.width, verticalOffset),
+ )
+ drawRect(
+ color = whiteColor,
+ topLeft = Offset(horizontalOffset, verticalOffset),
+ size = Size(squareSize, squareSize),
+ style = Stroke(
+ width = borderWidth,
+ join = StrokeJoin.Round,
+ pathEffect = PathEffect.dashPathEffect(
+ intervals = floatArrayOf(2 * borderLength, squareSize - 2 * borderLength),
+ phase = borderLength,
+ ),
+ ),
+ )
+ text?.let {
+ val textLayoutResult = text.let {
+ textMeasurer.measure(
+ text = it,
+ style = style,
+ constraints = Constraints.fixedWidth(squareSize.roundToInt()),
+ )
+ }
+ drawText(
+ textLayoutResult = textLayoutResult,
+ color = whiteColor,
+ topLeft = Offset(
+ x = horizontalOffset,
+ y = verticalOffset + squareSize + textPadding,
+ ),
+ )
+ }
+ }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000000..d9eb27b155
--- /dev/null
+++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/QrScanningState.kt
@@ -0,0 +1,12 @@
+package com.tangem.feature.qrscanning.presentation
+
+import androidx.compose.runtime.Immutable
+import com.tangem.core.ui.extensions.TextReference
+
+@Immutable
+data class QrScanningState(
+ val message: TextReference?,
+ val onQrScanned: (String) -> Unit,
+ val onBackClick: () -> Unit,
+ val onGalleryClicked: () -> Unit,
+)
\ 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/QrScanningStateFactory.kt
new file mode 100644
index 0000000000..0b7ef2c4da
--- /dev/null
+++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/presentation/QrScanningStateFactory.kt
@@ -0,0 +1,26 @@
+package com.tangem.feature.qrscanning.presentation
+
+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
+
+internal class QrScanningStateFactory(
+ val clickIntents: QrScanningClickIntents,
+) {
+
+ fun getInitialState(source: SourceType, network: String?): QrScanningState {
+ val message = when (source) {
+ SourceType.SEND -> network?.let { resourceReference(R.string.send_qrcode_scan_info, wrappedList(it)) }
+ else -> null
+ }
+
+ return QrScanningState(
+ message = message,
+ onBackClick = clickIntents::onBackClick,
+ onQrScanned = clickIntents::onQrScanned,
+ onGalleryClicked = clickIntents::onGalleryClicked,
+ )
+ }
+}
\ 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
new file mode 100644
index 0000000000..38219460a6
--- /dev/null
+++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningClickIntents.kt
@@ -0,0 +1,10 @@
+package com.tangem.feature.qrscanning.viewmodel
+
+interface QrScanningClickIntents {
+
+ fun onBackClick()
+
+ fun onQrScanned(qrCode: String)
+
+ fun onGalleryClicked()
+}
\ 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
new file mode 100644
index 0000000000..a879b5f9fb
--- /dev/null
+++ b/features/qr-scanning/impl/src/main/java/com/tangem/feature/qrscanning/viewmodel/QrScanningViewModel.kt
@@ -0,0 +1,55 @@
+package com.tangem.feature.qrscanning.viewmodel
+
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.tangem.feature.qrscanning.QrScanningRouter.Companion.NETWORK_KEY
+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 dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+import kotlin.properties.Delegates
+
+@HiltViewModel
+internal class QrScanningViewModel @Inject constructor(
+ private val emitQrScannedEventUseCase: EmitQrScannedEventUseCase,
+ private val dispatcher: CoroutineDispatcherProvider,
+ savedStateHandle: SavedStateHandle,
+) : ViewModel(), QrScanningClickIntents {
+
+ private val source: SourceType = savedStateHandle[SOURCE_KEY] ?: error("Source is mandatory")
+ private val network: String? = savedStateHandle[NETWORK_KEY]
+
+ private val factory = QrScanningStateFactory(
+ clickIntents = this,
+ )
+
+ var router: QrScanningInnerRouter by Delegates.notNull()
+
+ var uiState: QrScanningState by mutableStateOf(factory.getInitialState(source, network))
+ private set
+
+ override fun onBackClick() = router.popBackStack()
+
+ override fun onQrScanned(qrCode: String) {
+ if (qrCode.isNotBlank()) {
+ router.popBackStack()
+ viewModelScope.launch(dispatcher.main) {
+ emitQrScannedEventUseCase.invoke(source, qrCode)
+ }
+ }
+ }
+
+ override fun onGalleryClicked() {
+ // [REDACTED_JIRA]
+ }
+}
\ No newline at end of file
diff --git a/features/qr-scanning/impl/src/main/res/layout/layout_qr_scanning.xml b/features/qr-scanning/impl/src/main/res/layout/layout_qr_scanning.xml
deleted file mode 100644
index ecf16ff82e..0000000000
--- a/features/qr-scanning/impl/src/main/res/layout/layout_qr_scanning.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/features/qr-scanning/impl/src/main/res/values/colors.xml b/features/qr-scanning/impl/src/main/res/values/colors.xml
deleted file mode 100644
index 028dd5972c..0000000000
--- a/features/qr-scanning/impl/src/main/res/values/colors.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
- #FFFFFFFF
-
\ No newline at end of file
diff --git a/features/qr-scanning/impl/src/main/res/values/dimens.xml b/features/qr-scanning/impl/src/main/res/values/dimens.xml
deleted file mode 100644
index 19f54ca894..0000000000
--- a/features/qr-scanning/impl/src/main/res/values/dimens.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
- 4dp
- 8dp
-
-
\ No newline at end of file