speedup + minor fixes
This commit is contained in:
+1
-1
@@ -3,7 +3,7 @@
|
||||
## Quick Wins (1-3 days)
|
||||
|
||||
- [x] Duplicate UX polish
|
||||
- Add subtle in-app banner/snackbar (in addition to toast) with optional "View in history".
|
||||
- Add subtle in-app banner/snack bar (in addition to toast) with optional "View in history".
|
||||
|
||||
- [x] Batch mode polish
|
||||
- Add per-item copy/share in batch panel.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.clean.scanner.data.scanner
|
||||
|
||||
import android.graphics.Rect
|
||||
import android.os.SystemClock
|
||||
import androidx.camera.core.ImageAnalysis
|
||||
import androidx.camera.core.ImageProxy
|
||||
import com.clean.scanner.domain.ScanResult
|
||||
@@ -47,6 +48,8 @@ class MlKitBarcodeAnalyzer(
|
||||
const val MATCH_DISTANCE_THRESHOLD = 0.18f
|
||||
const val BOX_SMOOTHING_ALPHA = 0.35f
|
||||
const val MAX_MISSED_FRAMES = 2
|
||||
const val MIN_ANALYSIS_INTERVAL_MS = 45L
|
||||
const val MIN_STATE_PUBLISH_INTERVAL_MS = 66L
|
||||
}
|
||||
|
||||
private val scanner = BarcodeScanning.getClient(
|
||||
@@ -58,6 +61,10 @@ class MlKitBarcodeAnalyzer(
|
||||
@Volatile
|
||||
private var processing = false
|
||||
@Volatile
|
||||
private var lastAnalysisStartMs = 0L
|
||||
@Volatile
|
||||
private var lastStatePublishMs = 0L
|
||||
@Volatile
|
||||
private var lastHasPotential = false
|
||||
@Volatile
|
||||
private var lastHasReadable = false
|
||||
@@ -86,6 +93,11 @@ class MlKitBarcodeAnalyzer(
|
||||
imageProxy.close()
|
||||
return
|
||||
}
|
||||
val nowMs = SystemClock.elapsedRealtime()
|
||||
if (nowMs - lastAnalysisStartMs < MIN_ANALYSIS_INTERVAL_MS) {
|
||||
imageProxy.close()
|
||||
return
|
||||
}
|
||||
if (processing) {
|
||||
imageProxy.close()
|
||||
return
|
||||
@@ -100,6 +112,7 @@ class MlKitBarcodeAnalyzer(
|
||||
val sourceHeight = if (rotationDegrees == 90 || rotationDegrees == 270) imageProxy.width else imageProxy.height
|
||||
val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
|
||||
processing = true
|
||||
lastAnalysisStartMs = nowMs
|
||||
|
||||
scanner.process(image)
|
||||
.addOnSuccessListener { barcodes ->
|
||||
@@ -210,6 +223,7 @@ class MlKitBarcodeAnalyzer(
|
||||
sourceWidth: Int,
|
||||
sourceHeight: Int
|
||||
) {
|
||||
val nowMs = SystemClock.elapsedRealtime()
|
||||
if (
|
||||
lastHasPotential == hasPotential &&
|
||||
lastHasReadable == hasReadable &&
|
||||
@@ -217,11 +231,19 @@ class MlKitBarcodeAnalyzer(
|
||||
lastSourceWidth == sourceWidth &&
|
||||
lastSourceHeight == sourceHeight
|
||||
) return
|
||||
val isStateKindUnchanged = lastHasPotential == hasPotential &&
|
||||
lastHasReadable == hasReadable &&
|
||||
lastSourceWidth == sourceWidth &&
|
||||
lastSourceHeight == sourceHeight
|
||||
if (isStateKindUnchanged && nowMs - lastStatePublishMs < MIN_STATE_PUBLISH_INTERVAL_MS) {
|
||||
return
|
||||
}
|
||||
lastHasPotential = hasPotential
|
||||
lastHasReadable = hasReadable
|
||||
lastBoxes = boxes
|
||||
lastSourceWidth = sourceWidth
|
||||
lastSourceHeight = sourceHeight
|
||||
lastStatePublishMs = nowMs
|
||||
onDetectionStateChanged(hasPotential, hasReadable, boxes, sourceWidth, sourceHeight)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.clean.scanner.ui.components
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.util.Size
|
||||
import android.view.MotionEvent
|
||||
import android.view.ScaleGestureDetector
|
||||
import androidx.camera.core.CameraSelector
|
||||
@@ -12,6 +13,7 @@ import androidx.camera.view.PreviewView
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
@@ -49,7 +51,7 @@ fun CameraPreview(
|
||||
val mainExecutor = remember(context) { ContextCompat.getMainExecutor(context) }
|
||||
val previewView = remember { PreviewView(context) }
|
||||
val cameraRef = remember { mutableStateOf<androidx.camera.core.Camera?>(null) }
|
||||
val zoomRatio = remember { mutableStateOf(1f) }
|
||||
val zoomRatio = remember { mutableFloatStateOf(1f) }
|
||||
val latestAnalysisEnabled = rememberUpdatedState(analysisEnabled)
|
||||
val latestOnScan = rememberUpdatedState(onScan)
|
||||
val latestOnDetectionStateChanged = rememberUpdatedState(onDetectionStateChanged)
|
||||
@@ -107,6 +109,7 @@ fun CameraPreview(
|
||||
|
||||
val imageAnalysis = ImageAnalysis.Builder()
|
||||
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
||||
.setTargetResolution(Size(1280, 720))
|
||||
.build().apply {
|
||||
setAnalyzer(cameraExecutor, analyzer)
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import androidx.compose.foundation.gestures.detectTransformGestures
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
@@ -55,6 +54,8 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
@@ -62,6 +63,7 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.CornerRadius
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Path
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
@@ -85,6 +87,7 @@ import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.clean.scanner.R
|
||||
import com.clean.scanner.data.scanner.DetectionBox
|
||||
import com.clean.scanner.data.scanner.DetectionPoint
|
||||
import com.clean.scanner.domain.ScanResult
|
||||
import com.clean.scanner.ui.BatchScanRecord
|
||||
import com.clean.scanner.ui.components.CameraPreview
|
||||
@@ -93,6 +96,7 @@ import com.clean.scanner.util.Intents
|
||||
import com.clean.scanner.util.ScanContentParsers
|
||||
import com.clean.scanner.util.UrlRiskScorer
|
||||
import com.google.mlkit.vision.barcode.BarcodeScanning
|
||||
import com.google.mlkit.vision.barcode.BarcodeScanner
|
||||
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
|
||||
import com.google.mlkit.vision.barcode.common.Barcode
|
||||
import com.google.mlkit.vision.common.InputImage
|
||||
@@ -150,8 +154,8 @@ fun ScannerScreen(
|
||||
var hasPotentialInView by remember { mutableStateOf(false) }
|
||||
var hasReadableInView by remember { mutableStateOf(false) }
|
||||
var detectionBoxes by remember { mutableStateOf<List<DetectionBox>>(emptyList()) }
|
||||
var detectionSourceWidth by remember { mutableStateOf(0) }
|
||||
var detectionSourceHeight by remember { mutableStateOf(0) }
|
||||
var detectionSourceWidth by remember { mutableIntStateOf(0) }
|
||||
var detectionSourceHeight by remember { mutableIntStateOf(0) }
|
||||
val activity = context as? Activity
|
||||
val imageScanner = remember {
|
||||
BarcodeScanning.getClient(
|
||||
@@ -193,7 +197,7 @@ fun ScannerScreen(
|
||||
val raw = barcode.rawValue?.takeIf { it.isNotBlank() } ?: return@mapNotNull null
|
||||
val normalizedBox = barcode.boundingBox?.let { bounds ->
|
||||
val corners = barcode.cornerPoints?.map { p ->
|
||||
com.clean.scanner.data.scanner.DetectionPoint(
|
||||
DetectionPoint(
|
||||
x = (p.x / image.width.toFloat()).coerceIn(0f, 1f),
|
||||
y = (p.y / image.height.toFloat()).coerceIn(0f, 1f)
|
||||
)
|
||||
@@ -254,10 +258,16 @@ fun ScannerScreen(
|
||||
}
|
||||
}
|
||||
|
||||
BoxWithConstraints(modifier = Modifier.fillMaxSize()) {
|
||||
var containerSize by remember { mutableStateOf(IntSize.Zero) }
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.onSizeChanged { containerSize = it }
|
||||
) {
|
||||
val density = LocalDensity.current
|
||||
val viewW = with(density) { maxWidth.toPx() }
|
||||
val viewH = with(density) { maxHeight.toPx() }
|
||||
val viewW = containerSize.width.toFloat()
|
||||
val viewH = containerSize.height.toFloat()
|
||||
val galleryOpen = imageScanPreviewUri != null
|
||||
val aimW = viewW * 0.62f
|
||||
val aimH = with(density) { 200.dp.toPx() }
|
||||
@@ -324,7 +334,7 @@ fun ScannerScreen(
|
||||
val right = offsetX + (box.right * sourceW * scale)
|
||||
val bottom = offsetY + (box.bottom * sourceH * scale)
|
||||
val mappedCorners = box.corners.map { p ->
|
||||
androidx.compose.ui.geometry.Offset(
|
||||
Offset(
|
||||
x = offsetX + (p.x * sourceW * scale),
|
||||
y = offsetY + (p.y * sourceH * scale)
|
||||
)
|
||||
@@ -343,8 +353,8 @@ fun ScannerScreen(
|
||||
} else if (right > left && bottom > top) {
|
||||
drawRoundRect(
|
||||
color = boxColor.copy(alpha = 0.95f),
|
||||
topLeft = androidx.compose.ui.geometry.Offset(left, top),
|
||||
size = androidx.compose.ui.geometry.Size(right - left, bottom - top),
|
||||
topLeft = Offset(left, top),
|
||||
size = Size(right - left, bottom - top),
|
||||
cornerRadius = CornerRadius(14f, 14f),
|
||||
style = Stroke(width = 4f)
|
||||
)
|
||||
@@ -750,7 +760,7 @@ private fun loadBitmapFromUri(context: android.content.Context, uri: Uri): Bitma
|
||||
}
|
||||
|
||||
private suspend fun detectBarcodes(
|
||||
scanner: com.google.mlkit.vision.barcode.BarcodeScanner,
|
||||
scanner: BarcodeScanner,
|
||||
image: InputImage
|
||||
): List<Barcode> = suspendCancellableCoroutine { cont ->
|
||||
scanner.process(image)
|
||||
@@ -772,10 +782,10 @@ private fun GalleryScanPreviewDialog(
|
||||
val context = LocalContext.current
|
||||
val bitmap = remember(imageUri) { imageUri?.let { loadBitmapFromUri(context, it) } }
|
||||
var liveCandidates by remember(imageUri, candidates) { mutableStateOf(candidates) }
|
||||
var zoom by remember(imageUri) { mutableStateOf(1f) }
|
||||
var zoom by remember(imageUri) { mutableFloatStateOf(1f) }
|
||||
var pan by remember(imageUri) { mutableStateOf(Offset.Zero) }
|
||||
var viewportSize by remember { mutableStateOf(IntSize.Zero) }
|
||||
var scanTick by remember { mutableStateOf(0) }
|
||||
var scanTick by remember { mutableIntStateOf(0) }
|
||||
val markerPaint = remember {
|
||||
Paint().apply {
|
||||
color = android.graphics.Color.WHITE
|
||||
@@ -850,7 +860,7 @@ private fun GalleryScanPreviewDialog(
|
||||
val rightN = ((bounds.right + cropLeft) / imgW).coerceIn(0f, 1f)
|
||||
val bottomN = ((bounds.bottom + cropTop) / imgH).coerceIn(0f, 1f)
|
||||
val corners = barcode.cornerPoints?.map { p ->
|
||||
com.clean.scanner.data.scanner.DetectionPoint(
|
||||
DetectionPoint(
|
||||
x = ((p.x + cropLeft) / imgW).coerceIn(0f, 1f),
|
||||
y = ((p.y + cropTop) / imgH).coerceIn(0f, 1f)
|
||||
)
|
||||
@@ -952,8 +962,8 @@ private fun GalleryScanPreviewDialog(
|
||||
if (right > left && bottom > top) {
|
||||
drawRoundRect(
|
||||
color = color,
|
||||
topLeft = androidx.compose.ui.geometry.Offset(left, top),
|
||||
size = androidx.compose.ui.geometry.Size(right - left, bottom - top),
|
||||
topLeft = Offset(left, top),
|
||||
size = Size(right - left, bottom - top),
|
||||
cornerRadius = CornerRadius(10f, 10f),
|
||||
style = Stroke(width = 4f)
|
||||
)
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
plugins {
|
||||
id("com.android.application") version "9.0.0" apply false
|
||||
id("com.android.application") version "9.0.1" apply false
|
||||
id("com.google.devtools.ksp") version "2.3.5" apply false
|
||||
id("org.jetbrains.kotlin.plugin.compose") version "2.3.10" apply false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user