F3.2 Android: agregar pad manuscrito PNG para firmas
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
package com.korexlabs.dhinspeccion.ui
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas as AndroidCanvas
|
||||
import android.graphics.Color as AndroidColor
|
||||
import android.graphics.Paint
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.detectDragGestures
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.input.pointer.consume
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
|
||||
@Composable
|
||||
fun SignaturePad(
|
||||
label: String,
|
||||
enabled: Boolean = true,
|
||||
onCaptured: (File) -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val strokes = remember { mutableStateListOf<List<Offset>>() }
|
||||
var currentStroke by remember { mutableStateOf<List<Offset>>(emptyList()) }
|
||||
var canvasSize by remember { mutableStateOf(IntSize.Zero) }
|
||||
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Text(label, fontWeight = androidx.compose.ui.text.font.FontWeight.SemiBold)
|
||||
Text(
|
||||
"Firmá dentro del recuadro. La imagen se guarda como PNG y se incorpora al hash del Acta.",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
)
|
||||
Canvas(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(190.dp)
|
||||
.background(Color.White)
|
||||
.onSizeChanged { canvasSize = it }
|
||||
.pointerInput(enabled) {
|
||||
if (!enabled) return@pointerInput
|
||||
detectDragGestures(
|
||||
onDragStart = { position -> currentStroke = listOf(position) },
|
||||
onDrag = { change, _ ->
|
||||
change.consume()
|
||||
currentStroke = currentStroke + change.position
|
||||
},
|
||||
onDragEnd = {
|
||||
if (currentStroke.size > 1) strokes.add(currentStroke)
|
||||
currentStroke = emptyList()
|
||||
},
|
||||
onDragCancel = { currentStroke = emptyList() },
|
||||
)
|
||||
},
|
||||
) {
|
||||
val all = strokes + listOf(currentStroke)
|
||||
all.forEach { stroke ->
|
||||
stroke.zipWithNext().forEach { (start, end) ->
|
||||
drawLine(
|
||||
color = Color.Black,
|
||||
start = start,
|
||||
end = end,
|
||||
strokeWidth = 4.dp.toPx(),
|
||||
cap = StrokeCap.Round,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
OutlinedButton(
|
||||
onClick = { strokes.clear(); currentStroke = emptyList() },
|
||||
enabled = enabled && (strokes.isNotEmpty() || currentStroke.isNotEmpty()),
|
||||
modifier = Modifier.weight(1f),
|
||||
) { Text("Limpiar") }
|
||||
Button(
|
||||
onClick = {
|
||||
val width = canvasSize.width.coerceAtLeast(1)
|
||||
val height = canvasSize.height.coerceAtLeast(1)
|
||||
val targetWidth = 1000
|
||||
val targetHeight = 400
|
||||
val scaleX = targetWidth.toFloat() / width.toFloat()
|
||||
val scaleY = targetHeight.toFloat() / height.toFloat()
|
||||
val bitmap = Bitmap.createBitmap(targetWidth, targetHeight, Bitmap.Config.ARGB_8888)
|
||||
val native = AndroidCanvas(bitmap)
|
||||
native.drawColor(AndroidColor.WHITE)
|
||||
val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = AndroidColor.BLACK
|
||||
strokeWidth = 7f
|
||||
strokeCap = Paint.Cap.ROUND
|
||||
strokeJoin = Paint.Join.ROUND
|
||||
style = Paint.Style.STROKE
|
||||
}
|
||||
strokes.forEach { stroke ->
|
||||
stroke.zipWithNext().forEach { (start, end) ->
|
||||
native.drawLine(
|
||||
start.x * scaleX,
|
||||
start.y * scaleY,
|
||||
end.x * scaleX,
|
||||
end.y * scaleY,
|
||||
paint,
|
||||
)
|
||||
}
|
||||
}
|
||||
val file = File.createTempFile("DH_FIRMA_", ".png", context.cacheDir)
|
||||
FileOutputStream(file).use { stream ->
|
||||
check(bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream))
|
||||
}
|
||||
bitmap.recycle()
|
||||
onCaptured(file)
|
||||
},
|
||||
enabled = enabled && strokes.isNotEmpty(),
|
||||
modifier = Modifier.weight(1f),
|
||||
) { Text("Usar firma") }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user