diff --git a/mobile/build.gradle.kts b/mobile/build.gradle.kts
index 757ae51..5ed9011 100644
--- a/mobile/build.gradle.kts
+++ b/mobile/build.gradle.kts
@@ -77,7 +77,7 @@ dependencies {
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)
- implementation("org.burnoutcrew.composereorderable:reorderable:0.9.6")
implementation("io.ktor:ktor-client-core:3.5.0")
implementation("io.ktor:ktor-client-cio:3.5.0")
+ implementation("androidx.compose.material:material-icons-extended")
}
\ No newline at end of file
diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml
index 89ddf59..b747009 100644
--- a/mobile/src/main/AndroidManifest.xml
+++ b/mobile/src/main/AndroidManifest.xml
@@ -10,6 +10,11 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.OmniCards">
+
,
+ val iconId: String? = null
+) {
+ companion object {
+ fun parseIconsDataFromJson(json: String): List {
+ val array = JSONArray(json)
+ val icons = mutableListOf()
+
+ for (i in 0 until array.length()) {
+ val obj = array.getJSONObject(i)
+
+ icons.add(CardIcon(
+ names = jsonObjectToMap(obj.getJSONObject("names")),
+ iconId = if (obj.has("iconId")) obj.getString("iconId") else null
+ ))
+ }
+
+ return icons
+ }
+ }
+}
\ 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 c321a96..5889def 100644
--- a/mobile/src/main/java/ru/omni_devel/cards/CardInfo.kt
+++ b/mobile/src/main/java/ru/omni_devel/cards/CardInfo.kt
@@ -14,5 +14,6 @@ class CardInfo (
val name: String,
val codeType: BarcodeFormat,
val codeValue: String,
- val color: String?
+ val color: String?,
+ val icon: String?
) {}
diff --git a/mobile/src/main/java/ru/omni_devel/cards/Config.kt b/mobile/src/main/java/ru/omni_devel/cards/Config.kt
index cd67f75..fd27d81 100644
--- a/mobile/src/main/java/ru/omni_devel/cards/Config.kt
+++ b/mobile/src/main/java/ru/omni_devel/cards/Config.kt
@@ -1,5 +1,15 @@
package ru.omni_devel.cards
-const val TEMPLATES_URL = "https://gitea.omni-devel.ru/omni/OmniCardsRepo/raw/branch/main/templates.json"
+const val REPO_BASE_URL = "https://gitea.omni-devel.ru/omni/OmniCardsRepo/raw/branch/main"
-val SPACE_SYMBOLS = listOf(" ", "-", "_")
+const val TEMPLATES_REPO_FILE = "templates.json"
+
+const val ICONS_REPO_FILE = "icons.json"
+const val ICONS_REPO_FOLDER = "icons"
+
+val SEARCH_REPLACE_SYMBOLS = mapOf(
+ " " to "",
+ "-" to "",
+ "_" to "",
+ "ё" to "е"
+)
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 7d7a2eb..657c0f0 100644
--- a/mobile/src/main/java/ru/omni_devel/cards/DbHelper.kt
+++ b/mobile/src/main/java/ru/omni_devel/cards/DbHelper.kt
@@ -4,11 +4,14 @@ import android.content.ContentValues
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
+import androidx.core.database.getStringOrNull
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?) {
- db!!.execSQL("CREATE TABLE IF NOT EXISTS cards (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, codeType TEXT, codeValue TEXT, color TEXT)")
+ db!!.execSQL(
+ "CREATE TABLE IF NOT EXISTS cards (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, codeType TEXT, codeValue TEXT, color TEXT, icon TEXT)"
+ )
}
override fun onUpgrade(
@@ -35,9 +38,13 @@ class DbHelper(val context: Context, val factory: SQLiteDatabase.CursorFactory?)
db.execSQL("ALTER TABLE cards DROP COLUMN monochrome")
db.execSQL("ALTER TABLE cards DROP COLUMN position")
}
+
+ if (oldVersion < 6) {
+ db!!.execSQL("ALTER TABLE cards ADD COLUMN icon TEXT")
+ }
}
- fun addCard(name: String, codeValue: String, codeType: BarcodeFormat, color: String?) {
+ fun addCard(name: String, codeValue: String, codeType: BarcodeFormat, color: String?, icon: String?) {
val db = this.writableDatabase
val values = ContentValues()
@@ -46,19 +53,21 @@ class DbHelper(val context: Context, val factory: SQLiteDatabase.CursorFactory?)
values.put("codeValue", codeValue)
values.put("codeType", codeType.name)
values.put("color", color)
+ values.put("icon", icon)
db.insert("cards", null, values)
db.close()
}
- fun editCard(id: Int, name: String, codeValue: String, codeType: BarcodeFormat, color: String?) {
+ fun editCard(id: Int, name: String, codeValue: String, codeType: BarcodeFormat, color: String?, icon: String?) {
val values = ContentValues()
values.put("name", name)
values.put("codeValue", codeValue)
values.put("codeType", codeType.name)
values.put("color", color)
+ values.put("icon", icon)
val db = this.writableDatabase
@@ -79,7 +88,8 @@ class DbHelper(val context: Context, val factory: SQLiteDatabase.CursorFactory?)
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"))
+ color = cursor.getString(cursor.getColumnIndexOrThrow("color")),
+ icon = cursor.getStringOrNull(cursor.getColumnIndexOrThrow("icon"))
))
}
@@ -106,7 +116,8 @@ class DbHelper(val context: Context, val factory: SQLiteDatabase.CursorFactory?)
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"))
+ color = cursor.getString(cursor.getColumnIndexOrThrow("color")),
+ icon = cursor.getStringOrNull(cursor.getColumnIndexOrThrow("icon"))
)
cursor.close()
@@ -140,10 +151,11 @@ class DbHelper(val context: Context, val factory: SQLiteDatabase.CursorFactory?)
values.put("codeValue", card.codeValue)
values.put("codeType", card.codeType.name)
values.put("color", card.color)
+ values.put("icon", card.icon)
db.insert("cards", null, values)
}
db.close()
}
-}
\ No newline at end of file
+}
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 416647d..d6f15d7 100644
--- a/mobile/src/main/java/ru/omni_devel/cards/EditCardActivity.kt
+++ b/mobile/src/main/java/ru/omni_devel/cards/EditCardActivity.kt
@@ -10,6 +10,7 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -18,10 +19,13 @@ 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.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Switch
@@ -31,7 +35,9 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.runtime.mutableStateOf
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
@@ -73,13 +79,16 @@ fun CardEditable(
codeType: BarcodeFormat,
onCodeTypeChange: (BarcodeFormat) -> Unit,
color: String?,
- onColorChange: (String?) -> Unit
+ onColorChange: (String?) -> Unit,
+ icon: String?,
+ onIconChange: (String?) -> Unit
) {
val context = LocalContext.current
var isCodeTypeDropdownExpanded by rememberSaveable { mutableStateOf(false) }
var isColorEnabled by rememberSaveable { mutableStateOf(color != null) }
var currentColor by rememberSaveable { mutableStateOf(color) }
+ var currentIcon by rememberSaveable { mutableStateOf(icon) }
val fieldModifier = Modifier.fillMaxWidth()
@@ -118,6 +127,16 @@ fun CardEditable(
}
}
+ val selectIconLauncher = rememberLauncherForActivityResult(
+ contract = ActivityResultContracts.StartActivityForResult()
+ ) { result ->
+ if (result.resultCode == Activity.RESULT_OK) {
+ val icon = result.data?.getStringExtra("icon")
+
+ onIconChange(icon)
+ }
+ }
+
OutlinedTextField(
value = cardName,
onValueChange = onCardNameChange,
@@ -221,6 +240,29 @@ fun CardEditable(
}
)
}
+
+ AdaptiveButton(
+ modifier = Modifier.fillMaxWidth().height(64.dp),
+ contentPadding = PaddingValues(horizontal = 0.dp),
+ onClick = {
+ val intent = Intent(context, SelectIconActivity::class.java)
+
+ selectIconLauncher.launch(intent)
+ }
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(Color.Transparent)
+ .padding(horizontal = 8.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ CardIcon(if (icon == null) null else base64ToImageBitmap(icon), MaterialTheme.colorScheme.primary)
+
+ Text(stringResource(R.string.do_select_icon))
+ }
+ }
}
@Composable
@@ -229,6 +271,7 @@ fun AddCard(innerPadding: PaddingValues, getDbFun: () -> DbHelper) {
var codeValue by rememberSaveable { mutableStateOf("") }
var codeType by rememberSaveable { mutableStateOf(BarcodeFormat.QR_CODE) }
var color by rememberSaveable { mutableStateOf(null) }
+ var icon by rememberSaveable { mutableStateOf(null) }
val context = LocalActivity.current
@@ -248,7 +291,9 @@ fun AddCard(innerPadding: PaddingValues, getDbFun: () -> DbHelper) {
codeType = codeType,
onCodeTypeChange = { codeType = it },
color = color,
- onColorChange = { color = it }
+ onColorChange = { color = it },
+ icon = icon,
+ onIconChange = { icon = it }
)
AdaptiveButton(
@@ -259,7 +304,7 @@ fun AddCard(innerPadding: PaddingValues, getDbFun: () -> DbHelper) {
val err = checkCardData(cardName, codeValue)
if (err == null) {
- getDbFun().addCard(cardName, codeValue, codeType, color)
+ getDbFun().addCard(cardName, codeValue, codeType, color, icon)
context!!.finish()
} else {
Toast.makeText(context, context!!.getString(err), Toast.LENGTH_SHORT).show()
@@ -278,6 +323,7 @@ fun EditCard(cardInfo: CardInfo, innerPadding: PaddingValues, getDbFun: () -> Db
var codeValue by rememberSaveable { mutableStateOf(cardInfo.codeValue) }
var codeType by rememberSaveable { mutableStateOf(cardInfo.codeType) }
var color by rememberSaveable { mutableStateOf(cardInfo.color) }
+ var icon by rememberSaveable { mutableStateOf(cardInfo.icon) }
val context = LocalActivity.current
@@ -297,7 +343,9 @@ fun EditCard(cardInfo: CardInfo, innerPadding: PaddingValues, getDbFun: () -> Db
codeType = codeType,
onCodeTypeChange = { codeType = it },
color = color,
- onColorChange = { color = it }
+ onColorChange = { color = it },
+ icon = icon,
+ onIconChange = { icon = it }
)
AdaptiveButton(
@@ -308,7 +356,7 @@ fun EditCard(cardInfo: CardInfo, innerPadding: PaddingValues, getDbFun: () -> Db
val err = checkCardData(cardName, codeValue)
if (err == null) {
- getDbFun().editCard(cardInfo.id, cardName, codeValue, codeType, color)
+ getDbFun().editCard(cardInfo.id, cardName, codeValue, codeType, color, icon)
context!!.finish()
} else {
Toast.makeText(context, context!!.getString(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 9520334..e2baa2e 100644
--- a/mobile/src/main/java/ru/omni_devel/cards/Elements.kt
+++ b/mobile/src/main/java/ru/omni_devel/cards/Elements.kt
@@ -4,6 +4,7 @@ import android.content.Intent
import androidx.activity.compose.BackHandler
import androidx.activity.compose.LocalActivity
import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTapGestures
@@ -11,24 +12,26 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
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.RowScope
-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
-import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
import androidx.compose.material3.Button
-import androidx.compose.material3.ButtonColors
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ButtonElevation
import androidx.compose.material3.DrawerValue
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
+import androidx.compose.material3.Icon
+import androidx.compose.material.icons.outlined.QrCode
+import androidx.compose.material.icons.outlined.DensityMedium
+import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalDrawerSheet
import androidx.compose.material3.ModalNavigationDrawer
@@ -45,11 +48,10 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier.Companion
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
@@ -105,15 +107,15 @@ fun Page(name: String, innerPadding: PaddingValues, pages: List Unit) {
var menuExpanded by remember { mutableStateOf(false) }
+ val cardColor = if (cardInfo.color == null)
+ MaterialTheme.colorScheme.primaryContainer
+ else
+ Color(cardInfo.color.toColorInt())
+
Box(modifier = Modifier.fillMaxWidth()) {
Surface(
modifier = Modifier
@@ -185,20 +192,27 @@ fun CardButton(cardInfo: CardInfo, onDeleteCard: (Int) -> Unit) {
)
},
shape = RoundedCornerShape(12.dp),
- color = (if (cardInfo.color == null) MaterialTheme.colorScheme.primaryContainer else Color(cardInfo.color.toColorInt())).copy(0.425f),
+ color = cardColor.copy(0.425f),
tonalElevation = 2.dp,
) {
Row(
- modifier = Modifier.fillMaxSize(),
- verticalAlignment = Alignment.CenterVertically
+ modifier = Modifier
+ .fillMaxSize()
+ .background(Color.Transparent)
+ .padding(horizontal = 8.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
+ CardIcon(
+ if (cardInfo.icon == null) null else base64ToImageBitmap(cardInfo.icon),
+ cardColor = cardColor
+ )
+
Text(
cardInfo.name,
fontSize = 16.sp,
color = MaterialTheme.colorScheme.onPrimaryContainer,
- modifier = Modifier
- .padding(16.dp)
- .fillMaxWidth(),
+ modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Start
)
}
@@ -232,12 +246,46 @@ fun CardButton(cardInfo: CardInfo, onDeleteCard: (Int) -> Unit) {
}
}
+@Composable
+fun CardIcon(icon: ImageBitmap?, cardColor: Color, isLoading: Boolean = false) {
+ Row(
+ modifier = Modifier
+ .background(
+ cardColor.copy(0.525f),
+ RoundedCornerShape(12.dp)
+ )
+ .padding(8.dp)
+ .size(32.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.Center
+ ) {
+ if (isLoading) {
+ CircularProgressIndicator(
+ modifier = Modifier.size(24.dp),
+ color = MaterialTheme.colorScheme.primary
+ )
+ } else if (icon == null) {
+ Icon(
+ modifier = Modifier.size(24.dp),
+ imageVector = Icons.Outlined.QrCode,
+ tint = MaterialTheme.colorScheme.onPrimaryContainer,
+ contentDescription = null
+ )
+ } else {
+ Image(
+ bitmap = icon,
+ contentDescription = null,
+ modifier = Modifier.size(32.dp)
+ )
+ }
+ }
+}
+
@Composable
fun AdaptiveButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
- shape: Shape = ButtonDefaults.shape,
elevation: ButtonElevation? = ButtonDefaults.buttonElevation(),
border: BorderStroke? = null,
contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
@@ -248,7 +296,7 @@ fun AdaptiveButton(
onClick = onClick,
modifier = modifier,
enabled = enabled,
- shape = shape,
+ shape = RoundedCornerShape(12.dp),
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
contentColor = MaterialTheme.colorScheme.onPrimaryContainer
diff --git a/mobile/src/main/java/ru/omni_devel/cards/FillTemplateActivity.kt b/mobile/src/main/java/ru/omni_devel/cards/FillTemplateActivity.kt
index b6f7624..3102d4a 100644
--- a/mobile/src/main/java/ru/omni_devel/cards/FillTemplateActivity.kt
+++ b/mobile/src/main/java/ru/omni_devel/cards/FillTemplateActivity.kt
@@ -6,44 +6,22 @@ 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.background
-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.foundation.layout.size
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.material3.Button
-import androidx.compose.material3.CircularProgressIndicator
-import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
-import io.ktor.client.HttpClient
-import io.ktor.client.engine.cio.CIO
-import io.ktor.client.request.get
-import io.ktor.client.statement.HttpResponse
-import io.ktor.client.statement.bodyAsText
-import kotlinx.serialization.json.Json
import ru.omni_devel.cards.ui.theme.OmniCardsTheme
class FillTemplateActivity : ComponentActivity() {
@@ -76,7 +54,7 @@ fun FillTemplatePage(template: Template, innerPadding: PaddingValues) {
}) }
Page(
- selectTemplateNameViaLanguage(template.names, language),
+ selectItemNameViaLanguage(template.names, language),
innerPadding
) {
for (field in template.fields) {
@@ -88,7 +66,7 @@ fun FillTemplatePage(template: Template, innerPadding: PaddingValues) {
}
},
modifier = Modifier.fillMaxWidth(),
- label = { Text((if (field.maybeEmpty) "" else "*") + selectTemplateNameViaLanguage(field.names, language)) }
+ label = { Text((if (field.maybeEmpty) "" else "*") + selectItemNameViaLanguage(field.names, language)) }
)
}
diff --git a/mobile/src/main/java/ru/omni_devel/cards/SelectIconActivity.kt b/mobile/src/main/java/ru/omni_devel/cards/SelectIconActivity.kt
new file mode 100644
index 0000000..d1d62cb
--- /dev/null
+++ b/mobile/src/main/java/ru/omni_devel/cards/SelectIconActivity.kt
@@ -0,0 +1,210 @@
+package ru.omni_devel.cards
+
+import android.app.Activity
+import android.content.Intent
+import android.graphics.BitmapFactory
+import android.os.Bundle
+import android.widget.Toast
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.LocalActivity
+import androidx.activity.compose.setContent
+import androidx.activity.enableEdgeToEdge
+import androidx.activity.result.ActivityResult
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.QrCode
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateMapOf
+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.graphics.ImageBitmap
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import io.ktor.client.HttpClient
+import io.ktor.client.call.body
+import io.ktor.client.engine.cio.CIO
+import io.ktor.client.request.get
+import io.ktor.client.statement.HttpResponse
+import io.ktor.client.statement.bodyAsText
+import ru.omni_devel.cards.ui.theme.OmniCardsTheme
+
+class SelectIconActivity : ComponentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ enableEdgeToEdge()
+ setContent {
+ OmniCardsTheme {
+ Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
+ SelectIconPage(innerPadding)
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun SelectIconPage(innerPadding: PaddingValues) {
+ val context = LocalContext.current
+ val activityContext = LocalActivity.current
+
+ val language = getLanguage(context)
+
+ var searchQuery by rememberSaveable { mutableStateOf("") }
+
+ var iconsData by remember { mutableStateOf?>(null) }
+ val loadedIcons = remember { mutableStateMapOf() }
+ var loadingError by remember { mutableStateOf(null) }
+
+ LaunchedEffect(Unit) {
+ try {
+ val client = HttpClient(CIO)
+
+ val response: HttpResponse = client.get("$REPO_BASE_URL/$ICONS_REPO_FILE")
+
+ val loadedIconsData = CardIcon.parseIconsDataFromJson(response.bodyAsText())
+ iconsData = loadedIconsData
+
+ for (iconData in loadedIconsData) {
+ if (iconData.iconId != null) {
+ val response = client.get("$REPO_BASE_URL/icons/${iconData.iconId}.png")
+ val bytes: ByteArray = response.body()
+
+ BitmapFactory.decodeByteArray(bytes, 0, bytes.size)?.let { bitmap ->
+ loadedIcons[iconData.iconId] = bitmap.asImageBitmap()
+ }
+ }
+ }
+ } catch (e: Exception) {
+ loadingError = e.toString()
+ }
+ }
+
+ Page(context.getString(R.string.choosing_icon), innerPadding) {
+ OutlinedTextField(
+ value = searchQuery,
+ onValueChange = { newValue ->
+ searchQuery = newValue
+ },
+ modifier = Modifier.fillMaxWidth(),
+ label = { Text(stringResource(R.string.search)) }
+ )
+
+ LazyColumn(
+ verticalArrangement = Arrangement.spacedBy(8.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ if (loadingError != null) {
+ item {
+ Text(loadingError!!)
+ }
+ } else if (iconsData == null) {
+ item {
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ CircularProgressIndicator(
+ modifier = Modifier.size(48.dp),
+ color = MaterialTheme.colorScheme.primary
+ )
+ }
+ }
+ } else {
+ val searchResults = iconsData!!.filter { iconData ->
+ var queryCleaned = searchQuery.lowercase()
+
+ if (queryCleaned.isEmpty()) {
+ return@filter true
+ }
+
+ for (name in iconData.names.values) {
+ var nameCleaned = name.lowercase()
+
+ for ((from, to) in SEARCH_REPLACE_SYMBOLS) {
+ nameCleaned = nameCleaned.replace(from, to)
+ queryCleaned = queryCleaned.replace(from, to)
+ }
+
+ if (nameCleaned.contains(queryCleaned)) {
+ return@filter true
+ }
+ }
+
+ return@filter false
+ }
+
+ if (searchResults.isEmpty()) {
+ item {
+ Text(stringResource(R.string.icons_not_found))
+ }
+ } else {
+ for (iconData in searchResults) {
+ item {
+ val loadedIcon = loadedIcons[iconData.iconId]
+
+ AdaptiveButton(
+ modifier = Modifier.fillMaxWidth().height(64.dp),
+ contentPadding = PaddingValues(0.dp),
+ onClick = {
+ if (loadedIcon == null && iconData.iconId != null) {
+ Toast.makeText(context, context.getString(R.string.wait_for_icon_load), Toast.LENGTH_SHORT).show()
+ } else {
+ val intent = Intent()
+
+ if (loadedIcon != null) {
+ intent.putExtra("icon", imageBitmapToBase64(loadedIcon))
+ }
+
+ activityContext!!.setResult(Activity.RESULT_OK, intent)
+ activityContext.finish()
+ }
+ }
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(Color.Transparent)
+ .padding(horizontal = 8.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ CardIcon(loadedIcon, MaterialTheme.colorScheme.primary, loadedIcon == null && iconData.iconId != null)
+
+ Text(selectItemNameViaLanguage(iconData.names, language))
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/mobile/src/main/java/ru/omni_devel/cards/SelectTemplateActivity.kt b/mobile/src/main/java/ru/omni_devel/cards/SelectTemplateActivity.kt
index 774fa93..fd06822 100644
--- a/mobile/src/main/java/ru/omni_devel/cards/SelectTemplateActivity.kt
+++ b/mobile/src/main/java/ru/omni_devel/cards/SelectTemplateActivity.kt
@@ -71,7 +71,7 @@ fun SelectTemplatePage(innerPadding: PaddingValues) {
try {
val client = HttpClient(CIO)
- val response: HttpResponse = client.get(TEMPLATES_URL)
+ val response: HttpResponse = client.get("$REPO_BASE_URL/$TEMPLATES_REPO_FILE")
templates = Template.parseTemplatesFromJson(response.bodyAsText())
} catch (e: Exception) {
@@ -104,7 +104,7 @@ fun SelectTemplatePage(innerPadding: PaddingValues) {
searchQuery = newValue
},
modifier = Modifier.fillMaxWidth(),
- label = { Text(stringResource(R.string.search_templates)) }
+ label = { Text(stringResource(R.string.search)) }
)
LazyColumn(
@@ -139,9 +139,9 @@ fun SelectTemplatePage(innerPadding: PaddingValues) {
for (name in template.names.values) {
var nameCleaned = name.lowercase()
- for (spaceSymbol in SPACE_SYMBOLS) {
- nameCleaned = nameCleaned.replace(spaceSymbol, "")
- queryCleaned = queryCleaned.replace(spaceSymbol, "")
+ for ((from, to) in SEARCH_REPLACE_SYMBOLS) {
+ nameCleaned = nameCleaned.replace(from, to)
+ queryCleaned = queryCleaned.replace(from, to)
}
if (nameCleaned.contains(queryCleaned)) {
@@ -169,7 +169,7 @@ fun SelectTemplatePage(innerPadding: PaddingValues) {
fillTemplateLauncher.launch(intent)
}
) {
- Text(selectTemplateNameViaLanguage(template.names, language))
+ Text(selectItemNameViaLanguage(template.names, language))
}
}
}
diff --git a/mobile/src/main/java/ru/omni_devel/cards/Template.kt b/mobile/src/main/java/ru/omni_devel/cards/Template.kt
index 0ad4e91..01e65a7 100644
--- a/mobile/src/main/java/ru/omni_devel/cards/Template.kt
+++ b/mobile/src/main/java/ru/omni_devel/cards/Template.kt
@@ -4,18 +4,6 @@ import kotlinx.serialization.Serializable
import org.json.JSONArray
import org.json.JSONObject
-private fun jsonObjectToMap(obj: JSONObject): LinkedHashMap {
- val map = LinkedHashMap()
- val keys = obj.keys()
-
- while (keys.hasNext()) {
- val key = keys.next()
- map[key] = obj.getString(key)
- }
-
- return map
-}
-
@Serializable
class Field(
val id: String,
diff --git a/mobile/src/main/java/ru/omni_devel/cards/Utils.kt b/mobile/src/main/java/ru/omni_devel/cards/Utils.kt
index c189dc7..a3e9bfa 100644
--- a/mobile/src/main/java/ru/omni_devel/cards/Utils.kt
+++ b/mobile/src/main/java/ru/omni_devel/cards/Utils.kt
@@ -2,12 +2,19 @@ package ru.omni_devel.cards
import android.content.Context
import android.graphics.Bitmap
+import android.graphics.BitmapFactory
import android.os.LocaleList
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.asAndroidBitmap
import com.google.android.gms.wearable.Wearable
import com.google.zxing.BarcodeFormat
import com.google.zxing.EncodeHintType
import com.google.zxing.MultiFormatWriter
import com.journeyapps.barcodescanner.BarcodeEncoder
+import org.json.JSONObject
+import java.io.ByteArrayOutputStream
+import android.util.Base64
+import androidx.compose.ui.graphics.asImageBitmap
fun generateCode(value: String, format: BarcodeFormat): Bitmap {
val writer = MultiFormatWriter()
@@ -81,6 +88,36 @@ fun getLanguage(context: Context): String {
return languageCode
}
-fun selectTemplateNameViaLanguage(names: LinkedHashMap, language: String): String {
+fun selectItemNameViaLanguage(names: LinkedHashMap, language: String): String {
return names[language] ?: names.values.firstOrNull() ?: "???"
}
+
+fun jsonObjectToMap(obj: JSONObject): LinkedHashMap {
+ val map = LinkedHashMap()
+ val keys = obj.keys()
+
+ while (keys.hasNext()) {
+ val key = keys.next()
+ map[key] = obj.getString(key)
+ }
+
+ return map
+}
+
+fun imageBitmapToBase64(imageBitmap: ImageBitmap): String {
+ val bitmap = imageBitmap.asAndroidBitmap()
+ val outputStream = ByteArrayOutputStream()
+
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
+
+ val byteArray = outputStream.toByteArray()
+
+ return Base64.encodeToString(byteArray, Base64.DEFAULT)
+}
+
+fun base64ToImageBitmap(base64String: String): ImageBitmap {
+ val decodedBytes = Base64.decode(base64String, Base64.DEFAULT)
+ val bitmap = BitmapFactory.decodeByteArray(decodedBytes, 0, decodedBytes.size)
+
+ return bitmap.asImageBitmap()
+}
diff --git a/mobile/src/main/res/values-ru/strings.xml b/mobile/src/main/res/values-ru/strings.xml
index 0f1043a..2849e5c 100644
--- a/mobile/src/main/res/values-ru/strings.xml
+++ b/mobile/src/main/res/values-ru/strings.xml
@@ -39,7 +39,11 @@
Выбор шаблона
Ошибка в данных. Пожалуйста, заполните все необходимые (*) поля
Шаблоны не найдены
- Поиск шаблонов
+ Поиск
+ Выбор иконки
+ Иконки не найдены
+ Выбрать иконку
+ Дождитесь загрузки иконки
Имя карты не должно быть пустым
Значение карты не должно быть пустым
Имя карты должно быть короче 32 символов
diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml
index a11b85f..9a787f4 100644
--- a/mobile/src/main/res/values/strings.xml
+++ b/mobile/src/main/res/values/strings.xml
@@ -44,10 +44,15 @@
Choosing template
Error in data. Please, fill all required (*) fields
Templates not found
- Search templates
+ Search
+ Choosing icon
+ Icons not found
+ Select icon
+ Wait for icon load
Card name must not be empty
Card value must not be empty
Card name must be shorter than 32 characters
Failed to generate card code. Check the card data
+ SelectIconActivity
\ No newline at end of file