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