Compare commits
8
Commits
bdde3d8fbf
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c593e06e4c | ||
|
|
235afe61e7 | ||
|
|
8f79b60399 | ||
|
|
20a62b77b6 | ||
|
|
4748a46156 | ||
|
|
c1a4414f10 | ||
|
|
2a538fdf1c | ||
|
|
ff214eb3d2 |
@@ -23,7 +23,7 @@ android {
|
|||||||
minSdk = 30
|
minSdk = 30
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = generateVersionCode()
|
versionCode = generateVersionCode()
|
||||||
versionName = "4.2.1"
|
versionName = "4.3.1"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
@@ -81,4 +81,5 @@ dependencies {
|
|||||||
implementation("io.ktor:ktor-client-core:3.5.0")
|
implementation("io.ktor:ktor-client-core:3.5.0")
|
||||||
implementation("io.ktor:ktor-client-cio:3.5.0")
|
implementation("io.ktor:ktor-client-cio:3.5.0")
|
||||||
implementation("androidx.compose.material:material-icons-extended")
|
implementation("androidx.compose.material:material-icons-extended")
|
||||||
|
implementation("sh.calvin.reorderable:reorderable:2.4.3")
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
@@ -10,6 +11,14 @@
|
|||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.OmniCards">
|
android:theme="@style/Theme.OmniCards">
|
||||||
|
<activity
|
||||||
|
android:name="com.journeyapps.barcodescanner.CaptureActivity"
|
||||||
|
android:screenOrientation="portrait"
|
||||||
|
android:stateNotNeeded="true"
|
||||||
|
android:theme="@style/zxing_CaptureTheme"
|
||||||
|
android:windowSoftInputMode="stateAlwaysHidden"
|
||||||
|
tools:replace="android:screenOrientation" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".SelectIconActivity"
|
android:name=".SelectIconActivity"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
|
|||||||
@@ -15,5 +15,6 @@ class CardInfo (
|
|||||||
val codeType: BarcodeFormat,
|
val codeType: BarcodeFormat,
|
||||||
val codeValue: String,
|
val codeValue: String,
|
||||||
val color: String?,
|
val color: String?,
|
||||||
val icon: String?
|
val icon: String?,
|
||||||
|
val position: Int
|
||||||
) {}
|
) {}
|
||||||
|
|||||||
@@ -2,15 +2,17 @@ package ru.omni_devel.cards
|
|||||||
|
|
||||||
import android.content.ContentValues
|
import android.content.ContentValues
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.database.Cursor
|
||||||
import android.database.sqlite.SQLiteDatabase
|
import android.database.sqlite.SQLiteDatabase
|
||||||
import android.database.sqlite.SQLiteOpenHelper
|
import android.database.sqlite.SQLiteOpenHelper
|
||||||
import androidx.core.database.getStringOrNull
|
import androidx.core.database.getStringOrNull
|
||||||
|
import androidx.core.database.sqlite.transaction
|
||||||
import com.google.zxing.BarcodeFormat
|
import com.google.zxing.BarcodeFormat
|
||||||
|
|
||||||
class DbHelper(val context: Context, val factory: SQLiteDatabase.CursorFactory?) : SQLiteOpenHelper(context, "omni_cards", factory, 6) {
|
class DbHelper(val context: Context, val factory: SQLiteDatabase.CursorFactory?) : SQLiteOpenHelper(context, "omni_cards", factory, 8) {
|
||||||
override fun onCreate(db: SQLiteDatabase?) {
|
override fun onCreate(db: SQLiteDatabase?) {
|
||||||
db!!.execSQL(
|
db!!.execSQL(
|
||||||
"CREATE TABLE IF NOT EXISTS cards (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, codeType TEXT, codeValue TEXT, color TEXT, icon TEXT)"
|
"CREATE TABLE IF NOT EXISTS cards (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, codeType TEXT, codeValue TEXT, color TEXT, icon TEXT, position DEFAULT 0)"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,6 +44,32 @@ class DbHelper(val context: Context, val factory: SQLiteDatabase.CursorFactory?)
|
|||||||
if (oldVersion < 6) {
|
if (oldVersion < 6) {
|
||||||
db!!.execSQL("ALTER TABLE cards ADD COLUMN icon TEXT")
|
db!!.execSQL("ALTER TABLE cards ADD COLUMN icon TEXT")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (oldVersion < 7) {
|
||||||
|
db!!.execSQL("ALTER TABLE cards ADD COLUMN position INTEGER DEFAULT 0")
|
||||||
|
db.execSQL("UPDATE cards SET position = id")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readCard(cursor: Cursor): CardInfo {
|
||||||
|
return CardInfo(
|
||||||
|
id = cursor.getInt(cursor.getColumnIndexOrThrow("id")),
|
||||||
|
name = cursor.getString(cursor.getColumnIndexOrThrow("name")),
|
||||||
|
codeType = BarcodeFormat.valueOf(cursor.getString(cursor.getColumnIndexOrThrow("codeType"))),
|
||||||
|
codeValue = cursor.getString(cursor.getColumnIndexOrThrow("codeValue")),
|
||||||
|
color = cursor.getString(cursor.getColumnIndexOrThrow("color")),
|
||||||
|
icon = cursor.getStringOrNull(cursor.getColumnIndexOrThrow("icon")),
|
||||||
|
position = cursor.getInt(cursor.getColumnIndexOrThrow("position"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun nextPosition(db: SQLiteDatabase): Int {
|
||||||
|
val cursor = db.rawQuery("SELECT COALESCE(MAX(position), -1) + 1 FROM cards", null)
|
||||||
|
cursor.moveToFirst()
|
||||||
|
val next = cursor.getInt(0)
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
|
return next
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addCard(name: String, codeValue: String, codeType: BarcodeFormat, color: String?, icon: String?) {
|
fun addCard(name: String, codeValue: String, codeType: BarcodeFormat, color: String?, icon: String?) {
|
||||||
@@ -54,6 +82,7 @@ class DbHelper(val context: Context, val factory: SQLiteDatabase.CursorFactory?)
|
|||||||
values.put("codeType", codeType.name)
|
values.put("codeType", codeType.name)
|
||||||
values.put("color", color)
|
values.put("color", color)
|
||||||
values.put("icon", icon)
|
values.put("icon", icon)
|
||||||
|
values.put("position", nextPosition(db))
|
||||||
|
|
||||||
db.insert("cards", null, values)
|
db.insert("cards", null, values)
|
||||||
|
|
||||||
@@ -79,18 +108,11 @@ class DbHelper(val context: Context, val factory: SQLiteDatabase.CursorFactory?)
|
|||||||
fun getCards(): List<CardInfo> {
|
fun getCards(): List<CardInfo> {
|
||||||
val db = this.readableDatabase
|
val db = this.readableDatabase
|
||||||
|
|
||||||
val cursor = db.rawQuery("SELECT * FROM cards ORDER BY id ASC", null)
|
val cursor = db.rawQuery("SELECT * FROM cards ORDER BY position ASC", null)
|
||||||
val cards = mutableListOf<CardInfo>()
|
val cards = mutableListOf<CardInfo>()
|
||||||
|
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
cards.add(CardInfo(
|
cards.add(readCard(cursor))
|
||||||
id = cursor.getInt(cursor.getColumnIndexOrThrow("id")),
|
|
||||||
name = cursor.getString(cursor.getColumnIndexOrThrow("name")),
|
|
||||||
codeType = BarcodeFormat.valueOf(cursor.getString(cursor.getColumnIndexOrThrow("codeType"))),
|
|
||||||
codeValue = cursor.getString(cursor.getColumnIndexOrThrow("codeValue")),
|
|
||||||
color = cursor.getString(cursor.getColumnIndexOrThrow("color")),
|
|
||||||
icon = cursor.getStringOrNull(cursor.getColumnIndexOrThrow("icon"))
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cursor.close()
|
cursor.close()
|
||||||
@@ -111,14 +133,7 @@ class DbHelper(val context: Context, val factory: SQLiteDatabase.CursorFactory?)
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
val cardInfo = CardInfo(
|
val cardInfo = readCard(cursor)
|
||||||
id = cursor.getInt(cursor.getColumnIndexOrThrow("id")),
|
|
||||||
name = cursor.getString(cursor.getColumnIndexOrThrow("name")),
|
|
||||||
codeType = BarcodeFormat.valueOf(cursor.getString(cursor.getColumnIndexOrThrow("codeType"))),
|
|
||||||
codeValue = cursor.getString(cursor.getColumnIndexOrThrow("codeValue")),
|
|
||||||
color = cursor.getString(cursor.getColumnIndexOrThrow("color")),
|
|
||||||
icon = cursor.getStringOrNull(cursor.getColumnIndexOrThrow("icon"))
|
|
||||||
)
|
|
||||||
|
|
||||||
cursor.close()
|
cursor.close()
|
||||||
db.close()
|
db.close()
|
||||||
@@ -143,6 +158,8 @@ class DbHelper(val context: Context, val factory: SQLiteDatabase.CursorFactory?)
|
|||||||
fun addCards(cards: List<CardInfo>) {
|
fun addCards(cards: List<CardInfo>) {
|
||||||
val db = this.writableDatabase
|
val db = this.writableDatabase
|
||||||
|
|
||||||
|
db.use { db ->
|
||||||
|
db.transaction {
|
||||||
for (card in cards) {
|
for (card in cards) {
|
||||||
val values = ContentValues()
|
val values = ContentValues()
|
||||||
|
|
||||||
@@ -152,10 +169,27 @@ class DbHelper(val context: Context, val factory: SQLiteDatabase.CursorFactory?)
|
|||||||
values.put("codeType", card.codeType.name)
|
values.put("codeType", card.codeType.name)
|
||||||
values.put("color", card.color)
|
values.put("color", card.color)
|
||||||
values.put("icon", card.icon)
|
values.put("icon", card.icon)
|
||||||
|
values.put("position", nextPosition(db))
|
||||||
|
|
||||||
db.insert("cards", null, values)
|
db.insert("cards", null, values)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
db.close()
|
fun updatePositions(orderedIds: List<Int>) {
|
||||||
|
val db = this.writableDatabase
|
||||||
|
|
||||||
|
db.use { db ->
|
||||||
|
db.transaction {
|
||||||
|
orderedIds.forEachIndexed { index, id ->
|
||||||
|
val values = ContentValues()
|
||||||
|
|
||||||
|
values.put("position", index)
|
||||||
|
|
||||||
|
db.update("cards", values, "id = ?", arrayOf(id.toString()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.core.graphics.toColorInt
|
||||||
import com.google.zxing.BarcodeFormat
|
import com.google.zxing.BarcodeFormat
|
||||||
import com.journeyapps.barcodescanner.ScanContract
|
import com.journeyapps.barcodescanner.ScanContract
|
||||||
import com.journeyapps.barcodescanner.ScanOptions
|
import com.journeyapps.barcodescanner.ScanOptions
|
||||||
@@ -96,6 +97,11 @@ fun CardEditable(
|
|||||||
|
|
||||||
val fieldModifier = Modifier.fillMaxWidth()
|
val fieldModifier = Modifier.fillMaxWidth()
|
||||||
|
|
||||||
|
val cardColor = if (color == null)
|
||||||
|
MaterialTheme.colorScheme.primary
|
||||||
|
else
|
||||||
|
Color(color.toColorInt())
|
||||||
|
|
||||||
val scanLauncher = rememberLauncherForActivityResult(
|
val scanLauncher = rememberLauncherForActivityResult(
|
||||||
contract = ScanContract()
|
contract = ScanContract()
|
||||||
) { result ->
|
) { result ->
|
||||||
@@ -267,7 +273,7 @@ fun CardEditable(
|
|||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
CardIcon(if (icon == null) null else base64ToImageBitmap(icon), MaterialTheme.colorScheme.primary)
|
CardIcon(if (icon == null) null else base64ToImageBitmap(icon), cardColor)
|
||||||
|
|
||||||
Text(stringResource(R.string.do_select_icon))
|
Text(stringResource(R.string.do_select_icon))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import androidx.compose.material3.Icon
|
|||||||
import androidx.compose.material.icons.outlined.Add
|
import androidx.compose.material.icons.outlined.Add
|
||||||
import androidx.compose.material.icons.outlined.QrCode
|
import androidx.compose.material.icons.outlined.QrCode
|
||||||
import androidx.compose.material.icons.outlined.DensityMedium
|
import androidx.compose.material.icons.outlined.DensityMedium
|
||||||
|
import androidx.compose.material.icons.outlined.DragHandle
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.ExtendedFloatingActionButton
|
import androidx.compose.material3.ExtendedFloatingActionButton
|
||||||
import androidx.compose.material3.FloatingActionButton
|
import androidx.compose.material3.FloatingActionButton
|
||||||
@@ -185,7 +186,7 @@ fun TopBar(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CardButton(cardInfo: CardInfo, onDeleteCard: (Int) -> Unit) {
|
fun CardButton(cardInfo: CardInfo, onDeleteCard: (Int) -> Unit, reorderIcon: (@Composable () -> Unit)? = null) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
var menuExpanded by remember { mutableStateOf(false) }
|
var menuExpanded by remember { mutableStateOf(false) }
|
||||||
@@ -222,6 +223,11 @@ fun CardButton(cardInfo: CardInfo, onDeleteCard: (Int) -> Unit) {
|
|||||||
.background(Color.Transparent)
|
.background(Color.Transparent)
|
||||||
.padding(horizontal = 8.dp),
|
.padding(horizontal = 8.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
CardIcon(
|
CardIcon(
|
||||||
@@ -237,6 +243,11 @@ fun CardButton(cardInfo: CardInfo, onDeleteCard: (Int) -> Unit) {
|
|||||||
textAlign = TextAlign.Start
|
textAlign = TextAlign.Start
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (reorderIcon != null) {
|
||||||
|
reorderIcon()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DropdownMenu(
|
DropdownMenu(
|
||||||
|
|||||||
@@ -9,23 +9,26 @@ import androidx.compose.animation.AnimatedVisibility
|
|||||||
import androidx.compose.animation.expandVertically
|
import androidx.compose.animation.expandVertically
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.animation.scaleIn
|
|
||||||
import androidx.compose.animation.scaleOut
|
|
||||||
import androidx.compose.animation.shrinkVertically
|
import androidx.compose.animation.shrinkVertically
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.Add
|
import androidx.compose.material.icons.outlined.Add
|
||||||
import androidx.compose.material.icons.outlined.Sync
|
import androidx.compose.material.icons.outlined.Sync
|
||||||
import androidx.compose.material.icons.outlined.Backup
|
import androidx.compose.material.icons.outlined.Backup
|
||||||
import androidx.compose.material.icons.outlined.Code
|
import androidx.compose.material.icons.outlined.Code
|
||||||
|
import androidx.compose.material.icons.outlined.DragHandle
|
||||||
import androidx.compose.material.icons.outlined.Search
|
import androidx.compose.material.icons.outlined.Search
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@@ -37,12 +40,15 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.runtime.mutableStateListOf
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import ru.omni_devel.cards.ui.theme.OmniCardsTheme
|
import ru.omni_devel.cards.ui.theme.OmniCardsTheme
|
||||||
|
import sh.calvin.reorderable.ReorderableItem
|
||||||
|
import sh.calvin.reorderable.rememberReorderableLazyListState
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
private lateinit var db: DbHelper
|
private lateinit var db: DbHelper
|
||||||
@@ -60,12 +66,22 @@ class MainActivity : ComponentActivity() {
|
|||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
MainPage(innerPadding, cards, onDeleteCard = { cardId ->
|
MainPage(
|
||||||
|
innerPadding,
|
||||||
|
cards,
|
||||||
|
onDeleteCard = { cardId ->
|
||||||
db.removeCard(cardId)
|
db.removeCard(cardId)
|
||||||
cards.removeIf { it.id == cardId }
|
cards.removeIf { it.id == cardId }
|
||||||
}, getDbFun = {
|
syncCardsWithWatch(this, db.getCards())
|
||||||
|
},
|
||||||
|
onReorderCards = {
|
||||||
|
db.updatePositions(it)
|
||||||
|
syncCardsWithWatch(this, db.getCards())
|
||||||
|
},
|
||||||
|
getDbFun = {
|
||||||
return@MainPage db
|
return@MainPage db
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,14 +92,17 @@ class MainActivity : ComponentActivity() {
|
|||||||
|
|
||||||
cards.clear()
|
cards.clear()
|
||||||
cards.addAll(db.getCards())
|
cards.addAll(db.getCards())
|
||||||
|
|
||||||
|
syncCardsWithWatch(this, db.getCards())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MainPage(
|
fun MainPage(
|
||||||
innerPadding: PaddingValues,
|
innerPadding: PaddingValues,
|
||||||
cards: List<CardInfo>,
|
cards: SnapshotStateList<CardInfo>,
|
||||||
onDeleteCard: (Int) -> Unit,
|
onDeleteCard: (Int) -> Unit,
|
||||||
|
onReorderCards: (List<Int>) -> Unit,
|
||||||
getDbFun: () -> DbHelper
|
getDbFun: () -> DbHelper
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
@@ -92,6 +111,19 @@ fun MainPage(
|
|||||||
var searchQuery by rememberSaveable { mutableStateOf("") }
|
var searchQuery by rememberSaveable { mutableStateOf("") }
|
||||||
var isSearchFieldShowed by rememberSaveable { mutableStateOf(false) }
|
var isSearchFieldShowed by rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val lazyListState = rememberLazyListState()
|
||||||
|
val reorderableLazyListState = rememberReorderableLazyListState(lazyListState) { from, to ->
|
||||||
|
val fromId = from.key as? Int ?: return@rememberReorderableLazyListState
|
||||||
|
val toId = to.key as? Int ?: return@rememberReorderableLazyListState
|
||||||
|
|
||||||
|
val fromIndex = cards.indexOfFirst { it.id == fromId }
|
||||||
|
val toIndex = cards.indexOfFirst { it.id == toId }
|
||||||
|
|
||||||
|
if (fromIndex != -1 && toIndex != -1) {
|
||||||
|
cards.add(toIndex, cards.removeAt(fromIndex))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
@@ -114,13 +146,14 @@ fun MainPage(
|
|||||||
NavigationPageData(stringResource(R.string.do_add_card), Icons.Outlined.Add, EditCardActivity::class.java),
|
NavigationPageData(stringResource(R.string.do_add_card), Icons.Outlined.Add, EditCardActivity::class.java),
|
||||||
NavigationPageData(stringResource(R.string.do_sync), Icons.Outlined.Sync) {
|
NavigationPageData(stringResource(R.string.do_sync), Icons.Outlined.Sync) {
|
||||||
Toast.makeText(context, context.getString(R.string.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 ->
|
|
||||||
|
val isSuccess = syncCardsWithWatch(context, getDbFun().getCards())
|
||||||
|
|
||||||
if (isSuccess) {
|
if (isSuccess) {
|
||||||
Toast.makeText(context, context.getString(R.string.sync_is_successful), Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, context.getString(R.string.sync_is_successful), Toast.LENGTH_SHORT).show()
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(context, context.getString(R.string.sync_is_fail), Toast.LENGTH_LONG).show()
|
Toast.makeText(context, context.getString(R.string.sync_is_fail), Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
NavigationPageData(stringResource(R.string.open_backup_menu), Icons.Outlined.Backup, BackupActivity::class.java),
|
NavigationPageData(stringResource(R.string.open_backup_menu), Icons.Outlined.Backup, BackupActivity::class.java),
|
||||||
NavigationPageData(stringResource(R.string.source_code), Icons.Outlined.Code) {
|
NavigationPageData(stringResource(R.string.source_code), Icons.Outlined.Code) {
|
||||||
@@ -147,14 +180,11 @@ fun MainPage(
|
|||||||
cardNameCleaned = cardNameCleaned.replace(from, to)
|
cardNameCleaned = cardNameCleaned.replace(from, to)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cardNameCleaned.contains(queryCleaned)) {
|
return@filter cardNameCleaned.contains(queryCleaned)
|
||||||
return@filter true
|
|
||||||
}
|
|
||||||
|
|
||||||
return@filter false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
|
state = lazyListState,
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
@@ -188,8 +218,40 @@ fun MainPage(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (card in searchResults) {
|
for (card in searchResults) {
|
||||||
item {
|
item(key = card.id) {
|
||||||
CardButton(card, onDeleteCard)
|
ReorderableItem(reorderableLazyListState, key = card.id) { isDragging ->
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.alpha(if (isDragging) 0.7f else 1f)
|
||||||
|
) {
|
||||||
|
var reorderIcon: (@Composable () -> Unit)? = null
|
||||||
|
|
||||||
|
if (queryCleaned.isEmpty()) {
|
||||||
|
reorderIcon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.DragHandle,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||||
|
modifier = Modifier
|
||||||
|
.draggableHandle(
|
||||||
|
onDragStopped = {
|
||||||
|
onReorderCards(cards.map { it.id })
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.padding(8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CardButton(
|
||||||
|
card,
|
||||||
|
onDeleteCard,
|
||||||
|
reorderIcon
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ import com.journeyapps.barcodescanner.BarcodeEncoder
|
|||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.compose.ui.graphics.asImageBitmap
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
fun generateCode(value: String, format: BarcodeFormat): Bitmap {
|
fun generateCode(value: String, format: BarcodeFormat): Bitmap {
|
||||||
val writer = MultiFormatWriter()
|
val writer = MultiFormatWriter()
|
||||||
@@ -33,12 +35,14 @@ fun generateCode(value: String, format: BarcodeFormat): Bitmap {
|
|||||||
return BarcodeEncoder().createBitmap(matrix)
|
return BarcodeEncoder().createBitmap(matrix)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendToWatch(context: Context, path: String, message: String, onResult: (Boolean) -> Unit) {
|
fun sendToWatch(context: Context, path: String, message: String): Boolean {
|
||||||
val nodeClient = Wearable.getNodeClient(context)
|
val nodeClient = Wearable.getNodeClient(context)
|
||||||
|
|
||||||
|
var isSuccess = false
|
||||||
|
|
||||||
nodeClient.connectedNodes.addOnSuccessListener { nodes ->
|
nodeClient.connectedNodes.addOnSuccessListener { nodes ->
|
||||||
if (nodes.isEmpty()) {
|
if (nodes.isEmpty()) {
|
||||||
onResult(false)
|
isSuccess = false
|
||||||
|
|
||||||
return@addOnSuccessListener
|
return@addOnSuccessListener
|
||||||
}
|
}
|
||||||
@@ -59,13 +63,19 @@ fun sendToWatch(context: Context, path: String, message: String, onResult: (Bool
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (completedCount == nodes.size) {
|
if (completedCount == nodes.size) {
|
||||||
onResult(isAnySuccess)
|
isSuccess = isAnySuccess
|
||||||
}
|
}
|
||||||
}.addOnFailureListener(context.mainExecutor) {
|
}.addOnFailureListener(context.mainExecutor) {
|
||||||
onResult(false)
|
isSuccess = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return isSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
fun syncCardsWithWatch(context: Context, cards: List<CardInfo>): Boolean {
|
||||||
|
return sendToWatch(context, "/updateCards", Json.encodeToString(cards))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkCardData(name: String, value: String): Int? {
|
fun checkCardData(name: String, value: String): Int? {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ android {
|
|||||||
minSdk = 30
|
minSdk = 30
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = generateVersionCode()
|
versionCode = generateVersionCode()
|
||||||
versionName = "3.2.0"
|
versionName = "3.3.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@@ -72,4 +72,5 @@ dependencies {
|
|||||||
implementation(libs.material)
|
implementation(libs.material)
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
|
||||||
implementation("androidx.compose.material:material-icons-extended")
|
implementation("androidx.compose.material:material-icons-extended")
|
||||||
|
implementation("androidx.lifecycle:lifecycle-process:2.8.7")
|
||||||
}
|
}
|
||||||
@@ -10,5 +10,6 @@ class CardInfo (
|
|||||||
val codeType: BarcodeFormat,
|
val codeType: BarcodeFormat,
|
||||||
val codeValue: String,
|
val codeValue: String,
|
||||||
val color: String?,
|
val color: String?,
|
||||||
val icon: String?
|
val icon: String?,
|
||||||
|
val position: Int
|
||||||
)
|
)
|
||||||
@@ -2,12 +2,14 @@ package ru.omni_devel.cards.presentation
|
|||||||
|
|
||||||
import android.content.ContentValues
|
import android.content.ContentValues
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.database.Cursor
|
||||||
import android.database.sqlite.SQLiteDatabase
|
import android.database.sqlite.SQLiteDatabase
|
||||||
import android.database.sqlite.SQLiteOpenHelper
|
import android.database.sqlite.SQLiteOpenHelper
|
||||||
import androidx.core.database.getStringOrNull
|
import androidx.core.database.getStringOrNull
|
||||||
|
import androidx.core.database.sqlite.transaction
|
||||||
import com.google.zxing.BarcodeFormat
|
import com.google.zxing.BarcodeFormat
|
||||||
|
|
||||||
class DbHelper(val context: Context, val factory: SQLiteDatabase.CursorFactory?) : SQLiteOpenHelper(context, "omni_cards", factory, 5) {
|
class DbHelper(val context: Context, val factory: SQLiteDatabase.CursorFactory?) : SQLiteOpenHelper(context, "omni_cards", factory, 6) {
|
||||||
override fun onCreate(db: SQLiteDatabase?) {
|
override fun onCreate(db: SQLiteDatabase?) {
|
||||||
db!!.execSQL(
|
db!!.execSQL(
|
||||||
"CREATE TABLE IF NOT EXISTS cards (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, codeType TEXT, codeValue TEXT, color TEXT, icon TEXT)"
|
"CREATE TABLE IF NOT EXISTS cards (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, codeType TEXT, codeValue TEXT, color TEXT, icon TEXT)"
|
||||||
@@ -37,6 +39,23 @@ class DbHelper(val context: Context, val factory: SQLiteDatabase.CursorFactory?)
|
|||||||
if (oldVersion < 5) {
|
if (oldVersion < 5) {
|
||||||
db!!.execSQL("ALTER TABLE cards ADD COLUMN icon TEXT")
|
db!!.execSQL("ALTER TABLE cards ADD COLUMN icon TEXT")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (oldVersion < 6) {
|
||||||
|
db!!.execSQL("ALTER TABLE cards ADD COLUMN position INTEGER DEFAULT 0")
|
||||||
|
db.execSQL("UPDATE cards SET position = id")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readCard(cursor: Cursor): CardInfo {
|
||||||
|
return CardInfo(
|
||||||
|
id = cursor.getInt(cursor.getColumnIndexOrThrow("id")),
|
||||||
|
name = cursor.getString(cursor.getColumnIndexOrThrow("name")),
|
||||||
|
codeType = BarcodeFormat.valueOf(cursor.getString(cursor.getColumnIndexOrThrow("codeType"))),
|
||||||
|
codeValue = cursor.getString(cursor.getColumnIndexOrThrow("codeValue")),
|
||||||
|
color = cursor.getString(cursor.getColumnIndexOrThrow("color")),
|
||||||
|
icon = cursor.getStringOrNull(cursor.getColumnIndexOrThrow("icon")),
|
||||||
|
position = cursor.getInt(cursor.getColumnIndexOrThrow("position"))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearDatabase() {
|
fun clearDatabase() {
|
||||||
@@ -50,6 +69,8 @@ class DbHelper(val context: Context, val factory: SQLiteDatabase.CursorFactory?)
|
|||||||
fun addCards(cards: List<CardInfo>) {
|
fun addCards(cards: List<CardInfo>) {
|
||||||
val db = this.writableDatabase
|
val db = this.writableDatabase
|
||||||
|
|
||||||
|
db.use { db ->
|
||||||
|
db.transaction {
|
||||||
for (card in cards) {
|
for (card in cards) {
|
||||||
val values = ContentValues()
|
val values = ContentValues()
|
||||||
|
|
||||||
@@ -59,28 +80,22 @@ class DbHelper(val context: Context, val factory: SQLiteDatabase.CursorFactory?)
|
|||||||
values.put("codeType", card.codeType.name)
|
values.put("codeType", card.codeType.name)
|
||||||
values.put("color", card.color)
|
values.put("color", card.color)
|
||||||
values.put("icon", card.icon)
|
values.put("icon", card.icon)
|
||||||
|
values.put("position", card.position)
|
||||||
|
|
||||||
db.insert("cards", null, values)
|
db.insert("cards", null, values)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
db.close()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCards(): List<CardInfo> {
|
fun getCards(): List<CardInfo> {
|
||||||
val db = this.readableDatabase
|
val db = this.readableDatabase
|
||||||
|
|
||||||
val cursor = db.rawQuery("SELECT * FROM cards", null)
|
val cursor = db.rawQuery("SELECT * FROM cards ORDER BY position", null)
|
||||||
val cards = mutableListOf<CardInfo>()
|
val cards = mutableListOf<CardInfo>()
|
||||||
|
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
cards.add(CardInfo(
|
cards.add(readCard(cursor))
|
||||||
id = cursor.getInt(cursor.getColumnIndexOrThrow("id")),
|
|
||||||
name = cursor.getString(cursor.getColumnIndexOrThrow("name")),
|
|
||||||
codeType = BarcodeFormat.valueOf(cursor.getString(cursor.getColumnIndexOrThrow("codeType"))),
|
|
||||||
codeValue = cursor.getString(cursor.getColumnIndexOrThrow("codeValue")),
|
|
||||||
color = cursor.getString(cursor.getColumnIndexOrThrow("color")),
|
|
||||||
icon = cursor.getStringOrNull(cursor.getColumnIndexOrThrow("icon"))
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cursor.close()
|
cursor.close()
|
||||||
@@ -101,14 +116,7 @@ class DbHelper(val context: Context, val factory: SQLiteDatabase.CursorFactory?)
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
val cardInfo = CardInfo(
|
val cardInfo = readCard(cursor)
|
||||||
id = cursor.getInt(cursor.getColumnIndexOrThrow("id")),
|
|
||||||
name = cursor.getString(cursor.getColumnIndexOrThrow("name")),
|
|
||||||
codeType = BarcodeFormat.valueOf(cursor.getString(cursor.getColumnIndexOrThrow("codeType"))),
|
|
||||||
codeValue = cursor.getString(cursor.getColumnIndexOrThrow("codeValue")),
|
|
||||||
color = cursor.getString(cursor.getColumnIndexOrThrow("color")),
|
|
||||||
icon = cursor.getStringOrNull(cursor.getColumnIndexOrThrow("icon"))
|
|
||||||
)
|
|
||||||
|
|
||||||
cursor.close()
|
cursor.close()
|
||||||
db.close()
|
db.close()
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.BoxWithConstraints
|
|||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
@@ -22,16 +23,25 @@ import ru.omni_devel.cards.R
|
|||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
private lateinit var db: DbHelper
|
private lateinit var db: DbHelper
|
||||||
|
private val cardsState = mutableStateOf<List<CardInfo>>(emptyList())
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
db = DbHelper(this, null)
|
db = DbHelper(this, null)
|
||||||
|
|
||||||
|
cardsState.value = db.getCards()
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
App(db.getCards())
|
App(cardsState.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
|
||||||
|
cardsState.value = db.getCards()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("UnusedBoxWithConstraintsScope")
|
@SuppressLint("UnusedBoxWithConstraintsScope")
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import com.google.android.gms.wearable.MessageEvent
|
|||||||
import com.google.android.gms.wearable.WearableListenerService
|
import com.google.android.gms.wearable.WearableListenerService
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import ru.omni_devel.cards.R
|
import ru.omni_devel.cards.R
|
||||||
|
import androidx.lifecycle.ProcessLifecycleOwner
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
|
||||||
class MessagesReceiver : WearableListenerService() {
|
class MessagesReceiver : WearableListenerService() {
|
||||||
private lateinit var db: DbHelper
|
private lateinit var db: DbHelper
|
||||||
@@ -24,12 +26,26 @@ class MessagesReceiver : WearableListenerService() {
|
|||||||
val json = String(event.data, Charsets.UTF_8)
|
val json = String(event.data, Charsets.UTF_8)
|
||||||
|
|
||||||
val cards = Json.decodeFromString<List<CardInfo>>(json)
|
val cards = Json.decodeFromString<List<CardInfo>>(json)
|
||||||
|
val oldCards = db.getCards()
|
||||||
|
|
||||||
|
if (cards == oldCards) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
db.clearDatabase()
|
db.clearDatabase()
|
||||||
db.addCards(cards)
|
db.addCards(cards)
|
||||||
|
|
||||||
|
val isAppInForeground = ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(
|
||||||
|
Lifecycle.State.STARTED
|
||||||
|
)
|
||||||
|
|
||||||
|
if (isAppInForeground) {
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
Toast.makeText(this, this.getString(R.string.sync_is_successful), 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 {
|
val intent = Intent(this, MainActivity::class.java).apply {
|
||||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||||
@@ -40,3 +56,4 @@ class MessagesReceiver : WearableListenerService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user