@@ -3,6 +3,13 @@ package com.ismartcoding.plain.ui.page.scan
33import android.annotation.SuppressLint
44import android.content.Context
55import androidx.camera.lifecycle.ProcessCameraProvider
6+ import androidx.compose.animation.core.LinearEasing
7+ import androidx.compose.animation.core.RepeatMode
8+ import androidx.compose.animation.core.animateFloat
9+ import androidx.compose.animation.core.infiniteRepeatable
10+ import androidx.compose.animation.core.rememberInfiniteTransition
11+ import androidx.compose.animation.core.tween
12+ import androidx.compose.foundation.Canvas
613import androidx.compose.foundation.background
714import androidx.compose.foundation.clickable
815import androidx.compose.foundation.layout.Arrangement
@@ -31,6 +38,9 @@ import androidx.compose.runtime.setValue
3138import androidx.compose.ui.Alignment
3239import androidx.compose.ui.Modifier
3340import androidx.compose.ui.draw.clip
41+ import androidx.compose.ui.geometry.Offset
42+ import androidx.compose.ui.geometry.Size
43+ import androidx.compose.ui.graphics.Brush
3444import androidx.compose.ui.graphics.Color
3545import androidx.compose.ui.platform.LocalContext
3646import androidx.compose.ui.res.painterResource
@@ -123,6 +133,7 @@ fun ScanPage(navController: NavHostController) {
123133 }, content = { paddingValues ->
124134 Box (modifier = Modifier .fillMaxSize().padding(top = paddingValues.calculateTopPadding())) {
125135 if (hasCamPermission) ScanCameraView (lifecycleOwner, cameraDetecting, onCameraProvider = { cameraProvider = it }, onScanResult = { handleScanResult(it) })
136+ if (hasCamPermission) ScanOverlay ()
126137 Row (modifier = Modifier .fillMaxWidth().padding(start = 24 .dp, end = 24 .dp, bottom = 64 .dp).align(Alignment .BottomCenter ), horizontalArrangement = Arrangement .End ) {
127138 Box (modifier = Modifier .size(56 .dp).clip(CircleShape ).background(MaterialTheme .colorScheme.darkMask(0.2f )).clickable { sendEvent(PickFileEvent (PickFileTag .SCAN , PickFileType .IMAGE , multiple = false )) }, contentAlignment = Alignment .Center ) {
128139 Icon (painter = painterResource(R .drawable.image), contentDescription = stringResource(R .string.images), tint = Color .White )
@@ -139,3 +150,63 @@ private fun addScanResult(context: Context, scope: CoroutineScope, value: String
139150 withIO { ScanHistoryPreference .putAsync(context, results) }
140151 }
141152}
153+
154+ @Composable
155+ private fun ScanOverlay (modifier : Modifier = Modifier ) {
156+ val infiniteTransition = rememberInfiniteTransition(label = " scan" )
157+ val scanProgress by infiniteTransition.animateFloat(
158+ initialValue = 0f ,
159+ targetValue = 1f ,
160+ animationSpec = infiniteRepeatable(
161+ animation = tween(2000 , easing = LinearEasing ),
162+ repeatMode = RepeatMode .Reverse ,
163+ ),
164+ label = " scan_line" ,
165+ )
166+ Canvas (modifier = modifier.fillMaxSize()) {
167+ val boxSize = minOf(size.width, size.height) * 0.65f
168+ val left = (size.width - boxSize) / 2f
169+ val top = (size.height - boxSize) / 2f
170+
171+ // Dark overlay around scan area
172+ drawRect(color = Color .Black .copy(alpha = 0.5f ), topLeft = Offset (0f , 0f ), size = Size (size.width, top))
173+ drawRect(color = Color .Black .copy(alpha = 0.5f ), topLeft = Offset (0f , top + boxSize), size = Size (size.width, size.height - top - boxSize))
174+ drawRect(color = Color .Black .copy(alpha = 0.5f ), topLeft = Offset (0f , top), size = Size (left, boxSize))
175+ drawRect(color = Color .Black .copy(alpha = 0.5f ), topLeft = Offset (left + boxSize, top), size = Size (size.width - left - boxSize, boxSize))
176+
177+ // Corner decorations
178+ val cornerLen = 40 .dp.toPx()
179+ val cornerStroke = 3 .dp.toPx()
180+ val white = Color .White
181+ // top-left
182+ drawLine(white, Offset (left, top), Offset (left + cornerLen, top), cornerStroke)
183+ drawLine(white, Offset (left, top), Offset (left, top + cornerLen), cornerStroke)
184+ // top-right
185+ drawLine(white, Offset (left + boxSize, top), Offset (left + boxSize - cornerLen, top), cornerStroke)
186+ drawLine(white, Offset (left + boxSize, top), Offset (left + boxSize, top + cornerLen), cornerStroke)
187+ // bottom-left
188+ drawLine(white, Offset (left, top + boxSize), Offset (left + cornerLen, top + boxSize), cornerStroke)
189+ drawLine(white, Offset (left, top + boxSize), Offset (left, top + boxSize - cornerLen), cornerStroke)
190+ // bottom-right
191+ drawLine(white, Offset (left + boxSize, top + boxSize), Offset (left + boxSize - cornerLen, top + boxSize), cornerStroke)
192+ drawLine(white, Offset (left + boxSize, top + boxSize), Offset (left + boxSize, top + boxSize - cornerLen), cornerStroke)
193+
194+ // Animated scan line
195+ val lineY = top + boxSize * scanProgress
196+ drawRect(
197+ brush = Brush .horizontalGradient(
198+ colors = listOf (
199+ Color .Transparent ,
200+ Color (0xFF00E676 ).copy(alpha = 0.9f ),
201+ Color (0xFF00E676 ),
202+ Color (0xFF00E676 ).copy(alpha = 0.9f ),
203+ Color .Transparent ,
204+ ),
205+ startX = left,
206+ endX = left + boxSize,
207+ ),
208+ topLeft = Offset (left, lineY - 1.5 .dp.toPx()),
209+ size = Size (boxSize, 3 .dp.toPx()),
210+ )
211+ }
212+ }
0 commit comments