refactor: rewrite mobile app in Compose and add error handling for wear

This commit is contained in:
2026-06-11 17:32:02 +03:00
parent 59207d3351
commit 19a571ac06
40 changed files with 1003 additions and 608 deletions
@@ -1,124 +1,249 @@
package ru.omni_devel.cards
import android.content.Intent
import android.app.Activity
import android.os.Bundle
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.EditText
import android.widget.Spinner
import android.widget.TextView
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.Button
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.IconButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import com.google.zxing.BarcodeFormat
import com.journeyapps.barcodescanner.ScanContract
import com.journeyapps.barcodescanner.ScanOptions
import ru.omni_devel.cards.ui.theme.OmniCardsTheme
class EditCardActivity : AppCompatActivity() {
class EditCardActivity : ComponentActivity() {
private lateinit var db: DbHelper
private val barcodeLauncher = registerForActivityResult(ScanContract()) { result ->
if (result.contents != null) {
setCodeType(result.formatName)
findViewById<EditText>(R.id.cardValueInput).setText(result.contents)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_edit_card)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
val actionName = intent.getStringExtra("action") ?: EditCardAction.ADD.name
val action = EditCardAction.valueOf(actionName)
db = DbHelper(this, null)
val actionLabel: TextView = findViewById(R.id.editCardActionLabel)
val cardNameInput: EditText = findViewById(R.id.cardNameInput)
val cardValueInput: EditText = findViewById(R.id.cardValueInput)
val scanCodeButton: Button = findViewById(R.id.scanCodeButton)
val cardCodeTypeSelector: Spinner = findViewById(R.id.cardCodeTypeSelector)
val saveCardButton: Button = findViewById(R.id.saveCardButton)
val cardId = intent.getIntExtra("cardId", -1)
val cardInfo = db.getCard(cardId)
val options = BarcodeFormat.entries.map { it.name }
val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, options)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
cardCodeTypeSelector.adapter = adapter
scanCodeButton.setOnClickListener {
val options = ScanOptions()
options.setPrompt("Scan code")
options.setBeepEnabled(false)
options.setOrientationLocked(false)
barcodeLauncher.launch(options)
}
saveCardButton.setOnClickListener {
val cardName = cardNameInput.text.toString()
val cardValue = cardValueInput.text.toString()
val cardCodeType = BarcodeFormat.valueOf(cardCodeTypeSelector.selectedItem.toString())
when (action) {
EditCardAction.ADD -> {
db.addCard(cardName, cardValue, cardCodeType)
finish()
}
EditCardAction.EDIT -> {
val cardId = intent.getIntExtra("cardId", -1)
db.editCard(cardId, cardName, cardValue, cardCodeType)
val intent = Intent(this, ShowCardActivity::class.java)
intent.putExtra("cardId", cardId)
startActivity(intent)
finish()
setContent {
OmniCardsTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
EditCardPage(cardInfo, innerPadding, getDbFun = {
return@EditCardPage db
})
}
}
}
}
}
when (action) {
EditCardAction.ADD -> {
setCodeType("QR_CODE")
@Composable
fun CardEditable(
cardName: String,
onCardNameChange: (String) -> Unit,
codeValue: String,
oncodeValueChange: (String) -> Unit,
codeType: BarcodeFormat,
onCodeTypeChange: (BarcodeFormat) -> Unit
) {
var isCodeTypeDropdownExpanded by remember { mutableStateOf(false) }
actionLabel.text = "Adding card"
}
EditCardAction.EDIT -> {
val cardId = intent.getIntExtra("cardId", -1)
val fieldModifier = Modifier.fillMaxWidth()
val cardInfo = db.getCard(cardId)
val scanLauncher = rememberLauncherForActivityResult(
contract = ScanContract()
) { result ->
if (result.contents != null) {
oncodeValueChange(result.contents)
if (cardInfo == null) {
Toast.makeText(this, "cardInfo is null", Toast.LENGTH_LONG).show()
return
}
actionLabel.text = "Editing card"
cardNameInput.setText(cardInfo.name)
cardValueInput.setText(cardInfo.codeValue)
setCodeType(cardInfo.codeType.name)
result.formatName?.let { formatName ->
try {
onCodeTypeChange(BarcodeFormat.valueOf(formatName))
} catch (e: IllegalArgumentException) {}
}
}
}
private fun setCodeType(codeTypeString: String) {
val codeTypes = BarcodeFormat.entries.map { it.name }
val codeTypeIndex = codeTypes.indexOf(codeTypeString)
if (codeTypeIndex >= 0) {
findViewById<Spinner>(R.id.cardCodeTypeSelector).setSelection(codeTypeIndex)
OutlinedTextField(
value = cardName,
onValueChange = onCardNameChange,
modifier = fieldModifier,
label = { Text("Name") }
)
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
OutlinedTextField(
value = codeValue,
onValueChange = oncodeValueChange,
modifier = Modifier.weight(1f),
label = { Text("Value") }
)
Button(
modifier = Modifier.align(Alignment.CenterVertically),
onClick = {
scanLauncher.launch(
ScanOptions().apply {
setPrompt("Scan your card")
setBeepEnabled(true)
setOrientationLocked(false)
}
)
}
) {
Text("Scan")
}
}
}
Box(
modifier = fieldModifier,
) {
OutlinedTextField(
value = codeType.name,
onValueChange = {},
modifier = fieldModifier,
label = { Text("Code type") },
readOnly = true,
)
Box(
modifier = fieldModifier
.matchParentSize()
.clickable { isCodeTypeDropdownExpanded = !isCodeTypeDropdownExpanded }
)
DropdownMenu(
expanded = isCodeTypeDropdownExpanded,
onDismissRequest = {
isCodeTypeDropdownExpanded = false
},
modifier = fieldModifier
) {
BarcodeFormat.entries.forEach { format ->
DropdownMenuItem(
text = { Text(format.name) },
onClick = {
onCodeTypeChange(format)
isCodeTypeDropdownExpanded = false
}
)
}
}
}
}
@Composable
fun AddCard(innerPadding: PaddingValues, getDbFun: () -> DbHelper) {
var cardName by remember { mutableStateOf("") }
var codeValue by remember { mutableStateOf("") }
var codeType by remember { mutableStateOf(BarcodeFormat.QR_CODE) }
val context = LocalContext.current as Activity
Page(
"New card",
innerPadding
) {
Column(
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
CardEditable(
cardName = cardName,
onCardNameChange = { cardName = it },
codeValue = codeValue,
oncodeValueChange = { codeValue = it },
codeType = codeType,
onCodeTypeChange = { codeType = it }
)
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
val err = checkCardData(cardName, codeValue)
if (err == null) {
getDbFun().addCard(cardName, codeValue, codeType)
context.finish()
} else {
Toast.makeText(context, err, Toast.LENGTH_SHORT)
}
}
) {
Text("Save")
}
}
}
}
@Composable
fun EditCard(cardInfo: CardInfo, innerPadding: PaddingValues, getDbFun: () -> DbHelper) {
var cardName by remember { mutableStateOf(cardInfo.name) }
var codeValue by remember { mutableStateOf(cardInfo.codeValue) }
var codeType by remember { mutableStateOf(cardInfo.codeType) }
val context = LocalContext.current as Activity
Page(
"Editing card id${cardInfo.id}",
innerPadding
) {
Column(
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
CardEditable(
cardName = cardName,
onCardNameChange = { cardName = it },
codeValue = codeValue,
oncodeValueChange = { codeValue = it },
codeType = codeType,
onCodeTypeChange = { codeType = it }
)
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
val err = checkCardData(cardName, codeValue)
if (err == null) {
getDbFun().editCard(cardInfo.id, cardName, codeValue, codeType)
context.finish()
} else {
Toast.makeText(context, err, Toast.LENGTH_SHORT)
}
}
) {
Text("Save")
}
}
}
}
@Composable
fun EditCardPage(cardInfo: CardInfo?, innerPadding: PaddingValues, getDbFun: () -> DbHelper) {
if (cardInfo == null) {
AddCard(innerPadding, getDbFun)
} else {
EditCard(cardInfo, innerPadding, getDbFun)
}
}