diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButtonAndIcon.kt b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButtonAndIcon.kt
index 6151499106..c9e0526b4e 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButtonAndIcon.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButtonAndIcon.kt
@@ -2,16 +2,10 @@ package com.tangem.core.ui.components.appbar
import androidx.annotation.DrawableRes
import androidx.compose.animation.*
-import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
-import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.layout.size
import androidx.compose.material.Icon
-import androidx.compose.material.Text
-import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
@@ -29,57 +23,31 @@ fun AppBarWithBackButtonAndIcon(
onIconClick: (() -> Unit)? = null,
backgroundColor: Color = TangemTheme.colors.background.secondary,
) {
- Row(
- modifier = modifier
- .background(color = backgroundColor)
- .fillMaxWidth()
- .padding(all = TangemTheme.dimens.spacing16),
- horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16),
- verticalAlignment = Alignment.CenterVertically,
- ) {
- Icon(
- painter = painterResource(backIconRes ?: R.drawable.ic_back_24),
- contentDescription = null,
- modifier = Modifier
- .size(size = TangemTheme.dimens.size24)
- .clickable(
- interactionSource = remember { MutableInteractionSource() },
- indication = rememberRipple(bounded = false),
- ) { onBackClick() },
- tint = TangemTheme.colors.icon.primary1,
- )
- AnimatedContent(
- targetState = text,
- modifier = Modifier.weight(1f),
- transitionSpec = { fadeIn().togetherWith(fadeOut()) },
- label = "Toolbar title change",
- ) {
- if (!it.isNullOrBlank()) {
- Text(
- text = it,
- color = TangemTheme.colors.text.primary1,
- maxLines = 1,
- style = TangemTheme.typography.subtitle1,
- )
+ AppBarWithBackButtonAndIconContent(
+ onBackClick = onBackClick,
+ modifier = modifier,
+ text = text,
+ backIconRes = backIconRes,
+ backgroundColor = backgroundColor,
+ iconContent = {
+ AnimatedContent(
+ targetState = iconRes,
+ transitionSpec = { (fadeIn() + scaleIn()).togetherWith(fadeOut() + scaleOut()) },
+ label = "Toolbar icon change",
+ ) {
+ if (onIconClick != null && it != null) {
+ Icon(
+ painter = painterResource(it),
+ contentDescription = null,
+ modifier = Modifier
+ .size(size = TangemTheme.dimens.size24)
+ .clickable { onIconClick() },
+ tint = TangemTheme.colors.icon.primary1,
+ )
+ }
}
- }
- AnimatedContent(
- targetState = iconRes,
- transitionSpec = { (fadeIn() + scaleIn()).togetherWith(fadeOut() + scaleOut()) },
- label = "Toolbar icon change",
- ) {
- if (onIconClick != null && it != null) {
- Icon(
- painter = painterResource(it),
- contentDescription = null,
- modifier = Modifier
- .size(size = TangemTheme.dimens.size24)
- .clickable { onIconClick() },
- tint = TangemTheme.colors.icon.primary1,
- )
- }
- }
- }
+ },
+ )
}
@Preview(widthDp = 360, heightDp = 56, showBackground = true)
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButtonAndIconContent.kt b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButtonAndIconContent.kt
new file mode 100644
index 0000000000..6402068907
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButtonAndIconContent.kt
@@ -0,0 +1,131 @@
+package com.tangem.core.ui.components.appbar
+
+import androidx.annotation.DrawableRes
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.togetherWith
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.*
+import androidx.compose.material.Icon
+import androidx.compose.material.Text
+import androidx.compose.material.ripple.rememberRipple
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
+import com.tangem.core.ui.R
+import com.tangem.core.ui.res.TangemTheme
+
+/**
+ * App bar with back button and icon content for any possible number/style of icons
+ *
+ * @param onBackClick callback for back button
+ * @param modifier modifier
+ * @param text appbar title
+ * @param backIconRes icon for back button
+ * @param backgroundColor background color
+ * @param iconContent icon content
+ */
+@Composable
+fun AppBarWithBackButtonAndIconContent(
+ onBackClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ text: String? = null,
+ @DrawableRes backIconRes: Int? = null,
+ backIconTint: Color = TangemTheme.colors.icon.primary1,
+ backgroundColor: Color = TangemTheme.colors.background.secondary,
+ iconContent: @Composable () -> Unit,
+) {
+ Row(
+ modifier = modifier
+ .background(color = backgroundColor)
+ .fillMaxWidth()
+ .padding(all = TangemTheme.dimens.spacing16),
+ horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Icon(
+ painter = painterResource(backIconRes ?: R.drawable.ic_back_24),
+ contentDescription = null,
+ modifier = Modifier
+ .size(size = TangemTheme.dimens.size24)
+ .clickable(
+ interactionSource = remember { MutableInteractionSource() },
+ indication = rememberRipple(bounded = false),
+ ) { onBackClick() },
+ tint = backIconTint,
+ )
+ AnimatedContent(
+ targetState = text,
+ modifier = Modifier.weight(1f),
+ transitionSpec = { fadeIn().togetherWith(fadeOut()) },
+ label = "Toolbar title change",
+ ) {
+ if (!it.isNullOrBlank()) {
+ Text(
+ text = it,
+ color = TangemTheme.colors.text.primary1,
+ maxLines = 1,
+ style = TangemTheme.typography.subtitle1,
+ )
+ }
+ }
+ iconContent()
+ }
+}
+
+@Preview(widthDp = 360, heightDp = 56, showBackground = true)
+@Composable
+private fun PreviewAppBarWithBackButtonAndIconInLightTheme() {
+ TangemTheme(isDark = false) {
+ AppBarWithBackButtonAndIconContent(
+ text = "Title",
+ onBackClick = {},
+ iconContent = {
+ Row {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_qrcode_scan_24),
+ tint = TangemTheme.colors.icon.primary1,
+ contentDescription = null,
+ )
+ Icon(
+ painter = painterResource(id = R.drawable.ic_flash_on_24),
+ tint = TangemTheme.colors.icon.primary1,
+ contentDescription = null,
+ )
+ }
+ },
+ )
+ }
+}
+
+@Preview(widthDp = 360, heightDp = 56, showBackground = true)
+@Composable
+private fun PreviewAppBarWithBackButtonAndIconInDarkTheme() {
+ TangemTheme(isDark = true) {
+ AppBarWithBackButtonAndIconContent(
+ text = "Title",
+ onBackClick = {},
+ iconContent = {
+ Row {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_qrcode_scan_24),
+ tint = TangemTheme.colors.icon.primary1,
+ contentDescription = null,
+ )
+ Icon(
+ painter = painterResource(id = R.drawable.ic_flash_on_24),
+ tint = TangemTheme.colors.icon.primary1,
+ contentDescription = null,
+ )
+ }
+ },
+ )
+ }
+}
\ No newline at end of file
diff --git a/core/ui/src/main/res/drawable/ic_flash_off_24.xml b/core/ui/src/main/res/drawable/ic_flash_off_24.xml
new file mode 100644
index 0000000000..f7dae29762
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_flash_off_24.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/ic_flash_on_24.xml b/core/ui/src/main/res/drawable/ic_flash_on_24.xml
new file mode 100644
index 0000000000..32fd52a0e2
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_flash_on_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 1d6c8db728..712e2a79ce 100644
--- a/features/qr-scanning/impl/build.gradle.kts
+++ b/features/qr-scanning/impl/build.gradle.kts
@@ -19,6 +19,7 @@ dependencies {
implementation(projects.core.navigation)
implementation(deps.androidx.fragment.ktx)
+ implementation(deps.androidx.activity.compose)
/** Camera */
implementation(deps.camera.camera2)
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 9a339e2d92..17cf43ec3d 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
@@ -16,9 +16,14 @@ class MLKitBarcodeAnalyzer(private val onScanned: (String) -> Unit) : ImageAnaly
@ExperimentalGetImage
override fun analyze(imageProxy: ImageProxy) {
val mediaImage = imageProxy.image
- if (mediaImage != null && !isScanning) {
+ if (mediaImage != null) {
val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
+ analyze(image, imageProxy::close)
+ }
+ }
+ fun analyze(image: InputImage, onClose: (() -> Unit)? = null) {
+ if (!isScanning) {
isScanning = true
scanner.process(image)
.addOnSuccessListener { codes ->
@@ -28,13 +33,12 @@ class MLKitBarcodeAnalyzer(private val onScanned: (String) -> Unit) : ImageAnaly
onScanned.invoke(it)
}
}
-
isScanning = false
- imageProxy.close()
+ onClose?.invoke()
}
.addOnFailureListener {
isScanning = false
- imageProxy.close()
+ onClose?.invoke()
}
}
}
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
index d3526d4e5a..09760ea974 100644
--- 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
@@ -6,6 +6,7 @@ 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.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
@@ -15,7 +16,7 @@ import com.tangem.feature.qrscanning.inner.MLKitBarcodeAnalyzer
import java.util.concurrent.ExecutorService
@Composable
-internal fun CameraView(executor: () -> ExecutorService, analyzer: () -> MLKitBarcodeAnalyzer) {
+internal fun CameraView(executor: () -> ExecutorService, analyzer: () -> MLKitBarcodeAnalyzer, isFlash: Boolean) {
val context = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current
@@ -29,6 +30,10 @@ internal fun CameraView(executor: () -> ExecutorService, analyzer: () -> MLKitBa
imageAnalysisBackpressureStrategy = ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST
}
+ LaunchedEffect(key1 = isFlash) {
+ cameraController.cameraControl?.enableTorch(isFlash)
+ }
+
previewView.controller = cameraController
AndroidView({ previewView }, modifier = Modifier.fillMaxSize())
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 0a7ef5a8ec..8d169703a6 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,10 +1,16 @@
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.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.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.*
+import androidx.compose.material.ripple.rememberRipple
+import androidx.compose.material3.Icon
+import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
@@ -12,12 +18,15 @@ 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.tangem.core.ui.components.appbar.AppBarWithBackButtonAndIcon
+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
import com.tangem.core.ui.res.TangemColorPalette
@@ -35,6 +44,16 @@ 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)
+ }
+ }
+
Box(
modifier = Modifier
.fillMaxSize(),
@@ -42,16 +61,52 @@ internal fun QrScanningContent(
CameraView(
executor = executor,
analyzer = analyzer,
+ isFlash = isFlash,
)
Overlay(
message = uiState.message,
)
- AppBarWithBackButtonAndIcon(
+ AppBarWithBackButtonAndIconContent(
onBackClick = uiState.onBackClick,
- iconRes = R.drawable.ic_gallery_24,
- onIconClick = uiState.onGalleryClicked,
- backgroundColor = Color.Transparent,
modifier = Modifier.statusBarsPadding(),
+ backgroundColor = Color.Transparent,
+ backIconTint = TangemColorPalette.White,
+ iconContent = {
+ Row {
+ AnimatedContent(
+ targetState = isFlash,
+ transitionSpec = { fadeIn().togetherWith(fadeOut()) },
+ label = "Flash Change",
+ ) {
+ val flashIconRes = if (it) R.drawable.ic_flash_on_24 else R.drawable.ic_flash_off_24
+ Icon(
+ painter = painterResource(flashIconRes),
+ contentDescription = null,
+ modifier = Modifier
+ .padding(end = TangemTheme.dimens.spacing20)
+ .size(size = TangemTheme.dimens.size24)
+ .clickable(
+ interactionSource = remember { MutableInteractionSource() },
+ indication = rememberRipple(bounded = false),
+ onClick = { isFlash = !isFlash },
+ ),
+ tint = TangemColorPalette.White,
+ )
+ }
+ Icon(
+ painter = painterResource(R.drawable.ic_gallery_24),
+ contentDescription = null,
+ modifier = Modifier
+ .size(size = TangemTheme.dimens.size24)
+ .clickable(
+ interactionSource = remember { MutableInteractionSource() },
+ indication = rememberRipple(bounded = false),
+ onClick = { galleryLauncher.launch("image/*") },
+ ),
+ tint = TangemColorPalette.White,
+ )
+ }
+ },
)
}
}