Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e50d0bf4ce | ||
|
|
4b840bf87d | ||
|
|
36abc10972 | ||
|
|
5cf7e2b7b2 | ||
|
|
6f9490ea1c | ||
|
|
61090eefca | ||
|
|
52c485af64 | ||
|
|
fba54e0243 | ||
|
|
7d6cf9dd7b | ||
|
|
b18e99156a | ||
|
|
bb7807e2a4 |
+12
-2
@@ -4,6 +4,16 @@ plugins {
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
}
|
||||
|
||||
fun generateVersionCode(): Int {
|
||||
return try {
|
||||
val process = ProcessBuilder("git", "rev-list", "--count", "HEAD").start()
|
||||
|
||||
"${process.inputStream.bufferedReader().readText().trim()}0".toInt() // 0 at end for mobile
|
||||
} catch (e: Exception) {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "ru.omni_devel.cards"
|
||||
compileSdk = 36
|
||||
@@ -12,8 +22,8 @@ android {
|
||||
applicationId = "ru.omni_devel.cards"
|
||||
minSdk = 30
|
||||
targetSdk = 36
|
||||
versionCode = 60 // 0 at end for mobile
|
||||
versionName = "2.1.1"
|
||||
versionCode = generateVersionCode()
|
||||
versionName = "2.3.1"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -9,24 +9,20 @@
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.OmniCards">
|
||||
<activity
|
||||
android:name=".EditCardActivity"
|
||||
android:name=".BackupActivity"
|
||||
android:exported="false"
|
||||
android:label="@string/title_activity_edit_card"
|
||||
android:theme="@style/Theme.OmniCards" />
|
||||
<activity
|
||||
android:name=".AddCardActivity"
|
||||
android:name=".EditCardActivity"
|
||||
android:exported="false"
|
||||
android:label="@string/title_activity_add_card"
|
||||
android:theme="@style/Theme.OmniCards" />
|
||||
<activity
|
||||
android:name=".ShowCardActivity"
|
||||
android:exported="false"
|
||||
android:label="@string/title_activity_show_card"
|
||||
android:theme="@style/Theme.OmniCards" />
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.OmniCards">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
package ru.omni_devel.cards
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.LocalActivity
|
||||
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.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import kotlinx.serialization.json.Json
|
||||
import ru.omni_devel.cards.ui.theme.OmniCardsTheme
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
||||
class BackupActivity : ComponentActivity() {
|
||||
private lateinit var db: DbHelper
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
|
||||
db = DbHelper(this, null)
|
||||
|
||||
setContent {
|
||||
OmniCardsTheme {
|
||||
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
|
||||
BackupPage(innerPadding = innerPadding, getDbFun = {
|
||||
return@BackupPage db
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun BackupPage(innerPadding: PaddingValues, getDbFun: () -> DbHelper) {
|
||||
val context = LocalContext.current
|
||||
val activityContext = LocalActivity.current
|
||||
|
||||
val showImportDialog = rememberSaveable { mutableStateOf(false) }
|
||||
var pendingUri by rememberSaveable { mutableStateOf<Uri?>(null) }
|
||||
|
||||
val createBackupLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.CreateDocument("application/json")
|
||||
) { uri ->
|
||||
uri?.let {
|
||||
try {
|
||||
val jsonData = Json.encodeToString(getDbFun().getCards())
|
||||
|
||||
context.contentResolver.openOutputStream(it)?.use { outputStream ->
|
||||
outputStream.write(jsonData.toByteArray())
|
||||
|
||||
Toast.makeText(context, context.getString(R.string.backup_successfully_saved), Toast.LENGTH_SHORT).show()
|
||||
|
||||
activityContext!!.finish()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Toast.makeText(context, context.getString(R.string.failed_to_save_backup, e), Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val restoreFromBackupLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.GetContent()
|
||||
) { uri ->
|
||||
uri?.let {
|
||||
pendingUri = it
|
||||
showImportDialog.value = true
|
||||
}
|
||||
}
|
||||
|
||||
if (showImportDialog.value) {
|
||||
AlertDialog(
|
||||
title = { Text(stringResource(R.string.ask_do_restore)) },
|
||||
text = { Text(stringResource(R.string.restore_caution)) },
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
showImportDialog.value = false
|
||||
|
||||
pendingUri?.let { uri ->
|
||||
try {
|
||||
context.contentResolver.openInputStream(uri)?.use { stream ->
|
||||
val json = stream.bufferedReader().use { it.readText() }
|
||||
|
||||
val cards = Json.decodeFromString<List<CardInfo>>(json)
|
||||
|
||||
val db = getDbFun()
|
||||
|
||||
db.clearDatabase()
|
||||
db.addCards(cards)
|
||||
|
||||
Toast.makeText(context, context.getString(R.string.backup_successfully_restored), Toast.LENGTH_SHORT).show()
|
||||
|
||||
activityContext!!.finish()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Toast.makeText(context, context.getString(R.string.failed_to_restore_backup, e), Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
Text(stringResource(R.string.yes))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
showImportDialog.value = false
|
||||
}
|
||||
) {
|
||||
Text(stringResource(R.string.no))
|
||||
}
|
||||
},
|
||||
onDismissRequest = { showImportDialog.value = false },
|
||||
)
|
||||
}
|
||||
|
||||
Page("Backup", innerPadding) {
|
||||
FullWidthButton(
|
||||
onClick = {
|
||||
createBackupLauncher.launch("omnicards-backup.json")
|
||||
}
|
||||
) {
|
||||
Text(stringResource(R.string.do_backup))
|
||||
}
|
||||
FullWidthButton(
|
||||
onClick = {
|
||||
restoreFromBackupLauncher.launch("application/json")
|
||||
}
|
||||
) {
|
||||
Text(stringResource(R.string.do_restore))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,4 +106,28 @@ class DbHelper(val context: Context, val factory: SQLiteDatabase.CursorFactory?)
|
||||
|
||||
db.close()
|
||||
}
|
||||
|
||||
fun clearDatabase() {
|
||||
val db = this.writableDatabase
|
||||
|
||||
db.execSQL("DELETE FROM cards")
|
||||
}
|
||||
|
||||
fun addCards(cards: List<CardInfo>) {
|
||||
val db = this.writableDatabase
|
||||
|
||||
for (card in cards) {
|
||||
val values = ContentValues()
|
||||
|
||||
values.put("id", card.id)
|
||||
values.put("name", card.name)
|
||||
values.put("codeValue", card.codeValue)
|
||||
values.put("codeType", card.codeType.name)
|
||||
values.put("color", card.color)
|
||||
|
||||
db.insert("cards", null, values)
|
||||
}
|
||||
|
||||
db.close()
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package ru.omni_devel.cards
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.activity.ComponentActivity
|
||||
@@ -23,7 +22,6 @@ import androidx.compose.foundation.verticalScroll
|
||||
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.Switch
|
||||
@@ -32,11 +30,12 @@ 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.runtime.saveable.rememberSaveable
|
||||
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.graphics.toColorInt
|
||||
import com.github.skydoves.colorpicker.compose.ColorEnvelope
|
||||
@@ -82,9 +81,11 @@ fun CardEditable(
|
||||
color: String?,
|
||||
onColorChange: (String?) -> Unit
|
||||
) {
|
||||
var isCodeTypeDropdownExpanded by remember { mutableStateOf(false) }
|
||||
var isColorPickerExpanded by remember { mutableStateOf(false) }
|
||||
var isColorEnabled by remember { mutableStateOf(color != null) }
|
||||
val context = LocalContext.current
|
||||
|
||||
var isCodeTypeDropdownExpanded by rememberSaveable { mutableStateOf(false) }
|
||||
var isColorPickerExpanded by rememberSaveable { mutableStateOf(false) }
|
||||
var isColorEnabled by rememberSaveable { mutableStateOf(color != null) }
|
||||
|
||||
val colorPickerController = rememberColorPickerController()
|
||||
|
||||
@@ -108,7 +109,7 @@ fun CardEditable(
|
||||
value = cardName,
|
||||
onValueChange = onCardNameChange,
|
||||
modifier = fieldModifier,
|
||||
label = { Text("Name") }
|
||||
label = { Text(stringResource(R.string.card_name)) }
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
@@ -117,21 +118,21 @@ fun CardEditable(
|
||||
value = codeValue,
|
||||
onValueChange = onCodeValueChange,
|
||||
modifier = Modifier.weight(1f),
|
||||
label = { Text("Value") }
|
||||
label = { Text(stringResource(R.string.card_value)) }
|
||||
)
|
||||
Button(
|
||||
modifier = Modifier.align(Alignment.CenterVertically),
|
||||
onClick = {
|
||||
scanLauncher.launch(
|
||||
ScanOptions().apply {
|
||||
setPrompt("Scan your card")
|
||||
setPrompt(context.getString(R.string.scan_your_card))
|
||||
setBeepEnabled(true)
|
||||
setOrientationLocked(false)
|
||||
}
|
||||
)
|
||||
}
|
||||
) {
|
||||
Text("Scan")
|
||||
Text(stringResource(R.string.do_scan_card))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +143,7 @@ fun CardEditable(
|
||||
value = codeType.name,
|
||||
onValueChange = {},
|
||||
modifier = fieldModifier,
|
||||
label = { Text("Code type") },
|
||||
label = { Text(stringResource(R.string.card_code_type)) },
|
||||
readOnly = true,
|
||||
)
|
||||
Box(
|
||||
@@ -179,7 +180,7 @@ fun CardEditable(
|
||||
isColorPickerExpanded = !isColorPickerExpanded
|
||||
}
|
||||
) {
|
||||
Text("Choose color")
|
||||
Text(stringResource(R.string.do_choose_color))
|
||||
}
|
||||
Switch(
|
||||
checked = isColorEnabled,
|
||||
@@ -210,15 +211,15 @@ fun CardEditable(
|
||||
|
||||
@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) }
|
||||
var color by remember { mutableStateOf<String?>(null) }
|
||||
var cardName by rememberSaveable { mutableStateOf("") }
|
||||
var codeValue by rememberSaveable { mutableStateOf("") }
|
||||
var codeType by rememberSaveable { mutableStateOf(BarcodeFormat.QR_CODE) }
|
||||
var color by rememberSaveable { mutableStateOf<String?>(null) }
|
||||
|
||||
val context = LocalActivity.current
|
||||
|
||||
Page(
|
||||
"New card",
|
||||
stringResource(R.string.new_card_title),
|
||||
innerPadding
|
||||
) {
|
||||
Column(
|
||||
@@ -239,17 +240,19 @@ fun AddCard(innerPadding: PaddingValues, getDbFun: () -> DbHelper) {
|
||||
Button(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = {
|
||||
cardName = cardName.trim()
|
||||
|
||||
val err = checkCardData(cardName, codeValue)
|
||||
|
||||
if (err == null) {
|
||||
getDbFun().addCard(cardName, codeValue, codeType, color)
|
||||
context!!.finish()
|
||||
} else {
|
||||
Toast.makeText(context, err, Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context, context!!.getString(err), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
) {
|
||||
Text("Save")
|
||||
Text(stringResource(R.string.do_save))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -257,15 +260,15 @@ fun AddCard(innerPadding: PaddingValues, getDbFun: () -> DbHelper) {
|
||||
|
||||
@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) }
|
||||
var color by remember { mutableStateOf(cardInfo.color) }
|
||||
var cardName by rememberSaveable { mutableStateOf(cardInfo.name) }
|
||||
var codeValue by rememberSaveable { mutableStateOf(cardInfo.codeValue) }
|
||||
var codeType by rememberSaveable { mutableStateOf(cardInfo.codeType) }
|
||||
var color by rememberSaveable { mutableStateOf(cardInfo.color) }
|
||||
|
||||
val context = LocalActivity.current
|
||||
|
||||
Page(
|
||||
"Editing card id${cardInfo.id}",
|
||||
stringResource(R.string.editing_card_title, cardInfo.id),
|
||||
innerPadding
|
||||
) {
|
||||
Column(
|
||||
@@ -286,17 +289,19 @@ fun EditCard(cardInfo: CardInfo, innerPadding: PaddingValues, getDbFun: () -> Db
|
||||
Button(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = {
|
||||
cardName = cardName.trim()
|
||||
|
||||
val err = checkCardData(cardName, codeValue)
|
||||
|
||||
if (err == null) {
|
||||
getDbFun().editCard(cardInfo.id, cardName, codeValue, codeType, color)
|
||||
context!!.finish()
|
||||
} else {
|
||||
Toast.makeText(context, err, Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context, context!!.getString(err), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
) {
|
||||
Text("Save")
|
||||
Text(stringResource(R.string.do_save))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
@@ -30,6 +31,7 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
@@ -42,6 +44,7 @@ fun Page(name: String, innerPadding: PaddingValues, content: @Composable () -> U
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 16.dp)
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
) {
|
||||
@@ -56,7 +59,9 @@ fun TopBar(text: String, innerPadding: PaddingValues) {
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(MaterialTheme.colorScheme.primaryContainer)
|
||||
.padding(innerPadding)
|
||||
.padding(top = innerPadding.calculateTopPadding())
|
||||
.padding(horizontal = 8.dp)
|
||||
.padding(bottom = 8.dp)
|
||||
) {
|
||||
Text(
|
||||
text,
|
||||
@@ -124,7 +129,7 @@ fun CardButton(cardInfo: CardInfo, onDeleteCard: (Int) -> Unit) {
|
||||
onDismissRequest = { menuExpanded = false }
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = { Text("Edit") },
|
||||
text = { Text(stringResource(R.string.do_edit_card)) },
|
||||
onClick = {
|
||||
menuExpanded = false
|
||||
|
||||
@@ -136,7 +141,7 @@ fun CardButton(cardInfo: CardInfo, onDeleteCard: (Int) -> Unit) {
|
||||
}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text("Delete") },
|
||||
text = { Text(stringResource(R.string.do_delete_card)) },
|
||||
onClick = {
|
||||
menuExpanded = false
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import kotlinx.serialization.json.Json
|
||||
import ru.omni_devel.cards.ui.theme.OmniCardsTheme
|
||||
|
||||
@@ -87,7 +88,7 @@ fun MainPage(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
Page(
|
||||
"OmniCards",
|
||||
stringResource(R.string.app_name),
|
||||
innerPadding
|
||||
) {
|
||||
Column(
|
||||
@@ -95,7 +96,9 @@ fun MainPage(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
if (cards.isEmpty()) {
|
||||
Text("Add new card to get started")
|
||||
Text(
|
||||
stringResource(R.string.add_card_to_get_started)
|
||||
)
|
||||
} else {
|
||||
for (card in cards) {
|
||||
CardButton(card, onDeleteCard)
|
||||
@@ -126,31 +129,44 @@ fun MainPage(
|
||||
context.startActivity(intent)
|
||||
}
|
||||
) {
|
||||
Text("Add card")
|
||||
Text(stringResource(R.string.do_add_card))
|
||||
}
|
||||
FullWidthButton(
|
||||
onClick = {
|
||||
Toast.makeText(context, "Sync in progress...", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context, context.getString(R.string.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()
|
||||
Toast.makeText(context, context.getString(R.string.sync_is_successful), Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
Toast.makeText(context, "Sync failed! Please, check your connection", Toast.LENGTH_LONG).show()
|
||||
Toast.makeText(context, context.getString(R.string.sync_is_fail), Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
isActionBarShowed = false
|
||||
}
|
||||
) {
|
||||
Text("Sync with WearOS")
|
||||
Text(stringResource(R.string.do_sync))
|
||||
}
|
||||
FullWidthButton(
|
||||
onClick = {
|
||||
val intent = Intent(context, BackupActivity::class.java)
|
||||
|
||||
context.startActivity(intent)
|
||||
|
||||
isActionBarShowed = false
|
||||
}
|
||||
) {
|
||||
Text(stringResource(R.string.open_backup_menu))
|
||||
}
|
||||
FullWidthButton(
|
||||
onClick = {
|
||||
uriHandler.openUri("https://github.com/omni-devel/OmniCards")
|
||||
|
||||
isActionBarShowed = false
|
||||
}
|
||||
) {
|
||||
Text("Source code")
|
||||
Text(stringResource(R.string.source_code))
|
||||
}
|
||||
FullWidthButton(
|
||||
onClick = {
|
||||
|
||||
@@ -12,15 +12,19 @@ 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.foundation.layout.padding
|
||||
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.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import ru.omni_devel.cards.ui.theme.OmniCardsTheme
|
||||
|
||||
class ShowCardActivity : ComponentActivity() {
|
||||
@@ -82,14 +86,18 @@ fun ShowCardPage(innerPadding: PaddingValues, cardInfo: CardInfo) {
|
||||
Page(cardInfo.name, innerPadding) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
if (bitmap != null) {
|
||||
Image(
|
||||
bitmap = bitmap,
|
||||
contentDescription = cardInfo.codeValue,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
contentScale = ContentScale.FillWidth
|
||||
modifier = Modifier
|
||||
.padding(bottom = 8.dp)
|
||||
.fillMaxWidth()
|
||||
.weight(1f, fill = false),
|
||||
contentScale = ContentScale.Fit
|
||||
)
|
||||
Text(
|
||||
cardInfo.codeValue,
|
||||
@@ -98,7 +106,7 @@ fun ShowCardPage(innerPadding: PaddingValues, cardInfo: CardInfo) {
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
"Failed to generate code. Check card data",
|
||||
stringResource(R.string.failed_to_generate_card_code),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
textAlign = TextAlign.Center,
|
||||
color = MaterialTheme.colorScheme.error
|
||||
|
||||
@@ -2,6 +2,7 @@ package ru.omni_devel.cards
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.google.android.gms.wearable.Wearable
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import com.google.zxing.MultiFormatWriter
|
||||
@@ -49,11 +50,13 @@ fun sendToWatch(context: Context, path: String, message: String, onResult: (Bool
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
fun checkCardData(name: String, value: String): Int? {
|
||||
if (name.isEmpty()) {
|
||||
return R.string.card_name_should_not_be_empty
|
||||
} else if (value.isEmpty()) {
|
||||
return R.string.card_value_should_not_be_empty
|
||||
} else if (name.length > 32) {
|
||||
return R.string.name_must_be_shorter_than_32_symbols
|
||||
}
|
||||
|
||||
return null
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<resources>
|
||||
<string name="add_card_to_get_started">Добавьте новую карту для того, чтобы начать работу</string>
|
||||
|
||||
<string name="do_add_card">Добавить карту</string>
|
||||
<string name="do_edit_card">Редактировать карту</string>
|
||||
<string name="do_delete_card">Удалить карту</string>
|
||||
<string name="source_code">Исходный код</string>
|
||||
|
||||
<string name="do_sync">Синхронизировать с WearOS</string>
|
||||
<string name="sync_in_progress">Синхронизация в процессе…</string>
|
||||
<string name="sync_is_successful">Успешная синхронизация</string>
|
||||
<string name="sync_is_fail">Ошибка синхронизации. Проверьте подключение к часам</string>
|
||||
|
||||
<string name="open_backup_menu">Бэкап/восстановление</string>
|
||||
<string name="do_backup">Сохранить данные</string>
|
||||
<string name="do_restore">Восстановить данные</string>
|
||||
<string name="ask_do_restore">Восстановить данные?</string>
|
||||
<string name="backup_successfully_saved">Данные успешно сохранены</string>
|
||||
<string name="failed_to_save_backup">Не удалось сохранить данные: %s</string>
|
||||
<string name="backup_successfully_restored">Данные успешно восстановлены</string>
|
||||
<string name="failed_to_restore_backup">Не удалось восстановить данные: %s</string>
|
||||
<string name="restore_caution">Это заменит существующие карты данными из файла резервной копии. Продолжить?</string>
|
||||
|
||||
<string name="yes">Да</string>
|
||||
<string name="no">Нет</string>
|
||||
<string name="do_save">Сохранить</string>
|
||||
|
||||
<string name="new_card_title">Добавление карты</string>
|
||||
<string name="editing_card_title">Редактирование карты #%d</string>
|
||||
<string name="card_name">Название карты</string>
|
||||
<string name="card_value">Значение карты</string>
|
||||
<string name="do_scan_card">Сканировать карту</string>
|
||||
<string name="scan_your_card">Отсканируйте вашу карту</string>
|
||||
<string name="card_code_type">Тип кода карты</string>
|
||||
<string name="do_choose_color">Выбрать цвет</string>
|
||||
<string name="card_name_should_not_be_empty">Имя карты не должно быть пустым</string>
|
||||
<string name="card_value_should_not_be_empty">Значение карты не должно быть пустым</string>
|
||||
<string name="name_must_be_shorter_than_32_symbols">Имя карты должно быть короче 32 символов</string>
|
||||
|
||||
<string name="failed_to_generate_card_code">Не удалось сгенерировать код карты. Проверьте данные</string>
|
||||
</resources>
|
||||
@@ -1,48 +1,46 @@
|
||||
<resources>
|
||||
<string name="app_name">OmniCards</string>
|
||||
<!-- Strings used for fragments for navigation -->
|
||||
<string name="first_fragment_label">First Fragment</string>
|
||||
<string name="second_fragment_label">Second Fragment</string>
|
||||
<string name="next">Next</string>
|
||||
<string name="previous">Previous</string>
|
||||
<string name="first_fragment_label" translatable="false">First Fragment</string>
|
||||
<string name="second_fragment_label" translatable="false">Second Fragment</string>
|
||||
|
||||
<string name="lorem_ipsum">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam in scelerisque sem. Mauris
|
||||
volutpat, dolor id interdum ullamcorper, risus dolor egestas lectus, sit amet mattis purus
|
||||
dui nec risus. Maecenas non sodales nisi, vel dictum dolor. Class aptent taciti sociosqu ad
|
||||
litora torquent per conubia nostra, per inceptos himenaeos. Suspendisse blandit eleifend
|
||||
diam, vel rutrum tellus vulputate quis. Aliquam eget libero aliquet, imperdiet nisl a,
|
||||
ornare ex. Sed rhoncus est ut libero porta lobortis. Fusce in dictum tellus.\n\n
|
||||
Suspendisse interdum ornare ante. Aliquam nec cursus lorem. Morbi id magna felis. Vivamus
|
||||
egestas, est a condimentum egestas, turpis nisl iaculis ipsum, in dictum tellus dolor sed
|
||||
neque. Morbi tellus erat, dapibus ut sem a, iaculis tincidunt dui. Interdum et malesuada
|
||||
fames ac ante ipsum primis in faucibus. Curabitur et eros porttitor, ultricies urna vitae,
|
||||
molestie nibh. Phasellus at commodo eros, non aliquet metus. Sed maximus nisl nec dolor
|
||||
bibendum, vel congue leo egestas.\n\n
|
||||
Sed interdum tortor nibh, in sagittis risus mollis quis. Curabitur mi odio, condimentum sit
|
||||
amet auctor at, mollis non turpis. Nullam pretium libero vestibulum, finibus orci vel,
|
||||
molestie quam. Fusce blandit tincidunt nulla, quis sollicitudin libero facilisis et. Integer
|
||||
interdum nunc ligula, et fermentum metus hendrerit id. Vestibulum lectus felis, dictum at
|
||||
lacinia sit amet, tristique id quam. Cras eu consequat dui. Suspendisse sodales nunc ligula,
|
||||
in lobortis sem porta sed. Integer id ultrices magna, in luctus elit. Sed a pellentesque
|
||||
est.\n\n
|
||||
Aenean nunc velit, lacinia sed dolor sed, ultrices viverra nulla. Etiam a venenatis nibh.
|
||||
Morbi laoreet, tortor sed facilisis varius, nibh orci rhoncus nulla, id elementum leo dui
|
||||
non lorem. Nam mollis ipsum quis auctor varius. Quisque elementum eu libero sed commodo. In
|
||||
eros nisl, imperdiet vel imperdiet et, scelerisque a mauris. Pellentesque varius ex nunc,
|
||||
quis imperdiet eros placerat ac. Duis finibus orci et est auctor tincidunt. Sed non viverra
|
||||
ipsum. Nunc quis augue egestas, cursus lorem at, molestie sem. Morbi a consectetur ipsum, a
|
||||
placerat diam. Etiam vulputate dignissim convallis. Integer faucibus mauris sit amet finibus
|
||||
convallis.\n\n
|
||||
Phasellus in aliquet mi. Pellentesque habitant morbi tristique senectus et netus et
|
||||
malesuada fames ac turpis egestas. In volutpat arcu ut felis sagittis, in finibus massa
|
||||
gravida. Pellentesque id tellus orci. Integer dictum, lorem sed efficitur ullamcorper,
|
||||
libero justo consectetur ipsum, in mollis nisl ex sed nisl. Donec maximus ullamcorper
|
||||
sodales. Praesent bibendum rhoncus tellus nec feugiat. In a ornare nulla. Donec rhoncus
|
||||
libero vel nunc consequat, quis tincidunt nisl eleifend. Cras bibendum enim a justo luctus
|
||||
vestibulum. Fusce dictum libero quis erat maximus, vitae volutpat diam dignissim.
|
||||
</string>
|
||||
<string name="title_activity_show_card">ShowCardActivity</string>
|
||||
<string name="title_activity_add_card">AddCardActivity</string>
|
||||
<string name="title_activity_edit_card">EditCardActivity</string>
|
||||
<string name="app_name" translatable="false">OmniCards</string>
|
||||
|
||||
<string name="add_card_to_get_started">Add a new card to get started</string>
|
||||
|
||||
<string name="do_add_card">Add card</string>
|
||||
<string name="do_edit_card">Edit card</string>
|
||||
<string name="do_delete_card">Delete card</string>
|
||||
<string name="source_code">Source code</string>
|
||||
|
||||
<string name="do_sync">Sync with WearOS</string>
|
||||
<string name="sync_in_progress">Sync in progress…</string>
|
||||
<string name="sync_is_successful">Sync successful</string>
|
||||
<string name="sync_is_fail">Sync failed. Check your watch connection</string>
|
||||
|
||||
<string name="open_backup_menu">Backup / Restore</string>
|
||||
<string name="do_backup">Save data</string>
|
||||
<string name="do_restore">Restore data</string>
|
||||
<string name="ask_do_restore">Restore data?</string>
|
||||
<string name="backup_successfully_saved">Data saved successfully</string>
|
||||
<string name="failed_to_save_backup">Failed to save data: %s</string>
|
||||
<string name="backup_successfully_restored">Data restored successfully</string>
|
||||
<string name="failed_to_restore_backup">Failed to restore data: %s</string>
|
||||
<string name="restore_caution">This will replace your existing cards with data from the backup file. Continue?</string>
|
||||
|
||||
<string name="yes">Yes</string>
|
||||
<string name="no">No</string>
|
||||
<string name="do_save">Save</string>
|
||||
|
||||
<string name="new_card_title">Add card</string>
|
||||
<string name="editing_card_title">Editing card #%d</string>
|
||||
<string name="card_name">Card name</string>
|
||||
<string name="card_value">Card value</string>
|
||||
<string name="do_scan_card">Scan card</string>
|
||||
<string name="scan_your_card">Scan your card</string>
|
||||
<string name="card_code_type">Card code type</string>
|
||||
<string name="do_choose_color">Choose color</string>
|
||||
<string name="card_name_should_not_be_empty">Card name must not be empty</string>
|
||||
<string name="card_value_should_not_be_empty">Card value must not be empty</string>
|
||||
<string name="name_must_be_shorter_than_32_symbols">Card name must be shorter than 32 characters</string>
|
||||
|
||||
<string name="failed_to_generate_card_code">Failed to generate card code. Check the card data</string>
|
||||
</resources>
|
||||
+12
-2
@@ -4,6 +4,16 @@ plugins {
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
}
|
||||
|
||||
fun generateVersionCode(): Int {
|
||||
return try {
|
||||
val process = ProcessBuilder("git", "rev-list", "--count", "HEAD").start()
|
||||
|
||||
"${process.inputStream.bufferedReader().readText().trim()}1".toInt() // 0 at end for WearOS
|
||||
} catch (e: Exception) {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "ru.omni_devel.cards"
|
||||
compileSdk = 36
|
||||
@@ -12,8 +22,8 @@ android {
|
||||
applicationId = "ru.omni_devel.cards"
|
||||
minSdk = 30
|
||||
targetSdk = 36
|
||||
versionCode = 61 // 1 at end for WearOS
|
||||
versionName = "1.2.1"
|
||||
versionCode = generateVersionCode()
|
||||
versionName = "1.3.0"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -12,9 +12,11 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.wear.compose.foundation.lazy.TransformingLazyColumn
|
||||
import androidx.wear.compose.material3.Text
|
||||
import ru.omni_devel.cards.R
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
private lateinit var db: DbHelper
|
||||
@@ -41,7 +43,7 @@ fun App(cards: List<CardInfo>) {
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text("Sync with mobile APP to get started")
|
||||
Text(stringResource(R.string.sync_with_mobile_app_to_get_started))
|
||||
}
|
||||
} else {
|
||||
TransformingLazyColumn(
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.widget.Toast
|
||||
import com.google.android.gms.wearable.MessageEvent
|
||||
import com.google.android.gms.wearable.WearableListenerService
|
||||
import kotlinx.serialization.json.Json
|
||||
import ru.omni_devel.cards.R
|
||||
|
||||
class MessagesReceiver : WearableListenerService() {
|
||||
private lateinit var db: DbHelper
|
||||
@@ -28,7 +29,7 @@ class MessagesReceiver : WearableListenerService() {
|
||||
db.addCards(cards)
|
||||
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
Toast.makeText(this, "Database updated!", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(this, this.getString(R.string.sync_is_successful), Toast.LENGTH_SHORT).show()
|
||||
|
||||
val intent = Intent(this, MainActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
|
||||
@@ -19,9 +19,11 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.wear.compose.material3.MaterialTheme
|
||||
import androidx.wear.compose.material3.Text
|
||||
import ru.omni_devel.cards.R
|
||||
|
||||
class ShowCardActivity : ComponentActivity() {
|
||||
private lateinit var db: DbHelper
|
||||
@@ -62,6 +64,8 @@ class ShowCardActivity : ComponentActivity() {
|
||||
super.onPause()
|
||||
|
||||
setBrightness(WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE)
|
||||
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun setBrightness(level: Float) {
|
||||
@@ -103,7 +107,7 @@ fun ShowCardPage(cardInfo: CardInfo) {
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
"Failed to generate code",
|
||||
stringResource(R.string.failed_to_generate_card_code),
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<resources>
|
||||
<string name="sync_with_mobile_app_to_get_started">Синхронизируйте с мобильным приложением для начала работы</string>
|
||||
|
||||
<string name="sync_is_successful">Успешная синхронизация</string>
|
||||
|
||||
<string name="failed_to_generate_card_code">Не удалось сгенерировать код карты. Проверьте данные</string>
|
||||
</resources>
|
||||
@@ -1,4 +1,9 @@
|
||||
<resources>
|
||||
<string name="app_name">OmniCards</string>
|
||||
<string name="hello_world">Hello, %1$s!</string>
|
||||
<string name="app_name" translatable="false">OmniCards</string>
|
||||
|
||||
<string name="sync_with_mobile_app_to_get_started">Sync with mobile APP to get started</string>
|
||||
|
||||
<string name="sync_is_successful">Successfully synced</string>
|
||||
|
||||
<string name="failed_to_generate_card_code">Failed to generate card code. Check the card data</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user