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
@@ -14,4 +14,4 @@ class CardInfo (
val name: String,
val codeType: BarcodeFormat,
val codeValue: String,
)
)
@@ -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)
}
}
@@ -0,0 +1,132 @@
package ru.omni_devel.cards
import android.content.Intent
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.gestures.detectTapGestures
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.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.runtime.setValue
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@Composable
fun Page(name: String, innerPadding: PaddingValues, content: @Composable () -> Unit) {
Column(
modifier = Modifier.padding(innerPadding)
) {
TopBar(name)
Column(
modifier = Modifier.padding(horizontal = 16.dp)
) {
content()
}
}
}
@Composable
fun TopBar(text: String) {
Row(
modifier = Modifier.fillMaxWidth().background(MaterialTheme.colorScheme.primaryContainer).padding(horizontal = 12.dp, vertical = 16.dp)
) {
Text(
text,
fontSize = 24.sp,
color = MaterialTheme.colorScheme.onPrimaryContainer
)
}
}
@Composable
fun CardButton(cardInfo: CardInfo, onDeleteCard: (Int) -> Unit, getDbFun: () -> DbHelper) {
val context = LocalContext.current
var menuExpanded by remember { mutableStateOf(false) }
Box(modifier = Modifier.fillMaxWidth()) {
Surface(
modifier = Modifier
.fillMaxWidth()
.pointerInput(Unit) {
detectTapGestures(
onTap = {
val intent = Intent(context, ShowCardActivity::class.java)
intent.putExtra("cardId", cardInfo.id)
context.startActivity(intent)
},
onLongPress = {
menuExpanded = true
}
)
},
shape = ButtonDefaults.shape,
color = MaterialTheme.colorScheme.primaryContainer,
tonalElevation = 2.dp
) {
Text(
cardInfo.name,
fontSize = 16.sp,
color = MaterialTheme.colorScheme.onPrimaryContainer,
modifier = Modifier
.padding(vertical = 16.dp, horizontal = 16.dp)
.fillMaxWidth(),
textAlign = TextAlign.Start
)
}
DropdownMenu(
expanded = menuExpanded,
onDismissRequest = { menuExpanded = false }
) {
DropdownMenuItem(
text = { Text("Edit") },
onClick = {
menuExpanded = false
val intent = Intent(context, EditCardActivity::class.java)
intent.putExtra("cardId", cardInfo.id)
context.startActivity(intent)
}
)
DropdownMenuItem(
text = { Text("Delete") },
onClick = {
menuExpanded = false
onDeleteCard(cardInfo.id)
}
)
}
}
}
@Composable
fun FullWidthButton(onClick: () -> Unit, content: @Composable () -> Unit) {
Button(
modifier = Modifier.fillMaxWidth(),
onClick = onClick
) {
content()
}
}
@@ -2,55 +2,64 @@ package ru.omni_devel.cards
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.LinearLayout
import android.widget.PopupMenu
import android.widget.TextView
import android.widget.Toast
import androidx.activity.ComponentActivity
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 com.google.android.material.button.MaterialButton
import androidx.compose.foundation.background
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.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler
import kotlinx.serialization.json.Json
import ru.omni_devel.cards.ui.theme.OmniCardsTheme
class MainActivity : AppCompatActivity() {
private lateinit var cardsList: LinearLayout
class MainActivity : ComponentActivity() {
private lateinit var db: DbHelper
private val cards = mutableStateListOf<CardInfo>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_main)
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
}
db = DbHelper(this, null)
cardsList = findViewById(R.id.cardsList)
val addCardButton: Button = findViewById(R.id.addCardButton)
val syncWithWearOSButton: Button = findViewById(R.id.syncWithWearOSButton)
addCardButton.setOnClickListener {
val intent = Intent(this, EditCardActivity::class.java)
intent.putExtra("action", EditCardAction.ADD.name)
startActivity(intent)
}
syncWithWearOSButton.setOnClickListener {
Toast.makeText(this, "Sync in progress...", Toast.LENGTH_SHORT).show()
sendToWatch(this, "/updateCards", Json.encodeToString(db.getCards())) { isSuccess ->
if (isSuccess) {
Toast.makeText(this, "Successfully synced!", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "Sync failed! Please, check your connection", Toast.LENGTH_LONG).show()
setContent {
OmniCardsTheme {
Scaffold(
modifier = Modifier.fillMaxSize()
) { innerPadding ->
MainPage(innerPadding, cards, onDeleteCard = { cardId ->
db.removeCard(cardId)
cards.removeIf { it.id == cardId }
}, getDbFun = {
return@MainPage db
})
}
}
}
@@ -58,84 +67,102 @@ class MainActivity : AppCompatActivity() {
override fun onResume() {
super.onResume()
renderCards()
cards.clear()
cards.addAll(db.getCards())
}
}
private fun renderCards() {
val cards = db.getCards()
@Composable
fun MainPage(
innerPadding: PaddingValues,
cards: List<CardInfo>,
onDeleteCard: (Int) -> Unit,
getDbFun: () -> DbHelper
) {
val context = LocalContext.current
val uriHandler = LocalUriHandler.current
cardsList.removeAllViews()
var isActionBarShowed by remember { mutableStateOf(false) }
if (cards.isEmpty()) {
val text = TextView(this)
val params = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
text.text = "Add a card to get started"
text.layoutParams = params
text.textAlignment = View.TEXT_ALIGNMENT_CENTER
cardsList.addView(text)
} else {
for (card in cards) {
val button = MaterialButton(this)
val params = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
params.setMargins(16, 4, 16, 4)
button.text = card.name
button.layoutParams = params
button.cornerRadius = (12 * resources.displayMetrics.density).toInt()
button.setOnClickListener {
val intent = Intent(this, ShowCardActivity::class.java)
intent.putExtra("cardId", card.id)
startActivity(intent)
}
button.setOnLongClickListener { view ->
val popup = PopupMenu(this, view)
popup.menu.add("Edit")
popup.menu.add("Remove")
popup.setOnMenuItemClickListener { menuItem ->
when (menuItem.title) {
"Edit" -> {
val intent = Intent(this, EditCardActivity::class.java)
intent.putExtra("action", EditCardAction.EDIT.name)
intent.putExtra("cardId", card.id)
startActivity(intent)
true
}
"Remove" -> {
db.removeCard(card.id)
renderCards()
true
}
else -> false
}
Box(
modifier = Modifier.fillMaxSize()
) {
Page(
"OmniCards",
innerPadding
) {
Column(
modifier = Modifier.verticalScroll(rememberScrollState()).padding(bottom = 64.dp).background(Color.Transparent).padding(top = 16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
if (cards.isEmpty()) {
Text("Add new card to get started")
} else {
for (card in cards) {
CardButton(card, onDeleteCard, getDbFun)
}
popup.show()
true
}
}
}
cardsList.addView(button)
if (isActionBarShowed) {
Column(
modifier = Modifier.align(Alignment.BottomEnd).padding(16.dp).background(MaterialTheme.colorScheme.inversePrimary,
RoundedCornerShape(24.dp)).padding(16.dp).fillMaxWidth()
) {
FullWidthButton(
onClick = {
isActionBarShowed = false
val intent = Intent(context, EditCardActivity::class.java)
context.startActivity(intent)
}
) {
Text("Add card")
}
FullWidthButton(
onClick = {
Toast.makeText(context, "Sync in progress...", Toast.LENGTH_SHORT).show()
sendToWatch(context, "/updateCards", Json.encodeToString(getDbFun().getCards())) { isSuccess ->
if (isSuccess) {
Toast.makeText(context, "Successfully synced!", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, "Sync failed! Please, check your connection", Toast.LENGTH_LONG).show()
}
}
isActionBarShowed = false
}
) {
Text("Sync with WearOS")
}
FullWidthButton(
onClick = {
uriHandler.openUri("https://omni-devel.ru")
}
) {
Text("About author")
}
FullWidthButton(
onClick = {
isActionBarShowed = false
}
) {
Text(">")
}
}
} else {
Button(
modifier = Modifier.align(Alignment.BottomEnd).padding(32.dp),
onClick = {
isActionBarShowed = true
}
) {
Text("<")
}
}
}
}
}
@@ -2,35 +2,61 @@ package ru.omni_devel.cards
import android.os.Bundle
import android.view.WindowManager
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.activity.ComponentActivity
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.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.style.TextAlign
import ru.omni_devel.cards.ui.theme.OmniCardsTheme
class ShowCardActivity : AppCompatActivity() {
class ShowCardActivity : ComponentActivity() {
private lateinit var db: DbHelper
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_show_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
}
db = DbHelper(this, null)
val cardId = intent.getIntExtra("cardId", -1)
val cardInfo = db.getCard(cardId)
if (cardInfo == null) {
Toast.makeText(this, "Card is not exists", Toast.LENGTH_LONG).show()
finish()
return
}
setContent {
OmniCardsTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
ShowCardPage(innerPadding, cardInfo)
}
}
}
}
override fun onResume() {
super.onResume()
setBrightness(1.0f)
renderCard()
setBrightness(1f)
}
override fun onPause() {
@@ -39,31 +65,46 @@ class ShowCardActivity : AppCompatActivity() {
setBrightness(WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE)
}
private fun renderCard() {
val cardId = intent.getIntExtra("cardId", 0)
val cardInfo = db.getCard(cardId)
if (cardInfo == null) {
Toast.makeText(this, "cardInfo is null", Toast.LENGTH_LONG).show()
return
}
val cardCodeView: ImageView = findViewById(R.id.cardCodeView)
val cardNameElement: TextView = findViewById(R.id.editCardActionLabel)
cardNameElement.text = cardInfo.name
val code = generateCode(cardInfo.codeValue, cardInfo.codeType)
cardCodeView.setImageBitmap(code)
}
private fun setBrightness(level: Float) {
val params = window.attributes
params.screenBrightness = level
window.attributes = params
window.attributes.screenBrightness = level
}
}
}
@Composable
fun ShowCardPage(innerPadding: PaddingValues, cardInfo: CardInfo) {
val bitmap = remember(cardInfo) {
try {
generateCode(cardInfo.codeValue, cardInfo.codeType).asImageBitmap()
} catch (_: Exception) {
null
}
}
Page(cardInfo.name, innerPadding) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center
) {
if (bitmap != null) {
Image(
bitmap = bitmap,
contentDescription = cardInfo.codeValue,
modifier = Modifier.fillMaxWidth(),
contentScale = ContentScale.FillWidth
)
Text(
cardInfo.codeValue,
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center
)
} else {
Text(
"Failed to generate code. Check card data",
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
color = MaterialTheme.colorScheme.error
)
}
}
}
}
@@ -2,7 +2,6 @@ package ru.omni_devel.cards
import android.content.Context
import android.graphics.Bitmap
import android.widget.Toast
import com.google.android.gms.wearable.Wearable
import com.google.zxing.BarcodeFormat
import com.google.zxing.MultiFormatWriter
@@ -10,16 +9,14 @@ import com.journeyapps.barcodescanner.BarcodeEncoder
fun generateCode(value: String, format: BarcodeFormat): Bitmap {
val writer = MultiFormatWriter()
val matrix = writer.encode(value, format, 600, 300)
val matrix = writer.encode(value, format, 800, 400)
return BarcodeEncoder().createBitmap(matrix)
}
fun sendToWatch(context: Context, path: String, message: String, onResult: (Boolean) -> Unit): Boolean {
fun sendToWatch(context: Context, path: String, message: String, onResult: (Boolean) -> Unit) {
val nodeClient = Wearable.getNodeClient(context)
var isSuccess = false
nodeClient.connectedNodes.addOnSuccessListener { nodes ->
if (nodes.isEmpty()) {
onResult(false)
@@ -35,7 +32,7 @@ fun sendToWatch(context: Context, path: String, message: String, onResult: (Bool
node.id,
path,
message.toByteArray(Charsets.UTF_8)
).addOnCompleteListener { task ->
).addOnCompleteListener(context.mainExecutor) { task ->
completedCount++
if (task.isSuccessful) {
@@ -45,11 +42,19 @@ fun sendToWatch(context: Context, path: String, message: String, onResult: (Bool
if (completedCount == nodes.size) {
onResult(isAnySuccess)
}
}.addOnFailureListener {
}.addOnFailureListener(context.mainExecutor) {
onResult(false)
}
}
}
}
return isSuccess
fun checkCardData(name: String, value: String): String? {
if (name.trim().isEmpty()) {
return "Name should not be empty"
} else if (value.trim().isEmpty()) {
return "Code value should not be empty"
}
return null
}
@@ -0,0 +1,11 @@
package ru.omni_devel.cards.ui.theme
import androidx.compose.ui.graphics.Color
val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)
@@ -0,0 +1,58 @@
package ru.omni_devel.cards.ui.theme
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
private val DarkColorScheme = darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80
)
private val LightColorScheme = lightColorScheme(
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40
/* Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/
)
@Composable
fun OmniCardsTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}
@@ -0,0 +1,34 @@
package ru.omni_devel.cards.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with
val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
*/
)