From aaf817dd902ab8dc1df59ac56009522b60496817 Mon Sep 17 00:00:00 2001 From: Fabi019 Date: Sat, 9 Nov 2024 15:55:08 +0100 Subject: [PATCH 1/3] Initial concept for custom selection --- .../fabik/bluetoothhid/ui/CameraPreview.kt | 131 ++++++++++++++++++ .../bluetoothhid/ui/model/CameraViewModel.kt | 34 ++++- app/src/main/res/values-de-rDE/strings.xml | 1 + app/src/main/res/values-pl/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 5 files changed, 164 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/dev/fabik/bluetoothhid/ui/CameraPreview.kt b/app/src/main/java/dev/fabik/bluetoothhid/ui/CameraPreview.kt index af821f3..104443b 100644 --- a/app/src/main/java/dev/fabik/bluetoothhid/ui/CameraPreview.kt +++ b/app/src/main/java/dev/fabik/bluetoothhid/ui/CameraPreview.kt @@ -18,16 +18,23 @@ import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.animation.core.updateTransition import androidx.compose.foundation.Canvas +import androidx.compose.foundation.gestures.detectDragGestures import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.offset import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.DragIndicator import androidx.compose.material.icons.filled.Error +import androidx.compose.material.icons.filled.OpenInFull import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -47,8 +54,10 @@ import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.drawscope.clipPath import androidx.compose.ui.graphics.nativeCanvas import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.viewinterop.AndroidView import androidx.core.content.ContextCompat import androidx.lifecycle.Lifecycle @@ -68,6 +77,7 @@ import dev.fabik.bluetoothhid.utils.rememberPreference import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import java.util.concurrent.Executors +import kotlin.math.roundToInt @Composable @@ -368,6 +378,15 @@ fun CameraViewModel.OverlayCanvas() { ) } + 2 -> { + val pos = userSpecifiedOffset + val size = userSpecifiedSize + Rect( + pos + Offset(x - size.width, y - size.height), + pos + Offset(x + size.width, y + size.height) + ) + } + // Square for scanning qr codes else -> { val length = if (landscape) this.size.height * 0.6f else this.size.width * 0.8f @@ -418,6 +437,41 @@ fun CameraViewModel.OverlayCanvas() { } drawPath(path, color = Color.Blue, style = Stroke(5f)) + + // Draw horizontal line + points?.let { + val leftMiddle = + Offset((it[0].first + it[3].first) / 2f, (it[0].second + it[3].second) / 2f) + val rightMiddle = + Offset((it[1].first + it[2].first) / 2f, (it[1].second + it[2].second) / 2f) + val hDist = (rightMiddle - leftMiddle).getDistanceSquared() + + val topMiddle = + Offset((it[0].first + it[1].first) / 2f, (it[0].second + it[1].second) / 2f) + val bottomMiddle = + Offset((it[3].first + it[2].first) / 2f, (it[3].second + it[2].second) / 2f) + val vDist = (bottomMiddle - topMiddle).getDistanceSquared() + + val relDiff = (hDist - vDist) / (hDist + vDist) + if (relDiff > 0.05) { + drawLine( + Color.Red, + leftMiddle, + rightMiddle + ) + } else if (relDiff < -0.05) { + drawLine( + Color.Red, + topMiddle, + bottomMiddle + ) + } + +// drawCircle(Color.Yellow, 10f, leftMiddle) +// drawCircle(Color.Green, 10f, rightMiddle) +// drawCircle(Color.Cyan, 10f, topMiddle) +// drawCircle(Color.Magenta, 10f, bottomMiddle) + } } // Draw the focus circle if currently focusing @@ -438,6 +492,72 @@ fun CameraViewModel.OverlayCanvas() { drawDebugOverlay(drawContext.canvas.nativeCanvas, this.size) } } + + // Show the adjust buttons + if (restrictArea && overlayType == 2) { + TransformableSample() + } +} + +@Composable +private fun CameraViewModel.TransformableSample() { + var posOffsetX by remember { mutableFloatStateOf(userSpecifiedOffset.x) } + var posOffsetY by remember { mutableFloatStateOf(userSpecifiedOffset.x) } + var sizeOffsetX by remember { mutableFloatStateOf(userSpecifiedSize.width) } + var sizeOffsetY by remember { mutableFloatStateOf(userSpecifiedSize.height) } + + fun reset() { + posOffsetX = 0f + posOffsetY = 0f + userSpecifiedOffset = Offset(posOffsetX, posOffsetY) + sizeOffsetX = 200f + sizeOffsetY = 200f + userSpecifiedSize = Size(sizeOffsetX, sizeOffsetY) + } + + IconButton( + onClick = { reset() }, + colors = IconButtonDefaults.iconButtonColors(Color.Black.copy(alpha = 0.5f)), + modifier = Modifier + .offset { + IntOffset( + (posOffsetX + sizeOffsetX).roundToInt(), + (posOffsetY + sizeOffsetY).roundToInt() + ) + } + .pointerInput(Unit) { + detectDragGestures { change, dragAmount -> + change.consume() + sizeOffsetX += dragAmount.x + sizeOffsetY += dragAmount.y + userSpecifiedSize = Size(sizeOffsetX, sizeOffsetY) + } + } + ) { + Icon(Icons.Default.OpenInFull, "Modify size") + } + + IconButton( + onClick = { reset() }, + colors = IconButtonDefaults.iconButtonColors(Color.Black.copy(alpha = 0.5f)), + modifier = Modifier + .offset { + IntOffset( + (posOffsetX - sizeOffsetX).roundToInt(), + (posOffsetY + sizeOffsetY).roundToInt() + ) + } + .pointerInput(Unit) { + detectDragGestures { change, dragAmount -> + change.consume() + posOffsetX += dragAmount.x + posOffsetY += dragAmount.y + userSpecifiedOffset = Offset(posOffsetX, posOffsetY) + } + } + ) { + Icon(Icons.Default.DragIndicator, "Modify position") + } } fun CameraViewModel.drawDebugOverlay(canvas: NativeCanvas, size: Size) { @@ -488,6 +608,17 @@ fun CameraViewModel.drawDebugOverlay(canvas: NativeCanvas, size: Size) { } ) + // Draw the custom selection + canvas.drawText( + "Selector size: ${userSpecifiedSize.width.roundToInt()}x${userSpecifiedSize.height.roundToInt()} at (${userSpecifiedOffset.x.roundToInt()}, ${userSpecifiedOffset.y.roundToInt()})", + 10f, + y + 200f, + Paint().apply { + color = Color.White.toArgb() + textSize = 50f + } + ) + // Draw the histogram fun drawHistogram(values: Iterable, increment: Float, paint: Paint) { val path = android.graphics.Path() diff --git a/app/src/main/java/dev/fabik/bluetoothhid/ui/model/CameraViewModel.kt b/app/src/main/java/dev/fabik/bluetoothhid/ui/model/CameraViewModel.kt index 98da51a..b0beee1 100644 --- a/app/src/main/java/dev/fabik/bluetoothhid/ui/model/CameraViewModel.kt +++ b/app/src/main/java/dev/fabik/bluetoothhid/ui/model/CameraViewModel.kt @@ -38,6 +38,9 @@ class CameraViewModel : ViewModel() { var lastSourceRes: Size? = null var lastPreviewRes: Size? = null + var userSpecifiedOffset = Offset.Zero + var userSpecifiedSize = androidx.compose.ui.geometry.Size(200f, 200f) + // TODO: remove - no longer used for transformation var scale = 1f var transX = 0f @@ -104,16 +107,39 @@ class CameraViewModel : ViewModel() { // Filter out codes without value it.rawBytes != null && !it.rawValue.isNullOrEmpty() && !it.displayValue.isNullOrEmpty() }.filter { - // Filter if they are within the scan area - it.cornerPoints?.map { p -> + val points = it.cornerPoints?.map { p -> Offset(p.x * scale - transX, p.y * scale - transY) - }?.forEach { o -> + } + + // Filter if the edge points are within the scan area + points?.forEach { o -> if (fullyInside && !scanRect.contains(o)) { return@filter false - } else if (scanRect.contains(o)) { + } + if (!fullyInside && scanRect.contains(o)) { return@filter true } } + + // Check if the center lines are included + points?.let { + if (!fullyInside) { + val leftMiddle = (it[0] + it[3]) / 2f + val rightMiddle = (it[1] + it[2]) / 2f + + if (scanRect.contains(leftMiddle) && scanRect.contains(rightMiddle)) { + return@filter true + } + + val topMiddle = (it[0] + it[1]) / 2f + val bottomMiddle = (it[3] + it[2]) / 2f + + if (scanRect.contains(topMiddle) && scanRect.contains(bottomMiddle)) { + return@filter true + } + } + } + fullyInside }.filter { // Filter by regex diff --git a/app/src/main/res/values-de-rDE/strings.xml b/app/src/main/res/values-de-rDE/strings.xml index fa2ac6e..fc8205e 100644 --- a/app/src/main/res/values-de-rDE/strings.xml +++ b/app/src/main/res/values-de-rDE/strings.xml @@ -114,6 +114,7 @@ Quadratisch (QR-Code) Rechteckig (Barcode) + Benutzerdefiniert Am Schnellsten diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index c0a12df..ad02211 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -201,6 +201,7 @@ Kwadratowa (Kody QR) ProstokÄ…tna (Kody kreskowe) + Custom Najszybciej diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0a46e13..15f18dc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -200,6 +200,7 @@ Square (QR-Code) Rectangle (Barcode) + Custom Fastest From 123d0dd60f08728192ea5f12ccffec1c8befcf76 Mon Sep 17 00:00:00 2001 From: Fabi019 Date: Sat, 16 Nov 2024 12:51:26 +0100 Subject: [PATCH 2/3] Persist custom overlay size --- .../fabik/bluetoothhid/ui/CameraPreview.kt | 104 +++++++++++++----- .../bluetoothhid/ui/model/CameraViewModel.kt | 4 +- .../bluetoothhid/utils/PreferenceStore.kt | 6 + 3 files changed, 86 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/dev/fabik/bluetoothhid/ui/CameraPreview.kt b/app/src/main/java/dev/fabik/bluetoothhid/ui/CameraPreview.kt index 104443b..ebd5e07 100644 --- a/app/src/main/java/dev/fabik/bluetoothhid/ui/CameraPreview.kt +++ b/app/src/main/java/dev/fabik/bluetoothhid/ui/CameraPreview.kt @@ -19,8 +19,8 @@ import androidx.compose.animation.core.tween import androidx.compose.animation.core.updateTransition import androidx.compose.foundation.Canvas import androidx.compose.foundation.gestures.detectDragGestures +import androidx.compose.foundation.layout.absoluteOffset import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.offset import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.DragIndicator import androidx.compose.material.icons.filled.Error @@ -74,8 +74,11 @@ import dev.fabik.bluetoothhid.utils.PreferenceStore import dev.fabik.bluetoothhid.utils.RequiresModuleInstallation import dev.fabik.bluetoothhid.utils.getPreference import dev.fabik.bluetoothhid.utils.rememberPreference +import dev.fabik.bluetoothhid.utils.setPreference +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import java.util.concurrent.Executors import kotlin.math.roundToInt @@ -379,12 +382,15 @@ fun CameraViewModel.OverlayCanvas() { } 2 -> { - val pos = userSpecifiedOffset - val size = userSpecifiedSize - Rect( - pos + Offset(x - size.width, y - size.height), - pos + Offset(x + size.width, y + size.height) - ) + val pos = overlayPosition + val size = overlaySize + + if (pos != null && size != null) + Rect( + pos + Offset(x - size.width, y - size.height), + pos + Offset(x + size.width, y + size.height) + ) + else Rect.Zero } // Square for scanning qr codes @@ -495,42 +501,86 @@ fun CameraViewModel.OverlayCanvas() { // Show the adjust buttons if (restrictArea && overlayType == 2) { - TransformableSample() + CustomOverlayButtons() } } @Composable -private fun CameraViewModel.TransformableSample() { - var posOffsetX by remember { mutableFloatStateOf(userSpecifiedOffset.x) } - var posOffsetY by remember { mutableFloatStateOf(userSpecifiedOffset.x) } - var sizeOffsetX by remember { mutableFloatStateOf(userSpecifiedSize.width) } - var sizeOffsetY by remember { mutableFloatStateOf(userSpecifiedSize.height) } +private fun CameraViewModel.CustomOverlayButtons() { + val context = LocalContext.current + var posOffsetX by remember { + mutableFloatStateOf(runBlocking { + context.getPreference( + PreferenceStore.OVERLAY_POS_X + ).first() + }) + } + var posOffsetY by remember { + mutableFloatStateOf(runBlocking { + context.getPreference( + PreferenceStore.OVERLAY_POS_Y + ).first() + }) + } + var sizeOffsetX by remember { + mutableFloatStateOf(runBlocking { + context.getPreference( + PreferenceStore.OVERLAY_WIDTH + ).first() + }) + } + var sizeOffsetY by remember { + mutableFloatStateOf(runBlocking { + context.getPreference( + PreferenceStore.OVERLAY_HEIGHT + ).first() + }) + } + + LaunchedEffect(Unit) { + overlayPosition = Offset(posOffsetX, posOffsetY) + overlaySize = Size(sizeOffsetX, sizeOffsetY) + } + + fun saveState() { + runBlocking { + context.setPreference(PreferenceStore.OVERLAY_POS_X, posOffsetX) + context.setPreference(PreferenceStore.OVERLAY_POS_Y, posOffsetY) + context.setPreference(PreferenceStore.OVERLAY_WIDTH, sizeOffsetX) + context.setPreference(PreferenceStore.OVERLAY_HEIGHT, sizeOffsetY) + } + } fun reset() { - posOffsetX = 0f - posOffsetY = 0f - userSpecifiedOffset = Offset(posOffsetX, posOffsetY) - sizeOffsetX = 200f - sizeOffsetY = 200f - userSpecifiedSize = Size(sizeOffsetX, sizeOffsetY) + posOffsetX = PreferenceStore.OVERLAY_POS_X.defaultValue + posOffsetY = PreferenceStore.OVERLAY_POS_Y.defaultValue + sizeOffsetX = PreferenceStore.OVERLAY_WIDTH.defaultValue + sizeOffsetY = PreferenceStore.OVERLAY_HEIGHT.defaultValue + + overlayPosition = Offset(posOffsetX, posOffsetY) + overlaySize = Size(sizeOffsetX, sizeOffsetY) + + saveState() } IconButton( onClick = { reset() }, colors = IconButtonDefaults.iconButtonColors(Color.Black.copy(alpha = 0.5f)), modifier = Modifier - .offset { + .absoluteOffset { IntOffset( (posOffsetX + sizeOffsetX).roundToInt(), (posOffsetY + sizeOffsetY).roundToInt() ) } .pointerInput(Unit) { - detectDragGestures { change, dragAmount -> + detectDragGestures(onDragEnd = { + saveState() + }) { change, dragAmount -> change.consume() sizeOffsetX += dragAmount.x sizeOffsetY += dragAmount.y - userSpecifiedSize = Size(sizeOffsetX, sizeOffsetY) + overlaySize = Size(sizeOffsetX, sizeOffsetY) } } ) { @@ -541,18 +591,20 @@ private fun CameraViewModel.TransformableSample() { onClick = { reset() }, colors = IconButtonDefaults.iconButtonColors(Color.Black.copy(alpha = 0.5f)), modifier = Modifier - .offset { + .absoluteOffset { IntOffset( (posOffsetX - sizeOffsetX).roundToInt(), (posOffsetY + sizeOffsetY).roundToInt() ) } .pointerInput(Unit) { - detectDragGestures { change, dragAmount -> + detectDragGestures(onDragEnd = { + saveState() + }) { change, dragAmount -> change.consume() posOffsetX += dragAmount.x posOffsetY += dragAmount.y - userSpecifiedOffset = Offset(posOffsetX, posOffsetY) + overlayPosition = Offset(posOffsetX, posOffsetY) } } ) { @@ -610,7 +662,7 @@ fun CameraViewModel.drawDebugOverlay(canvas: NativeCanvas, size: Size) { // Draw the custom selection canvas.drawText( - "Selector size: ${userSpecifiedSize.width.roundToInt()}x${userSpecifiedSize.height.roundToInt()} at (${userSpecifiedOffset.x.roundToInt()}, ${userSpecifiedOffset.y.roundToInt()})", + "Selector size: ${overlaySize?.width?.roundToInt()}x${overlaySize?.height?.roundToInt()} at (${overlayPosition?.x?.roundToInt()}, ${overlayPosition?.y?.roundToInt()})", 10f, y + 200f, Paint().apply { diff --git a/app/src/main/java/dev/fabik/bluetoothhid/ui/model/CameraViewModel.kt b/app/src/main/java/dev/fabik/bluetoothhid/ui/model/CameraViewModel.kt index b0beee1..4920e2a 100644 --- a/app/src/main/java/dev/fabik/bluetoothhid/ui/model/CameraViewModel.kt +++ b/app/src/main/java/dev/fabik/bluetoothhid/ui/model/CameraViewModel.kt @@ -38,8 +38,8 @@ class CameraViewModel : ViewModel() { var lastSourceRes: Size? = null var lastPreviewRes: Size? = null - var userSpecifiedOffset = Offset.Zero - var userSpecifiedSize = androidx.compose.ui.geometry.Size(200f, 200f) + var overlayPosition: Offset? = null + var overlaySize: androidx.compose.ui.geometry.Size? = null // TODO: remove - no longer used for transformation var scale = 1f diff --git a/app/src/main/java/dev/fabik/bluetoothhid/utils/PreferenceStore.kt b/app/src/main/java/dev/fabik/bluetoothhid/utils/PreferenceStore.kt index 0947692..7053b64 100644 --- a/app/src/main/java/dev/fabik/bluetoothhid/utils/PreferenceStore.kt +++ b/app/src/main/java/dev/fabik/bluetoothhid/utils/PreferenceStore.kt @@ -82,6 +82,12 @@ open class PreferenceStore { val PRIVATE_MODE = booleanPreferencesKey("private_mode") defaultsTo false val DEVELOPER_MODE = booleanPreferencesKey("developer_mode") defaultsTo BuildConfig.DEBUG + + // Utility preferences + val OVERLAY_POS_X = floatPreferencesKey("overlay_pos_x") defaultsTo 0.0f + val OVERLAY_POS_Y = floatPreferencesKey("overlay_pos_y") defaultsTo 0.0f + val OVERLAY_WIDTH = floatPreferencesKey("overlay_width") defaultsTo 100.0f + val OVERLAY_HEIGHT = floatPreferencesKey("overlay_height") defaultsTo 100.0f } } From 9ecaa0c5b51346beb5ae3b4b55b2ef5e5643e5af Mon Sep 17 00:00:00 2001 From: Fabi019 Date: Thu, 21 Nov 2024 19:44:12 +0100 Subject: [PATCH 3/3] Fix contains check with reversed rectangles --- .../fabik/bluetoothhid/ui/CameraPreview.kt | 62 +++++++++++++------ .../bluetoothhid/ui/model/CameraViewModel.kt | 4 +- 2 files changed, 45 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/dev/fabik/bluetoothhid/ui/CameraPreview.kt b/app/src/main/java/dev/fabik/bluetoothhid/ui/CameraPreview.kt index ebd5e07..6e0b1a5 100644 --- a/app/src/main/java/dev/fabik/bluetoothhid/ui/CameraPreview.kt +++ b/app/src/main/java/dev/fabik/bluetoothhid/ui/CameraPreview.kt @@ -59,10 +59,11 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.viewinterop.AndroidView -import androidx.core.content.ContextCompat import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.viewmodel.compose.viewModel +import com.google.common.util.concurrent.FutureCallback +import com.google.common.util.concurrent.Futures import com.google.mlkit.vision.barcode.BarcodeScannerOptions import com.google.mlkit.vision.barcode.ZoomSuggestionOptions import dev.fabik.bluetoothhid.LocalJsEngineService @@ -80,6 +81,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import java.util.concurrent.Executors +import kotlin.math.absoluteValue import kotlin.math.roundToInt @@ -251,6 +253,9 @@ fun CameraViewModel.CameraPreview( runCatching { cameraController.bindToLifecycle(lifecycleOwner) + + // Enable only the image analysis use case + cameraController.setEnabledUseCases(CameraController.IMAGE_ANALYSIS) }.onFailure { Log.e("CameraPreview", "Failed to bind camera", it) errorDialog.open() @@ -266,23 +271,36 @@ fun CameraViewModel.CameraPreview( Log.d("CameraPreview", "Focusing: $isFocusing ($it)") } - cameraController.initializationFuture.addListener({ - // Enable only the image analysis use case - cameraController.setEnabledUseCases(CameraController.IMAGE_ANALYSIS) - - // Attach PreviewView after we know the camera is available. - previewView.controller = cameraController - previewView.setOnTouchListener { _, event -> - if (event.action == MotionEvent.ACTION_DOWN) - focusTouchPoint = Offset(event.x, event.y) - false - } - - // Camera is ready - onCameraReady(cameraController) + Futures.addCallback( + cameraController.initializationFuture, + object : FutureCallback { + override fun onSuccess(result: Void?) { + runCatching { + // Attach PreviewView after we know the camera is available. + previewView.controller = cameraController + previewView.setOnTouchListener { _, event -> + if (event.action == MotionEvent.ACTION_DOWN) + focusTouchPoint = Offset(event.x, event.y) + false + } + + // Camera is ready + onCameraReady(cameraController) + + initialized = true + }.onFailure { + Log.e("CameraPreview", "Failed to bind preview", it) + errorDialog.open() + } + } - initialized = true - }, ContextCompat.getMainExecutor(context)) + override fun onFailure(t: Throwable) { + Log.e("CameraPreview", "Failed to initialize camera", t) + errorDialog.open() + } + }, + context.mainExecutor + ) } Lifecycle.Event.ON_PAUSE -> { @@ -387,8 +405,14 @@ fun CameraViewModel.OverlayCanvas() { if (pos != null && size != null) Rect( - pos + Offset(x - size.width, y - size.height), - pos + Offset(x + size.width, y + size.height) + pos + Offset( + x - size.width.absoluteValue, + y - size.height.absoluteValue + ), + pos + Offset( + x + size.width.absoluteValue, + y + size.height.absoluteValue + ) ) else Rect.Zero } diff --git a/app/src/main/java/dev/fabik/bluetoothhid/ui/model/CameraViewModel.kt b/app/src/main/java/dev/fabik/bluetoothhid/ui/model/CameraViewModel.kt index 4920e2a..6cca136 100644 --- a/app/src/main/java/dev/fabik/bluetoothhid/ui/model/CameraViewModel.kt +++ b/app/src/main/java/dev/fabik/bluetoothhid/ui/model/CameraViewModel.kt @@ -38,8 +38,8 @@ class CameraViewModel : ViewModel() { var lastSourceRes: Size? = null var lastPreviewRes: Size? = null - var overlayPosition: Offset? = null - var overlaySize: androidx.compose.ui.geometry.Size? = null + var overlayPosition by mutableStateOf(null) + var overlaySize by mutableStateOf(null) // TODO: remove - no longer used for transformation var scale = 1f