Añadir Menu Gestion de SAIS Gestion de baterias y gestion de personas
This commit is contained in:
@@ -46,6 +46,8 @@ dependencies {
|
||||
implementation(platform(libs.androidx.compose.bom))
|
||||
implementation(libs.androidx.activity.compose)
|
||||
implementation(libs.androidx.compose.material3)
|
||||
implementation(libs.androidx.compose.material.icons.extended)
|
||||
implementation(libs.androidx.navigation.compose)
|
||||
implementation(libs.androidx.compose.ui)
|
||||
implementation(libs.androidx.compose.ui.graphics)
|
||||
implementation(libs.androidx.compose.ui.tooling.preview)
|
||||
|
||||
@@ -1,50 +1,11 @@
|
||||
package com.example.saipp
|
||||
|
||||
import android.Manifest
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.example.saipp.data.ScannerRepository
|
||||
import com.example.saipp.ui.scanner.BarcodeScannerView
|
||||
import com.example.saipp.ui.AppNavigation
|
||||
import com.example.saipp.ui.theme.SAIppTheme
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -52,181 +13,8 @@ class MainActivity : ComponentActivity() {
|
||||
enableEdgeToEdge()
|
||||
setContent {
|
||||
SAIppTheme {
|
||||
MainScreen()
|
||||
AppNavigation()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MainScreen() {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val repository = remember { ScannerRepository() }
|
||||
|
||||
var hasCameraPermission by remember {
|
||||
mutableStateOf(
|
||||
ContextCompat.checkSelfPermission(
|
||||
context,
|
||||
Manifest.permission.CAMERA
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
)
|
||||
}
|
||||
|
||||
val launcher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.RequestPermission(),
|
||||
onResult = { granted ->
|
||||
hasCameraPermission = granted
|
||||
}
|
||||
)
|
||||
|
||||
LaunchedEffect(key1 = true) {
|
||||
if (!hasCameraPermission) {
|
||||
launcher.launch(Manifest.permission.CAMERA)
|
||||
}
|
||||
}
|
||||
|
||||
var connectionStatus by remember { mutableStateOf<Result<Unit>?>(null) }
|
||||
var isCheckingConnection by remember { mutableStateOf(true) }
|
||||
|
||||
LaunchedEffect(key1 = true) {
|
||||
isCheckingConnection = true
|
||||
connectionStatus = repository.testConnection()
|
||||
isCheckingConnection = false
|
||||
}
|
||||
|
||||
var scannedBarcode by remember { mutableStateOf<String?>(null) }
|
||||
var isSaving by remember { mutableStateOf(false) }
|
||||
|
||||
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(innerPadding)
|
||||
) {
|
||||
if (hasCameraPermission) {
|
||||
// Only scan if no barcode is currently being shown in the popup
|
||||
if (scannedBarcode == null) {
|
||||
BarcodeScannerView(onBarcodeDetected = { barcode ->
|
||||
Log.d("MainActivity", "Barcode detected: $barcode")
|
||||
scannedBarcode = barcode
|
||||
})
|
||||
}
|
||||
|
||||
// Connection Status Indicator at the top
|
||||
ConnectionIndicator(
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopCenter)
|
||||
.padding(16.dp),
|
||||
isChecking = isCheckingConnection,
|
||||
status = connectionStatus
|
||||
)
|
||||
|
||||
if (isSaving) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Text(
|
||||
text = stringResource(R.string.camera_permission_required),
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
)
|
||||
}
|
||||
// ... rest of the code
|
||||
|
||||
// Popup Dialog for scanned results
|
||||
scannedBarcode?.let { barcode ->
|
||||
ResultDialog(
|
||||
barcode = barcode,
|
||||
onDismiss = { scannedBarcode = null },
|
||||
onConfirm = {
|
||||
scannedBarcode = null
|
||||
isSaving = true
|
||||
scope.launch {
|
||||
val result = repository.saveBarcode(barcode)
|
||||
isSaving = false
|
||||
result.onSuccess {
|
||||
Toast.makeText(context, R.string.save_success, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
result.onFailure { e ->
|
||||
val errorMessage = when (e.message) {
|
||||
"connection_failed" -> context.getString(R.string.db_connection_failed)
|
||||
"db_connection_error" -> context.getString(R.string.db_connection_error)
|
||||
"no_rows_inserted" -> context.getString(R.string.no_rows_inserted)
|
||||
else -> e.message ?: "Unknown error"
|
||||
}
|
||||
Toast.makeText(context, context.getString(R.string.save_error, errorMessage), Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ResultDialog(
|
||||
barcode: String,
|
||||
onDismiss: () -> Unit,
|
||||
onConfirm: () -> Unit
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text(text = stringResource(R.string.dialog_title)) },
|
||||
text = { Text(text = stringResource(R.string.dialog_message, barcode)) },
|
||||
confirmButton = {
|
||||
Button(onClick = onConfirm) {
|
||||
Text(stringResource(R.string.button_save))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text(stringResource(R.string.button_close))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ConnectionIndicator(
|
||||
modifier: Modifier = Modifier,
|
||||
isChecking: Boolean,
|
||||
status: Result<Unit>?
|
||||
) {
|
||||
Surface(
|
||||
modifier = modifier,
|
||||
color = Color.Black.copy(alpha = 0.6f),
|
||||
shape = CircleShape
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
val color = when {
|
||||
isChecking -> Color.Gray
|
||||
status?.isSuccess == true -> Color.Green
|
||||
else -> Color.Red
|
||||
}
|
||||
|
||||
Surface(
|
||||
modifier = Modifier.size(8.dp),
|
||||
color = color,
|
||||
shape = CircleShape
|
||||
) {}
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
Text(
|
||||
text = when {
|
||||
isChecking -> stringResource(R.string.checking_server)
|
||||
status?.isSuccess == true -> stringResource(R.string.connected_status)
|
||||
else -> stringResource(R.string.disconnected_status)
|
||||
},
|
||||
color = Color.White,
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.example.saipp.data
|
||||
|
||||
import com.example.saipp.data.model.Battery
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.sql.SQLException
|
||||
|
||||
class BatteryRepository {
|
||||
suspend fun getAll(): Result<List<Battery>> = withContext(Dispatchers.IO) {
|
||||
val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error"))
|
||||
try {
|
||||
val sql = "SELECT id_bateria, modelo, estado FROM baterias"
|
||||
val statement = connection.prepareStatement(sql)
|
||||
val resultSet = statement.executeQuery()
|
||||
val items = mutableListOf<Battery>()
|
||||
while (resultSet.next()) {
|
||||
items.add(Battery(
|
||||
id = resultSet.getInt("id_bateria"),
|
||||
model = resultSet.getString("modelo"),
|
||||
status = resultSet.getString("estado")
|
||||
))
|
||||
}
|
||||
Result.success(items)
|
||||
} catch (e: SQLException) {
|
||||
Result.failure(e)
|
||||
} finally {
|
||||
connection.close()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun insert(item: Battery): Result<Unit> = withContext(Dispatchers.IO) {
|
||||
val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error"))
|
||||
try {
|
||||
val sql = "INSERT INTO baterias (modelo, estado) VALUES (?, ?)"
|
||||
val statement = connection.prepareStatement(sql)
|
||||
statement.setString(1, item.model)
|
||||
statement.setString(2, item.status)
|
||||
if (statement.executeUpdate() > 0) Result.success(Unit)
|
||||
else Result.failure(Exception("no_rows_inserted"))
|
||||
} catch (e: SQLException) {
|
||||
Result.failure(e)
|
||||
} finally {
|
||||
connection.close()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun update(item: Battery): Result<Unit> = withContext(Dispatchers.IO) {
|
||||
val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error"))
|
||||
try {
|
||||
val sql = "UPDATE baterias SET modelo = ?, estado = ? WHERE id_bateria = ?"
|
||||
val statement = connection.prepareStatement(sql)
|
||||
statement.setString(1, item.model)
|
||||
statement.setString(2, item.status)
|
||||
statement.setInt(3, item.id)
|
||||
if (statement.executeUpdate() > 0) Result.success(Unit)
|
||||
else Result.failure(Exception("update_failed"))
|
||||
} catch (e: SQLException) {
|
||||
Result.failure(e)
|
||||
} finally {
|
||||
connection.close()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun delete(id: Int): Result<Unit> = withContext(Dispatchers.IO) {
|
||||
val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error"))
|
||||
try {
|
||||
val sql = "DELETE FROM baterias WHERE id_bateria = ?"
|
||||
val statement = connection.prepareStatement(sql)
|
||||
statement.setInt(1, id)
|
||||
if (statement.executeUpdate() > 0) Result.success(Unit)
|
||||
else Result.failure(Exception("delete_failed"))
|
||||
} catch (e: SQLException) {
|
||||
Result.failure(e)
|
||||
} finally {
|
||||
connection.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.example.saipp.data
|
||||
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.sql.Connection
|
||||
import java.sql.DriverManager
|
||||
import java.sql.SQLException
|
||||
@@ -34,4 +36,18 @@ object MariaDbConnection {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun testConnection(): Result<Unit> = withContext(Dispatchers.IO) {
|
||||
val connection = getConnection()
|
||||
if (connection != null) {
|
||||
try {
|
||||
connection.close()
|
||||
Result.success(Unit)
|
||||
} catch (e: SQLException) {
|
||||
Result.failure(e)
|
||||
}
|
||||
} else {
|
||||
Result.failure(Exception("connection_failed"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.example.saipp.data
|
||||
|
||||
import com.example.saipp.data.model.Person
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.sql.SQLException
|
||||
|
||||
class PersonRepository {
|
||||
suspend fun getAll(): Result<List<Person>> = withContext(Dispatchers.IO) {
|
||||
val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error"))
|
||||
try {
|
||||
val sql = "SELECT id_persona, nombre, puesto FROM personas"
|
||||
val statement = connection.prepareStatement(sql)
|
||||
val resultSet = statement.executeQuery()
|
||||
val items = mutableListOf<Person>()
|
||||
while (resultSet.next()) {
|
||||
items.add(Person(
|
||||
id = resultSet.getInt("id_persona"),
|
||||
name = resultSet.getString("nombre"),
|
||||
position = resultSet.getString("puesto")
|
||||
))
|
||||
}
|
||||
Result.success(items)
|
||||
} catch (e: SQLException) {
|
||||
Result.failure(e)
|
||||
} finally {
|
||||
connection.close()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun insert(item: Person): Result<Unit> = withContext(Dispatchers.IO) {
|
||||
val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error"))
|
||||
try {
|
||||
val sql = "INSERT INTO personas (nombre, puesto) VALUES (?, ?)"
|
||||
val statement = connection.prepareStatement(sql)
|
||||
statement.setString(1, item.name)
|
||||
statement.setString(2, item.position)
|
||||
if (statement.executeUpdate() > 0) Result.success(Unit)
|
||||
else Result.failure(Exception("no_rows_inserted"))
|
||||
} catch (e: SQLException) {
|
||||
Result.failure(e)
|
||||
} finally {
|
||||
connection.close()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun update(item: Person): Result<Unit> = withContext(Dispatchers.IO) {
|
||||
val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error"))
|
||||
try {
|
||||
val sql = "UPDATE personas SET nombre = ?, puesto = ? WHERE id_persona = ?"
|
||||
val statement = connection.prepareStatement(sql)
|
||||
statement.setString(1, item.name)
|
||||
statement.setString(2, item.position)
|
||||
statement.setInt(3, item.id)
|
||||
if (statement.executeUpdate() > 0) Result.success(Unit)
|
||||
else Result.failure(Exception("update_failed"))
|
||||
} catch (e: SQLException) {
|
||||
Result.failure(e)
|
||||
} finally {
|
||||
connection.close()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun delete(id: Int): Result<Unit> = withContext(Dispatchers.IO) {
|
||||
val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error"))
|
||||
try {
|
||||
val sql = "DELETE FROM personas WHERE id_persona = ?"
|
||||
val statement = connection.prepareStatement(sql)
|
||||
statement.setInt(1, id)
|
||||
if (statement.executeUpdate() > 0) Result.success(Unit)
|
||||
else Result.failure(Exception("delete_failed"))
|
||||
} catch (e: SQLException) {
|
||||
Result.failure(e)
|
||||
} finally {
|
||||
connection.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.example.saipp.data
|
||||
|
||||
import android.util.Log
|
||||
import com.example.saipp.data.model.Sai
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.sql.SQLException
|
||||
|
||||
class SaiRepository {
|
||||
companion object {
|
||||
private const val TAG = "SaiRepository"
|
||||
}
|
||||
|
||||
suspend fun getAll(): Result<List<Sai>> = withContext(Dispatchers.IO) {
|
||||
val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error"))
|
||||
try {
|
||||
val sql = "SELECT id_sai, ns FROM SAIs"
|
||||
val statement = connection.prepareStatement(sql)
|
||||
val resultSet = statement.executeQuery()
|
||||
val sais = mutableListOf<Sai>()
|
||||
while (resultSet.next()) {
|
||||
sais.add(Sai(id = resultSet.getInt("id_sai"), ns = resultSet.getString("ns")))
|
||||
}
|
||||
Result.success(sais)
|
||||
} catch (e: SQLException) {
|
||||
Result.failure(e)
|
||||
} finally {
|
||||
connection.close()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun insert(ns: String): Result<Unit> = withContext(Dispatchers.IO) {
|
||||
val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error"))
|
||||
try {
|
||||
val sql = "INSERT INTO SAIs (ns) VALUES (?)"
|
||||
val statement = connection.prepareStatement(sql)
|
||||
statement.setString(1, ns)
|
||||
if (statement.executeUpdate() > 0) Result.success(Unit)
|
||||
else Result.failure(Exception("no_rows_inserted"))
|
||||
} catch (e: SQLException) {
|
||||
Result.failure(e)
|
||||
} finally {
|
||||
connection.close()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun update(sai: Sai): Result<Unit> = withContext(Dispatchers.IO) {
|
||||
val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error"))
|
||||
try {
|
||||
val sql = "UPDATE SAIs SET ns = ? WHERE id_sai = ?"
|
||||
val statement = connection.prepareStatement(sql)
|
||||
statement.setString(1, sai.ns)
|
||||
statement.setInt(2, sai.id)
|
||||
if (statement.executeUpdate() > 0) Result.success(Unit)
|
||||
else Result.failure(Exception("update_failed"))
|
||||
} catch (e: SQLException) {
|
||||
Result.failure(e)
|
||||
} finally {
|
||||
connection.close()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun delete(id: Int): Result<Unit> = withContext(Dispatchers.IO) {
|
||||
val connection = MariaDbConnection.getConnection() ?: return@withContext Result.failure(Exception("db_connection_error"))
|
||||
try {
|
||||
val sql = "DELETE FROM SAIs WHERE id_sai = ?"
|
||||
val statement = connection.prepareStatement(sql)
|
||||
statement.setInt(1, id)
|
||||
if (statement.executeUpdate() > 0) Result.success(Unit)
|
||||
else Result.failure(Exception("delete_failed"))
|
||||
} catch (e: SQLException) {
|
||||
Result.failure(e)
|
||||
} finally {
|
||||
connection.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package com.example.saipp.data
|
||||
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.sql.SQLException
|
||||
|
||||
class ScannerRepository {
|
||||
companion object {
|
||||
private const val TAG = "ScannerRepository"
|
||||
}
|
||||
|
||||
suspend fun saveBarcode(barcode: String): Result<Unit> = withContext(Dispatchers.IO) {
|
||||
val connection = MariaDbConnection.getConnection()
|
||||
if (connection == null) {
|
||||
return@withContext Result.failure(Exception("db_connection_error"))
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO: Customize table and column names
|
||||
val sql = "INSERT INTO scans (barcode, timestamp) VALUES (?, CURRENT_TIMESTAMP)"
|
||||
val statement = connection.prepareStatement(sql)
|
||||
statement.setString(1, barcode)
|
||||
|
||||
val rowsInserted = statement.executeUpdate()
|
||||
if (rowsInserted > 0) {
|
||||
Log.d(TAG, "Barcode saved successfully: $barcode")
|
||||
Result.success(Unit)
|
||||
} else {
|
||||
Result.failure(Exception("no_rows_inserted"))
|
||||
}
|
||||
} catch (e: SQLException) {
|
||||
Log.e(TAG, "Database error: ${e.message}", e)
|
||||
Result.failure(e)
|
||||
} finally {
|
||||
try {
|
||||
connection.close()
|
||||
} catch (e: SQLException) {
|
||||
Log.e(TAG, "Error closing connection: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun testConnection(): Result<Unit> = withContext(Dispatchers.IO) {
|
||||
val connection = MariaDbConnection.getConnection()
|
||||
if (connection != null) {
|
||||
try {
|
||||
connection.close()
|
||||
Result.success(Unit)
|
||||
} catch (e: SQLException) {
|
||||
Result.failure(e)
|
||||
}
|
||||
} else {
|
||||
Result.failure(Exception("connection_failed"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.example.saipp.data.model
|
||||
|
||||
data class Battery(
|
||||
val id: Int = 0,
|
||||
val model: String,
|
||||
val status: String
|
||||
)
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.example.saipp.data.model
|
||||
|
||||
data class Person(
|
||||
val id: Int = 0,
|
||||
val name: String,
|
||||
val position: String
|
||||
)
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.example.saipp.data.model
|
||||
|
||||
data class Sai(
|
||||
val id: Int = 0,
|
||||
val ns: String
|
||||
)
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.example.saipp.ui
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.example.saipp.ui.management.*
|
||||
|
||||
sealed class Screen(val route: String) {
|
||||
object Home : Screen("home")
|
||||
object SaiList : Screen("sai_list")
|
||||
object SaiScanner : Screen("sai_scanner")
|
||||
object BatteryList : Screen("battery_list")
|
||||
object AddBattery : Screen("add_battery")
|
||||
object PersonList : Screen("person_list")
|
||||
object AddPerson : Screen("add_person")
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AppNavigation() {
|
||||
val navController = rememberNavController()
|
||||
|
||||
NavHost(navController = navController, startDestination = Screen.Home.route) {
|
||||
composable(Screen.Home.route) {
|
||||
HomeScreen(
|
||||
onNavigateToSais = { navController.navigate(Screen.SaiList.route) },
|
||||
onNavigateToBatteries = { navController.navigate(Screen.BatteryList.route) },
|
||||
onNavigateToPeople = { navController.navigate(Screen.PersonList.route) }
|
||||
)
|
||||
}
|
||||
composable(Screen.SaiList.route) {
|
||||
SaiListScreen(
|
||||
onNavigateBack = { navController.popBackStack() },
|
||||
onNavigateToScan = { navController.navigate(Screen.SaiScanner.route) }
|
||||
)
|
||||
}
|
||||
composable(Screen.SaiScanner.route) {
|
||||
SaiScannerScreen(
|
||||
onNavigateBack = { navController.popBackStack() }
|
||||
)
|
||||
}
|
||||
composable(Screen.BatteryList.route) {
|
||||
BatteryListScreen(
|
||||
onNavigateBack = { navController.popBackStack() },
|
||||
onNavigateToAdd = { navController.navigate(Screen.AddBattery.route) }
|
||||
)
|
||||
}
|
||||
composable(Screen.AddBattery.route) {
|
||||
AddBatteryScreen(
|
||||
onNavigateBack = { navController.popBackStack() }
|
||||
)
|
||||
}
|
||||
composable(Screen.PersonList.route) {
|
||||
PersonListScreen(
|
||||
onNavigateBack = { navController.popBackStack() },
|
||||
onNavigateToAdd = { navController.navigate(Screen.AddPerson.route) }
|
||||
)
|
||||
}
|
||||
composable(Screen.AddPerson.route) {
|
||||
AddPersonScreen(
|
||||
onNavigateBack = { navController.popBackStack() }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.example.saipp.ui
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.example.saipp.R
|
||||
|
||||
@Composable
|
||||
fun ResultDialog(
|
||||
barcode: String,
|
||||
onDismiss: () -> Unit,
|
||||
onConfirm: () -> Unit
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text(text = stringResource(R.string.dialog_title)) },
|
||||
text = { Text(text = stringResource(R.string.dialog_message, barcode)) },
|
||||
confirmButton = {
|
||||
Button(onClick = onConfirm) {
|
||||
Text(stringResource(R.string.button_save))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text(stringResource(R.string.button_close))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ConnectionIndicator(
|
||||
modifier: Modifier = Modifier,
|
||||
isChecking: Boolean,
|
||||
status: Result<Unit>?
|
||||
) {
|
||||
Surface(
|
||||
modifier = modifier,
|
||||
color = Color.Black.copy(alpha = 0.6f),
|
||||
shape = CircleShape
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
val color = when {
|
||||
isChecking -> Color.Gray
|
||||
status?.isSuccess == true -> Color.Green
|
||||
else -> Color.Red
|
||||
}
|
||||
|
||||
Surface(
|
||||
modifier = Modifier.size(8.dp),
|
||||
color = color,
|
||||
shape = CircleShape
|
||||
) {}
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
Text(
|
||||
text = when {
|
||||
isChecking -> stringResource(R.string.checking_server)
|
||||
status?.isSuccess == true -> stringResource(R.string.connected_status)
|
||||
else -> stringResource(R.string.disconnected_status)
|
||||
},
|
||||
color = Color.White,
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.example.saipp.ui.management
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.example.saipp.data.BatteryRepository
|
||||
import com.example.saipp.data.model.Battery
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AddBatteryScreen(
|
||||
onNavigateBack: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val repository = remember { BatteryRepository() }
|
||||
|
||||
var model by remember { mutableStateOf("") }
|
||||
var status by remember { mutableStateOf("") }
|
||||
var isSaving by remember { mutableStateOf(false) }
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text("Añadir Batería") },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onNavigateBack) {
|
||||
Icon(Icons.Default.ArrowBack, contentDescription = "Atrás")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
) { padding ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(padding)
|
||||
.padding(16.dp)
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = model,
|
||||
onValueChange = { model = it },
|
||||
label = { Text("Modelo") },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
OutlinedTextField(
|
||||
value = status,
|
||||
onValueChange = { status = it },
|
||||
label = { Text("Estado") },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
Button(
|
||||
onClick = {
|
||||
if (model.isNotBlank() && status.isNotBlank()) {
|
||||
isSaving = true
|
||||
scope.launch {
|
||||
repository.insert(Battery(model = model, status = status))
|
||||
.onSuccess {
|
||||
Toast.makeText(context, "Batería añadida", Toast.LENGTH_SHORT).show()
|
||||
onNavigateBack()
|
||||
}
|
||||
.onFailure { Toast.makeText(context, "Error: ${it.message}", Toast.LENGTH_SHORT).show() }
|
||||
isSaving = false
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
enabled = !isSaving
|
||||
) {
|
||||
if (isSaving) CircularProgressIndicator(modifier = Modifier.size(24.dp))
|
||||
else Text("Guardar")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.example.saipp.ui.management
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.example.saipp.data.PersonRepository
|
||||
import com.example.saipp.data.model.Person
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AddPersonScreen(
|
||||
onNavigateBack: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val repository = remember { PersonRepository() }
|
||||
|
||||
var name by remember { mutableStateOf("") }
|
||||
var position by remember { mutableStateOf("") }
|
||||
var isSaving by remember { mutableStateOf(false) }
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text("Añadir Persona") },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onNavigateBack) {
|
||||
Icon(Icons.Default.ArrowBack, contentDescription = "Atrás")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
) { padding ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(padding)
|
||||
.padding(16.dp)
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = name,
|
||||
onValueChange = { name = it },
|
||||
label = { Text("Nombre") },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
OutlinedTextField(
|
||||
value = position,
|
||||
onValueChange = { position = it },
|
||||
label = { Text("Puesto") },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
Button(
|
||||
onClick = {
|
||||
if (name.isNotBlank() && position.isNotBlank()) {
|
||||
isSaving = true
|
||||
scope.launch {
|
||||
repository.insert(Person(name = name, position = position))
|
||||
.onSuccess {
|
||||
Toast.makeText(context, "Persona añadida", Toast.LENGTH_SHORT).show()
|
||||
onNavigateBack()
|
||||
}
|
||||
.onFailure { Toast.makeText(context, "Error: ${it.message}", Toast.LENGTH_SHORT).show() }
|
||||
isSaving = false
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
enabled = !isSaving
|
||||
) {
|
||||
if (isSaving) CircularProgressIndicator(modifier = Modifier.size(24.dp))
|
||||
else Text("Guardar")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package com.example.saipp.ui.management
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.example.saipp.data.BatteryRepository
|
||||
import com.example.saipp.data.model.Battery
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun BatteryListScreen(
|
||||
onNavigateBack: () -> Unit,
|
||||
onNavigateToAdd: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val repository = remember { BatteryRepository() }
|
||||
var items by remember { mutableStateOf<List<Battery>>(emptyList()) }
|
||||
var isLoading by remember { mutableStateOf(true) }
|
||||
|
||||
fun loadItems() {
|
||||
scope.launch {
|
||||
isLoading = true
|
||||
repository.getAll()
|
||||
.onSuccess { items = it }
|
||||
.onFailure { Toast.makeText(context, "Error: ${it.message}", Toast.LENGTH_SHORT).show() }
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
loadItems()
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text("Listado de Baterías") },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onNavigateBack) {
|
||||
Icon(Icons.Default.ArrowBack, contentDescription = "Atrás")
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
FloatingActionButton(onClick = onNavigateToAdd) {
|
||||
Icon(Icons.Default.Add, contentDescription = "Añadir Batería")
|
||||
}
|
||||
}
|
||||
) { padding ->
|
||||
if (isLoading) {
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
} else {
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(padding)
|
||||
) {
|
||||
items(items) { item ->
|
||||
ListItem(
|
||||
headlineContent = { Text("Modelo: ${item.model}") },
|
||||
supportingContent = { Text("Estado: ${item.status}") },
|
||||
trailingContent = {
|
||||
IconButton(onClick = {
|
||||
scope.launch {
|
||||
repository.delete(item.id).onSuccess { loadItems() }
|
||||
}
|
||||
}) {
|
||||
Icon(Icons.Default.Delete, contentDescription = "Eliminar")
|
||||
}
|
||||
}
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.example.saipp.ui.management
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.example.saipp.ui.ConnectionIndicator
|
||||
import com.example.saipp.R
|
||||
import com.example.saipp.data.MariaDbConnection
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun HomeScreen(
|
||||
onNavigateToSais: () -> Unit,
|
||||
onNavigateToBatteries: () -> Unit,
|
||||
onNavigateToPeople: () -> Unit
|
||||
) {
|
||||
var connectionStatus by remember { mutableStateOf<Result<Unit>?>(null) }
|
||||
var isCheckingConnection by remember { mutableStateOf(true) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
isCheckingConnection = true
|
||||
connectionStatus = MariaDbConnection.testConnection()
|
||||
isCheckingConnection = false
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(title = { Text(stringResource(R.string.app_name)) })
|
||||
}
|
||||
) { padding ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(padding)
|
||||
.padding(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
ConnectionIndicator(
|
||||
isChecking = isCheckingConnection,
|
||||
status = connectionStatus
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
|
||||
MenuButton(
|
||||
text = "Gestión de SAIs",
|
||||
onClick = onNavigateToSais,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
MenuButton(
|
||||
text = "Gestión de Baterías",
|
||||
onClick = onNavigateToBatteries,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
MenuButton(
|
||||
text = "Gestión de Personas",
|
||||
onClick = onNavigateToPeople,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MenuButton(
|
||||
text: String,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Button(
|
||||
onClick = onClick,
|
||||
modifier = modifier.height(64.dp),
|
||||
shape = MaterialTheme.shapes.medium
|
||||
) {
|
||||
Text(text = text, style = MaterialTheme.typography.titleMedium)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package com.example.saipp.ui.management
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.example.saipp.data.PersonRepository
|
||||
import com.example.saipp.data.model.Person
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun PersonListScreen(
|
||||
onNavigateBack: () -> Unit,
|
||||
onNavigateToAdd: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val repository = remember { PersonRepository() }
|
||||
var items by remember { mutableStateOf<List<Person>>(emptyList()) }
|
||||
var isLoading by remember { mutableStateOf(true) }
|
||||
|
||||
fun loadItems() {
|
||||
scope.launch {
|
||||
isLoading = true
|
||||
repository.getAll()
|
||||
.onSuccess { items = it }
|
||||
.onFailure { Toast.makeText(context, "Error: ${it.message}", Toast.LENGTH_SHORT).show() }
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
loadItems()
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text("Listado de Personas") },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onNavigateBack) {
|
||||
Icon(Icons.Default.ArrowBack, contentDescription = "Atrás")
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
FloatingActionButton(onClick = onNavigateToAdd) {
|
||||
Icon(Icons.Default.Add, contentDescription = "Añadir Persona")
|
||||
}
|
||||
}
|
||||
) { padding ->
|
||||
if (isLoading) {
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
} else {
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(padding)
|
||||
) {
|
||||
items(items) { item ->
|
||||
ListItem(
|
||||
headlineContent = { Text(item.name) },
|
||||
supportingContent = { Text(item.position) },
|
||||
trailingContent = {
|
||||
IconButton(onClick = {
|
||||
scope.launch {
|
||||
repository.delete(item.id).onSuccess { loadItems() }
|
||||
}
|
||||
}) {
|
||||
Icon(Icons.Default.Delete, contentDescription = "Eliminar")
|
||||
}
|
||||
}
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.example.saipp.ui.management
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.example.saipp.data.SaiRepository
|
||||
import com.example.saipp.data.model.Sai
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SaiListScreen(
|
||||
onNavigateBack: () -> Unit,
|
||||
onNavigateToScan: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val repository = remember { SaiRepository() }
|
||||
var sais by remember { mutableStateOf<List<Sai>>(emptyList()) }
|
||||
var isLoading by remember { mutableStateOf(true) }
|
||||
|
||||
fun loadSais() {
|
||||
scope.launch {
|
||||
isLoading = true
|
||||
repository.getAll()
|
||||
.onSuccess { sais = it }
|
||||
.onFailure { Toast.makeText(context, "Error: ${it.message}", Toast.LENGTH_SHORT).show() }
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
loadSais()
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text("Listado de SAIs") },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onNavigateBack) {
|
||||
Icon(Icons.Default.ArrowBack, contentDescription = "Atrás")
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
FloatingActionButton(onClick = onNavigateToScan) {
|
||||
Icon(Icons.Default.Add, contentDescription = "Añadir SAI")
|
||||
}
|
||||
}
|
||||
) { padding ->
|
||||
if (isLoading) {
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
} else {
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(padding)
|
||||
) {
|
||||
items(sais) { sai ->
|
||||
ListItem(
|
||||
headlineContent = { Text("NS: ${sai.ns}") },
|
||||
trailingContent = {
|
||||
IconButton(onClick = {
|
||||
scope.launch {
|
||||
repository.delete(sai.id).onSuccess { loadSais() }
|
||||
}
|
||||
}) {
|
||||
Icon(Icons.Default.Delete, contentDescription = "Eliminar")
|
||||
}
|
||||
}
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package com.example.saipp.ui.management
|
||||
|
||||
import android.Manifest
|
||||
import android.content.pm.PackageManager
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.example.saipp.ui.ResultDialog
|
||||
import com.example.saipp.data.SaiRepository
|
||||
import com.example.saipp.ui.scanner.BarcodeScannerView
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SaiScannerScreen(
|
||||
onNavigateBack: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val repository = remember { SaiRepository() }
|
||||
|
||||
var hasCameraPermission by remember {
|
||||
mutableStateOf(
|
||||
ContextCompat.checkSelfPermission(
|
||||
context,
|
||||
Manifest.permission.CAMERA
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
)
|
||||
}
|
||||
|
||||
val launcher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.RequestPermission(),
|
||||
onResult = { granted ->
|
||||
hasCameraPermission = granted
|
||||
}
|
||||
)
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
if (!hasCameraPermission) {
|
||||
launcher.launch(Manifest.permission.CAMERA)
|
||||
}
|
||||
}
|
||||
|
||||
var scannedBarcode by remember { mutableStateOf<String?>(null) }
|
||||
var isSaving by remember { mutableStateOf(false) }
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text("Escanear SAI") },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onNavigateBack) {
|
||||
Icon(Icons.Default.ArrowBack, contentDescription = "Atrás")
|
||||
}
|
||||
},
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.surface.copy(alpha = 0.5f)
|
||||
)
|
||||
)
|
||||
}
|
||||
) { padding ->
|
||||
Box(modifier = Modifier.fillMaxSize().padding(padding)) {
|
||||
if (hasCameraPermission) {
|
||||
if (scannedBarcode == null) {
|
||||
BarcodeScannerView(onBarcodeDetected = { barcode ->
|
||||
scannedBarcode = barcode
|
||||
})
|
||||
}
|
||||
|
||||
if (isSaving) {
|
||||
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
|
||||
}
|
||||
} else {
|
||||
Text(
|
||||
text = "Se requiere permiso de cámara.",
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
)
|
||||
}
|
||||
|
||||
scannedBarcode?.let { barcode ->
|
||||
ResultDialog(
|
||||
barcode = barcode,
|
||||
onDismiss = { scannedBarcode = null },
|
||||
onConfirm = {
|
||||
scannedBarcode = null
|
||||
isSaving = true
|
||||
scope.launch {
|
||||
repository.insert(barcode)
|
||||
.onSuccess {
|
||||
Toast.makeText(context, "SAI guardado", Toast.LENGTH_SHORT).show()
|
||||
onNavigateBack()
|
||||
}
|
||||
.onFailure { Toast.makeText(context, "Error: ${it.message}", Toast.LENGTH_SHORT).show() }
|
||||
isSaving = false
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user