FragmentTicketVenta
package com.example.ventas2
import android.graphics.Color
import android.graphics.Typeface
import android.os.Bundle
import android.view.GestureDetector
import android.view.Gravity
import android.view.MotionEvent
import android.view.View
import android.widget.ArrayAdapter
import android.widget.FrameLayout
import android.widget.ListView
import android.widget.TextView
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import kotlin.math.abs
class MenuPruebaActivity : AppCompatActivity() {
private val ruta = mutableListOf()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_menu_prueba)
val sidePanel = findViewById(R.id.sidePanel)
val rootLayout = findViewById(R.id.main)
sidePanel.post {
sidePanel.translationX = -sidePanel.width.toFloat()
}
val contentFrame = findViewById(R.id.contentFrame)
val tvRuta = findViewById(R.id.tvRuta)
val listView = ListView(this)
contentFrame.addView(listView)
/////////////////////////////////////////////////////////
data class NodoMenu(
val nombre: String,
val hijos: List = emptyList(),
val unidad: String? = null
)
val menuPrincipal = listOf(
NodoMenu("Inicio"),
NodoMenu(
"Ventas",
listOf(
NodoMenu(
"Cierres",
listOf(
NodoMenu(
"Común",
listOf(
NodoMenu("Rojo", unidad = "u"),
NodoMenu("Azul", unidad = "u")
)
),
NodoMenu("Diente de Perro", unidad = "u"),
NodoMenu("Metal", unidad = "u")
)
),
NodoMenu(
"Botones",
listOf(
NodoMenu("Redondos", unidad = "u"),
NodoMenu("Cuadrados", unidad = "u")
)
),
NodoMenu(
"Cintas",
listOf(
NodoMenu("Negra", unidad = "m"),
NodoMenu("Roja", unidad = "m")
)
)
)
),
NodoMenu(
"Configuracion",
listOf(
NodoMenu("Agregar"),
NodoMenu("Editar"),
NodoMenu("Eliminar")
)
),
NodoMenu("Salir")
)
var nivelActual = menuPrincipal
val pila = mutableListOf>()
fun actualizarLista() {
val nombres = nivelActual.map { it.nombre }
listView.adapter = ArrayAdapter(
this,
android.R.layout.simple_list_item_1,
nombres
)
}
actualizarLista()
fun abrirMenu() {
sidePanel.animate()
.translationX(0f)
.setDuration(200)
.start()
}
fun cerrarMenu() {
sidePanel.animate()
.translationX(-sidePanel.width.toFloat())
.setDuration(200)
.start()
}
var startX = 0f
rootLayout.setOnTouchListener { _, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
startX = event.rawX
true
}
MotionEvent.ACTION_UP -> {
val delta = event.rawX - startX
if (delta > 120) {
abrirMenu()
} else if (delta < -120) {
cerrarMenu()
}
true
}
else -> false
}
}
/* fun actualizarRuta() {
tvRuta.text = if (ruta.isEmpty()) {
"Ventas"
} else {
"Ventas > " + ruta.joinToString(" > ")
}
}
*/
fun actualizarRuta() {
tvRuta.text = ruta.joinToString(" > ")
}
fun animarLista(direccion: Int) {
val fromX = if (direccion > 0) 1f else -1f
listView.translationX = fromX * listView.width
val width = if (listView.width == 0) 300 else listView.width
listView.translationX = fromX * width
listView.animate()
.translationX(0f)
.setDuration(180)
.start()
}
fun volverNivel() {
if (pila.isNotEmpty()) {
nivelActual = pila[pila.size - 1]
pila.removeAt(pila.size - 1)
if (ruta.isNotEmpty()) {
ruta.removeAt(ruta.size - 1) // o ruta.removeAt(ruta.lastIndex)
}
actualizarRuta()
actualizarLista()
animarLista(-1)
}
}
val gestureDetector = GestureDetector(this,
object : GestureDetector.SimpleOnGestureListener() {
private val SWIPE_THRESHOLD = 100
private val SWIPE_VELOCITY_THRESHOLD = 100
override fun onFling(
e1: MotionEvent?,
e2: MotionEvent,
velocityX: Float,
velocityY: Float
): Boolean {
val diffX = e2.x - (e1?.x ?: 0f)
val diffY = e2.y - (e1?.y ?: 0f)
if (abs(diffX) > abs(diffY) &&
abs(diffX) > SWIPE_THRESHOLD &&
abs(velocityX) > SWIPE_VELOCITY_THRESHOLD
)
// if (kotlin.math.abs(diffX) > SWIPE_THRESHOLD &&
// kotlin.math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD
// )
{
if (diffX > 0) {
volverNivel()
}
return true
}
return false
}
})
listView.setOnTouchListener { _, event ->
gestureDetector.onTouchEvent(event)
false
}
listView.setOnItemClickListener { _, _, position, _ ->
val seleccionado = nivelActual[position]
if (seleccionado.hijos.isNotEmpty()) {
pila.add(nivelActual)
nivelActual = seleccionado.hijos
ruta.add(seleccionado.nombre)
actualizarRuta()
actualizarLista()
animarLista(1)
} else {
cerrarMenu()
Toast.makeText(
this,
"Producto: ${seleccionado.nombre}",
Toast.LENGTH_SHORT
).show()
// Acá después conectamos con tu diálogo real
}
}
}
}
MenuPruebaActivity
package com.example.ventas2
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Typeface
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.text.InputType
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import androidx.appcompat.app.AlertDialog
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.FileProvider
import androidx.fragment.app.Fragment
import java.io.File
import java.io.FileOutputStream
import java.text.NumberFormat
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
//import java.util.logging.Handler
import android.os.Handler
import android.os.Looper
import android.graphics.BitmapFactory
class FragmentTicketVenta : Fragment() {
// NO CIERRA NUNCA
// override fun onDestroy() {
// super.onDestroy()
// NotificationManagerCompat.from(requireContext()).cancel(1001)
// }
// CIERRA AL CERRAR LA APP DEBERÍA
override fun onDestroy() {
super.onDestroy()
//NotificationManagerCompat.from(this).cancel(1001)
NotificationManagerCompat.from(requireContext()).cancel(1001)
//NotificationManagerCompat.from(requireActivity()).cancel(1001)
}
// Se cierra sola al minimizar
// override fun onStop() {
// super.onStop()
//NotificationManagerCompat.from(this).cancel(1001)
// NotificationManagerCompat.from(requireActivity()).cancel(1001)
// }
// NO CIERRA NUNCA
// override fun onDestroyView() {
// super.onDestroyView()
// NotificationManagerCompat.from(requireContext()).cancel(1001)
// }
private var totalGeneral = 0.0
lateinit var textTotalGeneral: TextView
lateinit var listView: ListView
lateinit var tableProductos: TableLayout
lateinit var tableHeader: TableLayout
lateinit var buttonGuardar: Button
// lateinit var textFecha: TextView
private val lineas = mutableListOf()
data class LineaTicket(
val producto: String,
val cantidad: Double,
val unidad: String,
val precioUnit: Double,
val total: Double
)
private val productos = mapOf(
"Botones" to "u",
"Lentejuelas" to "g",
"Elásticos" to "m"
)
private val nombresProductos = productos.keys.toList()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_ticket_venta, container, false)
//////////////////////////////////////////////////////////////////
crearCanalNotificacion()
pedirPermisoNotificaciones()
// Solo para probar (cambialo a 10 segundos)
Handler(Looper.getMainLooper()).postDelayed({
val horaActual = SimpleDateFormat("HH:mm", Locale("es","AR"))
.format(Date())
mostrarNotificacion("Son las $horaActual")
}, 10_000L) // 10 segundos
//val intent = Intent(requireContext(), MenuPruebaActivity::class.java)
//startActivity(intent)
/*
val toolbar = view.findViewById(R.id.toolbar)
toolbar.setOnClickListener {
requireActivity().supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, FragmentMenuPrueba())
.addToBackStack(null)
.commit()
}
*/
/////////////////////////////////////////////////////////////////
textTotalGeneral = view.findViewById(R.id.textTotalGeneral)
listView = view.findViewById(R.id.listView)
tableHeader = view.findViewById(R.id.tableHeader)
tableProductos = view.findViewById(R.id.tableProductos)
buttonGuardar = view.findViewById(R.id.buttonGuardar)
buttonGuardar.visibility = View.GONE
buttonGuardar.setOnClickListener {
// val nombreArchivo = "venta_${System.currentTimeMillis()}.txt"
// generarComprobantePNG(nombreArchivo)
/* val exito = generarComprobantePNG(nombreArchivo) //guardarVentaEnDescargas(nombreArchivo)
if (exito) {
AlertDialog.Builder(requireContext())
.setTitle("¡Venta guardada!")
.setMessage("Archivo: $nombreArchivo\nUbicación: Descargas")
.setPositiveButton("OK", null)
.show()
} else {
Toast.makeText(requireContext(), "Error al guardar la venta", Toast.LENGTH_LONG).show()
}
*/
val editText = EditText(requireContext())
editText.hint = "Nombre del comprador"
AlertDialog.Builder(requireContext())
.setTitle("Nombre del cliente")
.setView(editText)
.setPositiveButton("Generar") { _, _ ->
val nombre = editText.text.toString()
generarComprobantePNG(nombre)
}
.setNegativeButton("Cancelar", null)
.show()
}
textTotalGeneral.setOnLongClickListener {
nuevaVenta()
true
}
agregarFilaTitulo()
val adapter = ArrayAdapter(
requireContext(),
android.R.layout.simple_list_item_1,
nombresProductos
)
listView.adapter = adapter
listView.setOnItemClickListener { _, _, position, _ ->
val producto = nombresProductos[position]
val unidad = productos[producto]!!
val inputCantidad = EditText(requireContext())
inputCantidad.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL
AlertDialog.Builder(requireContext())
.setTitle("Cantidad")
.setMessage("Ingrese cantidad para $producto ($unidad)")
.setView(inputCantidad)
.setPositiveButton("OK") { _, _ ->
val cantidadStr = inputCantidad.text.toString()
if (cantidadStr.isNotEmpty()) {
val cantidad = cantidadStr.toDouble()
val inputPrecio = EditText(requireContext())
inputPrecio.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL
AlertDialog.Builder(requireContext())
.setTitle("Precio Unitario")
.setMessage("Ingrese precio unitario para $producto")
.setView(inputPrecio)
.setPositiveButton("OK") { _, _ ->
val precioStr = inputPrecio.text.toString()
if (precioStr.isNotEmpty()) {
val precioUnit = precioStr.toDouble()
val totalLinea = cantidad * precioUnit
agregarProducto(
producto,
cantidad,
unidad,
precioUnit,
totalLinea
)
}
}
.setNegativeButton("Cancelar", null)
.show()
}
}
.setNegativeButton("Cancelar", null)
.show()
}
return view
}
private fun formatearCantidad(cantidad: Double, unidad: String): String {
return if (unidad == "u") {
"${cantidad.toInt()}u"
} else {
"${"%.2f".format(cantidad)}$unidad"
}
}
private fun generarComprobantePNG(nombreCliente: String): Boolean {
return try {
val inflater = LayoutInflater.from(requireContext())
val view = inflater.inflate(R.layout.layout_comprobante, null)
val textFecha = view.findViewById(R.id.textFecha)
val fechaActual = SimpleDateFormat("dd/MM/yyyy - HH:mm", Locale("es", "AR"))
textFecha.text = fechaActual.format(Date())
val layoutLineas = view.findViewById(R.id.layoutLineas)
val txtTotal = view.findViewById(R.id.txtTotal)
layoutLineas.removeAllViews()
var totalGeneral = 0.0
// val view = inflater.inflate(R.layout.layout_comprobante, null)
val nombreArchivo = "venta_${System.currentTimeMillis()}.png"
val txtNombre = view.findViewById(R.id.txtCliente)
txtNombre.text = nombreCliente
val headerLayout = LinearLayout(requireContext())
headerLayout.orientation = LinearLayout.HORIZONTAL
headerLayout.layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
val paramsProducto = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 2f)
val paramsOtros = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
fun crearHeader(texto: String, params: LinearLayout.LayoutParams, gravity: Int): TextView {
return TextView(requireContext()).apply {
layoutParams = params
text = texto
textSize = 13f
setTypeface(null, Typeface.BOLD)
setTextColor(Color.DKGRAY)
this.gravity = gravity
}
}
headerLayout.addView(crearHeader("Producto", paramsProducto, Gravity.START))
headerLayout.addView(crearHeader("Cant.", paramsOtros, Gravity.END))
headerLayout.addView(crearHeader("P.Unit", paramsOtros, Gravity.END))
headerLayout.addView(crearHeader("Total", paramsOtros, Gravity.END))
layoutLineas.addView(headerLayout)
for (linea in lineas) {
val filaLayout = LinearLayout(requireContext())
filaLayout.orientation = LinearLayout.HORIZONTAL
filaLayout.layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
val paramsProducto = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 2f)
val paramsOtros = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
val txtProducto = TextView(requireContext())
txtProducto.layoutParams = paramsProducto
txtProducto.text = linea.producto
txtProducto.textSize = 14f
val txtCantidad = TextView(requireContext())
txtCantidad.layoutParams = paramsOtros
txtCantidad.text = formatearCantidad(linea.cantidad, linea.unidad)
txtCantidad.gravity = Gravity.END
txtCantidad.textSize = 14f
val txtPrecio = TextView(requireContext())
txtPrecio.layoutParams = paramsOtros
//txtPrecio.text = "$${linea.precioUnit}"
txtPrecio.text = formatearMoneda(linea.precioUnit)
txtPrecio.gravity = Gravity.END
txtPrecio.textSize = 14f
val txtTotalLinea = TextView(requireContext())
txtTotalLinea.layoutParams = paramsOtros
//txtTotalLinea.text = "$${linea.total}"
txtTotalLinea.text = formatearMoneda(linea.total)
txtTotalLinea.gravity = Gravity.END
txtTotalLinea.textSize = 14f
filaLayout.addView(txtProducto)
filaLayout.addView(txtCantidad)
filaLayout.addView(txtPrecio)
filaLayout.addView(txtTotalLinea)
layoutLineas.addView(filaLayout)
totalGeneral += linea.total
}
//txtTotal.text = "TOTAL: $${"%.2f".format(totalGeneral)}"
txtTotal.text = "TOTAL: ${formatearMoneda(totalGeneral)}"
/*
view.measure(
View.MeasureSpec.makeMeasureSpec(800, View.MeasureSpec.EXACTLY),
View.MeasureSpec.UNSPECIFIED
)
*/
val density = resources.displayMetrics.density
val widthPx = (400 * density).toInt() // 600dp ancho tipo recibo vertical
view.measure(
View.MeasureSpec.makeMeasureSpec(widthPx, View.MeasureSpec.EXACTLY),
View.MeasureSpec.UNSPECIFIED
)
view.layout(0, 0, view.measuredWidth, view.measuredHeight)
val bitmap = Bitmap.createBitmap(
view.measuredWidth,
view.measuredHeight,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap)
view.draw(canvas)
val file = File(requireContext().cacheDir, "$nombreArchivo.png")
val fos = FileOutputStream(file)
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos)
fos.flush()
fos.close()
compartirImagen(file)
true // 👈 éxito
} catch (e: Exception) {
e.printStackTrace()
false // 👈 error
}
}
private fun formatearMoneda(valor: Double): String {
val formato = NumberFormat.getNumberInstance(Locale("es", "AR"))
formato.maximumFractionDigits = 0
formato.minimumFractionDigits = 0
return "$${formato.format(valor)}"
}
private fun compartirImagen(file: File) {
val uri = FileProvider.getUriForFile(
requireContext(),
"${requireContext().packageName}.provider",
file
)
val intent = Intent(Intent.ACTION_SEND)
intent.type = "image/png"
intent.putExtra(Intent.EXTRA_STREAM, uri)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
startActivity(Intent.createChooser(intent, "Compartir comprobante"))
}
private fun agregarFilaTitulo() {
val filaTitulo = TableRow(requireContext())
val col1 = TextView(requireContext())
col1.text = "Producto"; col1.setPadding(16,16,16,16); col1.setTextColor(Color.YELLOW)
val col2 = TextView(requireContext()); col2.gravity = Gravity.END; col2.text = "Cant"; col2.setPadding(16,16,16,16); col2.setTextColor(Color.YELLOW)
val col3 = TextView(requireContext()); col3.gravity = Gravity.END; col3.text = "P.Unit"; col3.setPadding(16,16,16,16); col3.setTextColor(Color.YELLOW)
val col4 = TextView(requireContext()); col4.gravity = Gravity.END; col4.text = "Total"; col4.setPadding(16,16,16,16); col4.setTextColor(Color.YELLOW)
col1.setTypeface(null, Typeface.BOLD)
col2.setTypeface(null, Typeface.BOLD)
col3.setTypeface(null, Typeface.BOLD)
col4.setTypeface(null, Typeface.BOLD)
filaTitulo.setBackgroundColor(Color.DKGRAY)
filaTitulo.addView(col1)
filaTitulo.addView(col2)
filaTitulo.addView(col3)
filaTitulo.addView(col4)
//tableProductos.addView(filaTitulo) //tableHeader
tableHeader.addView(filaTitulo)
}
private fun agregarProducto(
producto: String,
cantidad: Double,
unidad: String,
precioUnit: Double,
totalLinea: Double
) {
lineas.add(
LineaTicket(producto, cantidad, unidad, precioUnit, totalLinea)
)
val fila = TableRow(requireContext())
val colProducto = TextView(requireContext())
colProducto.text = producto; colProducto.setPadding(16,16,16,16)
val colCantUnidad = TextView(requireContext())
colCantUnidad.gravity = Gravity.END
val cantidadFormateada = if (unidad == "u") cantidad.toInt().toString() else if (cantidad % 1.0 == 0.0) cantidad.toInt().toString() else cantidad.toString()
colCantUnidad.text = "$cantidadFormateada$unidad"; colCantUnidad.setPadding(16,16,16,16)
val colPrecio = TextView(requireContext())
colPrecio.gravity = Gravity.END
val formato = NumberFormat.getNumberInstance(Locale("es","AR"))
colPrecio.text = "$" + formato.format(precioUnit.toInt()); colPrecio.setPadding(16,16,16,16)
val colTotal = TextView(requireContext())
colTotal.gravity = Gravity.END
colTotal.text = "$" + formato.format(totalLinea.toInt()); colTotal.setPadding(16,16,16,16)
fila.addView(colProducto)
fila.addView(colCantUnidad)
fila.addView(colPrecio)
fila.addView(colTotal)
//refrescarColoresFilas()
val index = tableProductos.childCount
val colorPar = Color.parseColor("#1C1C1E")
val colorImpar = Color.parseColor("#1E88E5") //1E88E5 //2A2A2E
val colorBase = if (index % 2 == 0) colorPar else colorImpar
fila.setBackgroundColor(colorBase)
tableProductos.addView(fila)
totalGeneral += totalLinea
textTotalGeneral.text = "TOTAL: $" + formato.format(totalGeneral.toInt())
actualizarBotonGuardar()
//fila.setBackgroundColor(Color.TRANSPARENT)
//fila.setBackgroundColor(Color.parseColor("#1E3A5F")
/*
fila.setOnClickListener {
//fila.setBackgroundColor(Color.parseColor("#1E3A5F"))
fila.setBackgroundColor(Color.TRANSPARENT)
AlertDialog.Builder(requireContext())
.setTitle("Eliminar")
.setMessage("¿Seguro quieres eliminar la línea seleccionada?")
.setPositiveButton("Sí") { _, _ ->
totalGeneral -= totalLinea
textTotalGeneral.text = "TOTAL: $" + formato.format(totalGeneral.toInt())
tableProductos.removeView(fila)
}
.setNegativeButton("No") { dialog, _ ->
//fila.setBackgroundColor(Color.TRANSPARENT)
fila.setBackgroundColor(Color.parseColor("#1E3A5F"))
dialog.dismiss()
}
//.setOnCancelListener { fila.setBackgroundColor(Color.TRANSPARENT) }
.setOnCancelListener { fila.setBackgroundColor(Color.parseColor("#1E3A5F")) }
.setCancelable(false)
.show()
}
*/
fila.setOnClickListener {
fila.setBackgroundColor(Color.parseColor("#3A4F7A"))
AlertDialog.Builder(requireContext())
.setTitle("Eliminar")
.setMessage("¿Seguro quieres eliminar la línea seleccionada?")
.setPositiveButton("Sí") { _, _ ->
totalGeneral -= totalLinea
textTotalGeneral.text = "TOTAL: $" + formato.format(totalGeneral.toInt())
tableProductos.removeView(fila)
refrescarColoresFilas()
actualizarBotonGuardar()
}
.setNegativeButton("No") { dialog, _ ->
fila.setBackgroundColor(colorBase)
dialog.dismiss()
}
.setOnCancelListener {
fila.setBackgroundColor(colorBase)
}
.show()
}
}
private fun refrescarColoresFilas() {
val colorPar = Color.parseColor("#1C1C1E")
val colorImpar = Color.parseColor("#1E88E5") //1E88E5 //2A2A2E
for (i in 0 until tableProductos.childCount) {
val fila = tableProductos.getChildAt(i)
val colorBase = if (i % 2 == 0) colorPar else colorImpar
fila.setBackgroundColor(colorBase)
}
}
private fun actualizarBotonGuardar() {
buttonGuardar.visibility = if (tableProductos.childCount > 0) View.VISIBLE else View.GONE
//buttonGuardar.visibility = View.VISIBLE else View.GONE
}
private fun nuevaVenta() {
//while (tableProductos.childCount > 1)
//tableProductos.removeViewAt()
tableProductos.removeAllViews()
//refrescarColoresFilas()
lineas.clear() // 🔥 ESTO ES LO QUE FALTABA
totalGeneral = 0.0
val formato = NumberFormat.getNumberInstance(Locale("es","AR"))
textTotalGeneral.text = "TOTAL: $0"
actualizarBotonGuardar()
}
private fun guardarVentaEnDescargas(nombreArchivo: String): Boolean {
return try {
val downloadsFolder = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
if (!downloadsFolder.exists()) downloadsFolder.mkdirs()
val archivo = File(downloadsFolder, nombreArchivo)
val sb = StringBuilder()
sb.append("Verdulería Juanita\n--------------------------------------------------\n")
sb.append(String.format("%-10s %-10s %-10s %-10s\n", "Producto", "Cant/Unidad", "Precio U.", "Total"))
for (i in 1 until tableProductos.childCount) {
val fila = tableProductos.getChildAt(i) as TableRow
val colProducto = (fila.getChildAt(0) as TextView).text.toString()
val colCantidad = (fila.getChildAt(1) as TextView).text.toString()
val colPrecioUnit = (fila.getChildAt(2) as TextView).text.toString()
val colTotal = (fila.getChildAt(3) as TextView).text.toString()
sb.append(String.format("%-10s %-10s %-10s %-10s\n", colProducto, colCantidad, colPrecioUnit, colTotal))
}
sb.append("--------------------------------------------------\nTOTAL: $" + totalGeneral.toInt() + "\n--------------------------------------------------\n¡Gracias por su compra!\nNo válido como factura\n")
archivo.writeText(sb.toString())
true
} catch (e: Exception) { e.printStackTrace(); false }
}
private fun crearCanalNotificacion() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val nombre = "CanalVentas"
val descripcion = "Notificaciones de prueba"
val importancia = NotificationManager.IMPORTANCE_HIGH
val canal = NotificationChannel("canal_ventas", nombre, importancia)
canal.description = descripcion
val notificationManager = requireContext()
.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(canal)
}
}
private fun mostrarNotificacion(mensaje: String) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (requireContext().checkSelfPermission(android.Manifest.permission.POST_NOTIFICATIONS)
!= PackageManager.PERMISSION_GRANTED) {
return
}
}
val builder = NotificationCompat.Builder(requireContext(), "canal_ventas")
//.setSmallIcon(R.drawable.ic_launcher_foreground)
.setSmallIcon(R.drawable.ic_notificacion)
.setLargeIcon(
BitmapFactory.decodeResource(resources, R.drawable.bot)
)
.setContentTitle("Mercería ButtonTop")
//.setContentText(mensaje)
.setContentText("App activa ❤️")
//.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setOngoing(true) // Siempre visible no se puede quitar
with(NotificationManagerCompat.from(requireContext())) {
notify(1001, builder.build())
}
}
private fun programarNotificacionEn4Minutos() {
Handler(Looper.getMainLooper()).postDelayed({
val horaActual = SimpleDateFormat("HH:mm", Locale("es","AR"))
.format(Date())
mostrarNotificacion("Son las $horaActual")
}, 4 * 60 * 1000) // 4 minutos
}
private fun pedirPermisoNotificaciones() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (requireContext().checkSelfPermission(android.Manifest.permission.POST_NOTIFICATIONS)
!= PackageManager.PERMISSION_GRANTED) {
requestPermissions(
arrayOf(android.Manifest.permission.POST_NOTIFICATIONS),
101
)
}
}
}
}
fragment_ticket_venta.xml
layout_comprobante.xml
activity_menu_prueba.xml