diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 04ab44d..0ab5709 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,7 +9,7 @@ playServicesWearable = "18.0.0" material = "1.14.0" activityKtx = "1.13.0" constraintlayout = "2.2.1" -kotlin = "2.2.10" +kotlin = "2.4.0" composeBom = "2024.09.00" composeMaterial3 = "1.5.6" composeFoundation = "1.5.6" @@ -22,6 +22,7 @@ zxingAndroid = "4.3.0" lifecycleRuntimeKtx = "2.6.1" navigationFragmentKtx = "2.6.0" navigationUiKtx = "2.6.0" +colorpicker = "1.2.0" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -52,6 +53,7 @@ androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecyc androidx-material3 = { group = "androidx.compose.material3", name = "material3" } androidx-navigation-fragment-ktx = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "navigationFragmentKtx" } androidx-navigation-ui-ktx = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navigationUiKtx" } +compose-colorpicker = { module = "com.github.skydoves:colorpicker-compose", version.ref = "colorpicker" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } diff --git a/mobile/build.gradle.kts b/mobile/build.gradle.kts index 2a1a4b8..7db6b6a 100644 --- a/mobile/build.gradle.kts +++ b/mobile/build.gradle.kts @@ -1,7 +1,6 @@ plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.compose) - kotlin("plugin.serialization") version "2.2.20" } android { @@ -12,8 +11,8 @@ android { applicationId = "ru.omni_devel.cards" minSdk = 30 targetSdk = 36 - versionCode = 30 // 0 at end for mobile - versionName = "2.0" + versionCode = 40 // 0 at end for mobile + versionName = "2.1" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } @@ -63,4 +62,5 @@ dependencies { implementation(libs.zxing.android) implementation("com.google.android.gms:play-services-wearable:18.1.0") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0") + implementation(libs.compose.colorpicker) } \ No newline at end of file diff --git a/mobile/src/main/java/ru/omni_devel/cards/CardInfo.kt b/mobile/src/main/java/ru/omni_devel/cards/CardInfo.kt index b6ad1ec..eebe6d3 100644 --- a/mobile/src/main/java/ru/omni_devel/cards/CardInfo.kt +++ b/mobile/src/main/java/ru/omni_devel/cards/CardInfo.kt @@ -14,4 +14,5 @@ class CardInfo ( val name: String, val codeType: BarcodeFormat, val codeValue: String, -) + val color: String?, +) {} diff --git a/mobile/src/main/java/ru/omni_devel/cards/DbHelper.kt b/mobile/src/main/java/ru/omni_devel/cards/DbHelper.kt index 8feb765..1eec773 100644 --- a/mobile/src/main/java/ru/omni_devel/cards/DbHelper.kt +++ b/mobile/src/main/java/ru/omni_devel/cards/DbHelper.kt @@ -6,25 +6,28 @@ import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteOpenHelper import com.google.zxing.BarcodeFormat -class DbHelper(val context: Context, val factory: SQLiteDatabase.CursorFactory?) : SQLiteOpenHelper(context, "omni_cards", factory, 1) { +class DbHelper(val context: Context, val factory: SQLiteDatabase.CursorFactory?) : SQLiteOpenHelper(context, "omni_cards", factory, 2) { override fun onCreate(db: SQLiteDatabase?) { - db!!.execSQL("CREATE TABLE IF NOT EXISTS cards (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, codeType TEXT, codeValue TEXT)") + db!!.execSQL("CREATE TABLE IF NOT EXISTS cards (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, codeType TEXT, codeValue TEXT, color TEXT)") } override fun onUpgrade( db: SQLiteDatabase?, - p1: Int, + oldVersion: Int, p2: Int ) { - db!!.execSQL("DROP TABLE IF EXISTS cards") + if (oldVersion < 2) { + db!!.execSQL("ALTER TABLE cards ADD COLUMN color TEXT") + } } - fun addCard(name: String, codeValue: String, codeType: BarcodeFormat) { + fun addCard(name: String, codeValue: String, codeType: BarcodeFormat, color: String?) { val values = ContentValues() values.put("name", name) values.put("codeValue", codeValue) values.put("codeType", codeType.name) + values.put("color", color) val db = this.writableDatabase @@ -33,12 +36,13 @@ class DbHelper(val context: Context, val factory: SQLiteDatabase.CursorFactory?) db.close() } - fun editCard(id: Int, name: String, codeValue: String, codeType: BarcodeFormat) { + fun editCard(id: Int, name: String, codeValue: String, codeType: BarcodeFormat, color: String?) { val values = ContentValues() values.put("name", name) values.put("codeValue", codeValue) values.put("codeType", codeType.name) + values.put("color", color) val db = this.writableDatabase @@ -58,7 +62,8 @@ class DbHelper(val context: Context, val factory: SQLiteDatabase.CursorFactory?) 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")) + codeValue = cursor.getString(cursor.getColumnIndexOrThrow("codeValue")), + color = cursor.getString(cursor.getColumnIndexOrThrow("color")) )) } @@ -84,7 +89,8 @@ class DbHelper(val context: Context, val factory: SQLiteDatabase.CursorFactory?) 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")) + codeValue = cursor.getString(cursor.getColumnIndexOrThrow("codeValue")), + color = cursor.getString(cursor.getColumnIndexOrThrow("color")) ) cursor.close() diff --git a/mobile/src/main/java/ru/omni_devel/cards/EditCardActivity.kt b/mobile/src/main/java/ru/omni_devel/cards/EditCardActivity.kt index d8da1b7..be2918c 100644 --- a/mobile/src/main/java/ru/omni_devel/cards/EditCardActivity.kt +++ b/mobile/src/main/java/ru/omni_devel/cards/EditCardActivity.kt @@ -4,6 +4,7 @@ import android.app.Activity 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 @@ -15,12 +16,17 @@ 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.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +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 import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -29,8 +35,13 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember 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.unit.dp +import androidx.core.graphics.toColorInt +import com.github.skydoves.colorpicker.compose.ColorEnvelope +import com.github.skydoves.colorpicker.compose.HsvColorPicker +import com.github.skydoves.colorpicker.compose.rememberColorPickerController import com.google.zxing.BarcodeFormat import com.journeyapps.barcodescanner.ScanContract import com.journeyapps.barcodescanner.ScanOptions @@ -65,11 +76,17 @@ fun CardEditable( cardName: String, onCardNameChange: (String) -> Unit, codeValue: String, - oncodeValueChange: (String) -> Unit, + onCodeValueChange: (String) -> Unit, codeType: BarcodeFormat, - onCodeTypeChange: (BarcodeFormat) -> Unit + onCodeTypeChange: (BarcodeFormat) -> Unit, + 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 colorPickerController = rememberColorPickerController() val fieldModifier = Modifier.fillMaxWidth() @@ -77,7 +94,7 @@ fun CardEditable( contract = ScanContract() ) { result -> if (result.contents != null) { - oncodeValueChange(result.contents) + onCodeValueChange(result.contents) result.formatName?.let { formatName -> try { @@ -98,7 +115,7 @@ fun CardEditable( ) { OutlinedTextField( value = codeValue, - onValueChange = oncodeValueChange, + onValueChange = onCodeValueChange, modifier = Modifier.weight(1f), label = { Text("Value") } ) @@ -151,6 +168,44 @@ fun CardEditable( } } } + + Row( + modifier = fieldModifier, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Button( + enabled = isColorEnabled, + onClick = { + isColorPickerExpanded = !isColorPickerExpanded + } + ) { + Text("Choose color") + } + Switch( + checked = isColorEnabled, + onCheckedChange = { + isColorEnabled = it + + if (!isColorEnabled) { + onColorChange(null) + } + } + ) + } + + if (isColorPickerExpanded && isColorEnabled) { + HsvColorPicker( + modifier = Modifier + .fillMaxWidth() + .height(450.dp) + .padding(10.dp), + initialColor = color?.let { Color(it.toColorInt()) }, + controller = colorPickerController, + onColorChanged = { colorEnvelope: ColorEnvelope -> + onColorChange("#${colorEnvelope.hexCode.substring(2)}") + } + ) + } } @Composable @@ -158,23 +213,27 @@ 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(null) } - val context = LocalContext.current as Activity + val context = LocalActivity.current Page( "New card", innerPadding ) { Column( + modifier = Modifier.verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.spacedBy(8.dp) ) { CardEditable( cardName = cardName, onCardNameChange = { cardName = it }, codeValue = codeValue, - oncodeValueChange = { codeValue = it }, + onCodeValueChange = { codeValue = it }, codeType = codeType, - onCodeTypeChange = { codeType = it } + onCodeTypeChange = { codeType = it }, + color = color, + onColorChange = { color = it } ) Button( @@ -183,8 +242,8 @@ fun AddCard(innerPadding: PaddingValues, getDbFun: () -> DbHelper) { val err = checkCardData(cardName, codeValue) if (err == null) { - getDbFun().addCard(cardName, codeValue, codeType) - context.finish() + getDbFun().addCard(cardName, codeValue, codeType, color) + context!!.finish() } else { Toast.makeText(context, err, Toast.LENGTH_SHORT).show() } @@ -201,23 +260,27 @@ fun EditCard(cardInfo: CardInfo, innerPadding: PaddingValues, getDbFun: () -> Db 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) } - val context = LocalContext.current as Activity + val context = LocalActivity.current Page( "Editing card id${cardInfo.id}", innerPadding ) { Column( + modifier = Modifier.verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.spacedBy(8.dp) ) { CardEditable( cardName = cardName, onCardNameChange = { cardName = it }, codeValue = codeValue, - oncodeValueChange = { codeValue = it }, + onCodeValueChange = { codeValue = it }, codeType = codeType, - onCodeTypeChange = { codeType = it } + onCodeTypeChange = { codeType = it }, + color = color, + onColorChange = { color = it } ) Button( @@ -226,8 +289,8 @@ fun EditCard(cardInfo: CardInfo, innerPadding: PaddingValues, getDbFun: () -> Db val err = checkCardData(cardName, codeValue) if (err == null) { - getDbFun().editCard(cardInfo.id, cardName, codeValue, codeType) - context.finish() + getDbFun().editCard(cardInfo.id, cardName, codeValue, codeType, color) + context!!.finish() } else { Toast.makeText(context, err, Toast.LENGTH_SHORT).show() } diff --git a/mobile/src/main/java/ru/omni_devel/cards/Elements.kt b/mobile/src/main/java/ru/omni_devel/cards/Elements.kt index 1dfbf40..7be999f 100644 --- a/mobile/src/main/java/ru/omni_devel/cards/Elements.kt +++ b/mobile/src/main/java/ru/omni_devel/cards/Elements.kt @@ -2,14 +2,18 @@ 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.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +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.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.DropdownMenu @@ -23,11 +27,13 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier 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.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.core.graphics.toColorInt @Composable fun Page(name: String, innerPadding: PaddingValues, content: @Composable () -> Unit) { @@ -58,7 +64,7 @@ fun TopBar(text: String) { } @Composable -fun CardButton(cardInfo: CardInfo, onDeleteCard: (Int) -> Unit, getDbFun: () -> DbHelper) { +fun CardButton(cardInfo: CardInfo, onDeleteCard: (Int) -> Unit) { val context = LocalContext.current var menuExpanded by remember { mutableStateOf(false) } @@ -82,15 +88,31 @@ fun CardButton(cardInfo: CardInfo, onDeleteCard: (Int) -> Unit, getDbFun: () -> 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 - ) + Row( + modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Min), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Box( + modifier = Modifier + .fillMaxHeight() + .width(32.dp) + .background( + if (cardInfo.color == null) MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.625f) + else Color(cardInfo.color.toColorInt()).copy(alpha = 0.625f) + ) + ) + + Text( + cardInfo.name, + fontSize = 16.sp, + color = MaterialTheme.colorScheme.onPrimaryContainer, + modifier = Modifier + .padding(16.dp) + .fillMaxWidth() + .weight(1f), + textAlign = TextAlign.Start + ) + } } DropdownMenu( diff --git a/mobile/src/main/java/ru/omni_devel/cards/MainActivity.kt b/mobile/src/main/java/ru/omni_devel/cards/MainActivity.kt index 8a24ea4..3ce73be 100644 --- a/mobile/src/main/java/ru/omni_devel/cards/MainActivity.kt +++ b/mobile/src/main/java/ru/omni_devel/cards/MainActivity.kt @@ -100,7 +100,7 @@ fun MainPage( Text("Add new card to get started") } else { for (card in cards) { - CardButton(card, onDeleteCard, getDbFun) + CardButton(card, onDeleteCard) } } } diff --git a/wear/build.gradle.kts b/wear/build.gradle.kts index 5a841cd..cc5dd52 100644 --- a/wear/build.gradle.kts +++ b/wear/build.gradle.kts @@ -1,7 +1,6 @@ plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.compose) - kotlin("plugin.serialization") version "2.2.20" } android { @@ -14,7 +13,6 @@ android { targetSdk = 36 versionCode = 41 // 1 at end for WearOS versionName = "1.1" - } buildTypes { diff --git a/wear/src/main/java/ru/omni_devel/cards/presentation/MainActivity.kt b/wear/src/main/java/ru/omni_devel/cards/presentation/MainActivity.kt index 150dd87..7a99867 100644 --- a/wear/src/main/java/ru/omni_devel/cards/presentation/MainActivity.kt +++ b/wear/src/main/java/ru/omni_devel/cards/presentation/MainActivity.kt @@ -8,11 +8,9 @@ import androidx.activity.compose.setContent import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints -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.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -22,6 +20,7 @@ import androidx.compose.ui.unit.dp import androidx.wear.compose.foundation.lazy.TransformingLazyColumn import androidx.wear.compose.material3.Button import androidx.wear.compose.material3.Text +import com.google.zxing.BarcodeFormat class MainActivity : ComponentActivity() { private lateinit var db: DbHelper @@ -32,7 +31,15 @@ class MainActivity : ComponentActivity() { db = DbHelper(this, null) setContent { - App(db.getCards()) +// App(db.getCards()) + App(listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10).map { + CardInfo( + id = it, + name = "Card #$it", + codeType = BarcodeFormat.QR_CODE, + codeValue = "000$it" + ) + }) } } }