forked from omni/OmniCards
feat: full md3 redesign and few tweaks
fully redesigned android and wearos app auto sync with wearos enabled minify
This commit is contained in:
@@ -27,8 +27,12 @@ android {
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
applicationIdSuffix = ".debug"
|
||||
}
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
@@ -52,6 +56,7 @@ dependencies {
|
||||
implementation(libs.androidx.appcompat)
|
||||
implementation(libs.androidx.compose.foundation)
|
||||
implementation(libs.androidx.compose.material3)
|
||||
implementation("androidx.compose.material:material-icons-extended")
|
||||
implementation(libs.androidx.compose.ui)
|
||||
implementation(libs.androidx.compose.ui.graphics)
|
||||
implementation(libs.androidx.compose.ui.tooling.preview)
|
||||
|
||||
Vendored
+11
-1
@@ -18,4 +18,14 @@
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
#-renamesourcefileattribute SourceFile
|
||||
|
||||
-keepattributes *Annotation*, InnerClasses
|
||||
-dontnote kotlinx.serialization.**
|
||||
-keepclassmembers @kotlinx.serialization.Serializable class ** {
|
||||
*** Companion;
|
||||
*** serializer(...);
|
||||
}
|
||||
-keepclasseswithmembers class **$$serializer { *; }
|
||||
-keepclassmembers enum com.google.zxing.BarcodeFormat { *; }
|
||||
|
||||
|
||||
@@ -4,10 +4,14 @@ import com.google.zxing.BarcodeFormat
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
class CardInfo (
|
||||
class CardInfo(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
val codeType: BarcodeFormat,
|
||||
val codeValue: String,
|
||||
val color: String?
|
||||
)
|
||||
val color: String?,
|
||||
val position: Int = 0,
|
||||
val iconPath: String? = null,
|
||||
val monochrome: Boolean = false,
|
||||
val iconData: String? = null,
|
||||
)
|
||||
|
||||
@@ -6,40 +6,40 @@ 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, 2) {
|
||||
class DbHelper(val context: Context, val factory: SQLiteDatabase.CursorFactory?) : SQLiteOpenHelper(context, "omni_cards", factory, 3) {
|
||||
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, position INTEGER DEFAULT 0, iconPath TEXT)")
|
||||
}
|
||||
|
||||
override fun onUpgrade(
|
||||
db: SQLiteDatabase?,
|
||||
oldVersion: Int,
|
||||
p2: Int
|
||||
) {
|
||||
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, p2: Int) {
|
||||
if (oldVersion < 2) {
|
||||
db!!.execSQL("ALTER TABLE cards ADD COLUMN color TEXT")
|
||||
}
|
||||
if (oldVersion < 3) {
|
||||
db!!.execSQL("ALTER TABLE cards ADD COLUMN position INTEGER DEFAULT 0")
|
||||
db.execSQL("ALTER TABLE cards ADD COLUMN iconPath TEXT")
|
||||
db.execSQL("UPDATE cards SET position = id")
|
||||
}
|
||||
}
|
||||
|
||||
fun clearDatabase() {
|
||||
val db = this.writableDatabase
|
||||
|
||||
db.execSQL("DELETE FROM cards")
|
||||
|
||||
db.close()
|
||||
}
|
||||
|
||||
fun addCards(cards: List<CardInfo>) {
|
||||
val db = this.writableDatabase
|
||||
|
||||
for (card in cards) {
|
||||
cards.forEachIndexed { index, card ->
|
||||
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)
|
||||
values.put("iconPath", card.iconPath)
|
||||
values.put("position", index)
|
||||
|
||||
db.insert("cards", null, values)
|
||||
}
|
||||
@@ -47,20 +47,25 @@ class DbHelper(val context: Context, val factory: SQLiteDatabase.CursorFactory?)
|
||||
db.close()
|
||||
}
|
||||
|
||||
private fun readCard(cursor: android.database.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")),
|
||||
position = cursor.getInt(cursor.getColumnIndexOrThrow("position")),
|
||||
iconPath = cursor.getString(cursor.getColumnIndexOrThrow("iconPath")),
|
||||
)
|
||||
}
|
||||
|
||||
fun getCards(): List<CardInfo> {
|
||||
val db = this.readableDatabase
|
||||
|
||||
val cursor = db.rawQuery("SELECT * FROM cards", null)
|
||||
val cursor = db.rawQuery("SELECT * FROM cards ORDER BY position ASC, id ASC", null)
|
||||
val cards = mutableListOf<CardInfo>()
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
cards.add(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"))
|
||||
))
|
||||
cards.add(readCard(cursor))
|
||||
}
|
||||
|
||||
cursor.close()
|
||||
@@ -71,7 +76,6 @@ class DbHelper(val context: Context, val factory: SQLiteDatabase.CursorFactory?)
|
||||
|
||||
fun getCard(id: Int): CardInfo? {
|
||||
val db = this.readableDatabase
|
||||
|
||||
val cursor = db.rawQuery("SELECT * FROM cards WHERE id = ?", arrayOf(id.toString()))
|
||||
|
||||
if (!cursor.moveToFirst()) {
|
||||
@@ -81,17 +85,10 @@ class DbHelper(val context: Context, val factory: SQLiteDatabase.CursorFactory?)
|
||||
return null
|
||||
}
|
||||
|
||||
val cardInfo = 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"))
|
||||
)
|
||||
|
||||
val cardInfo = readCard(cursor)
|
||||
cursor.close()
|
||||
db.close()
|
||||
|
||||
return cardInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,77 +1,127 @@
|
||||
package ru.omni_devel.cards.presentation
|
||||
|
||||
import android.content.Intent
|
||||
import android.graphics.BitmapFactory
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
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.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.QrCode2
|
||||
import androidx.compose.material.icons.filled.QrCodeScanner
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.ColorMatrix
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.graphics.compositeOver
|
||||
import androidx.compose.ui.graphics.luminance
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.core.graphics.toColorInt
|
||||
import androidx.wear.compose.material3.Button
|
||||
import androidx.wear.compose.material3.ButtonDefaults
|
||||
import androidx.wear.compose.material3.Icon
|
||||
import androidx.wear.compose.material3.MaterialTheme
|
||||
import androidx.wear.compose.material3.Text
|
||||
import com.google.zxing.BarcodeFormat
|
||||
|
||||
private fun autoGlyph(codeType: BarcodeFormat): ImageVector {
|
||||
return when (codeType) {
|
||||
BarcodeFormat.QR_CODE, BarcodeFormat.DATA_MATRIX, BarcodeFormat.AZTEC, BarcodeFormat.PDF_417 -> Icons.Filled.QrCode2
|
||||
else -> Icons.Filled.QrCodeScanner
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun rememberIconBitmap(path: String?): ImageBitmap? {
|
||||
return remember(path) {
|
||||
if (path == null) return@remember null
|
||||
runCatching { BitmapFactory.decodeFile(path)?.asImageBitmap() }.getOrNull()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CardButton(cardInfo: CardInfo) {
|
||||
val context = LocalContext.current
|
||||
|
||||
Box(modifier = Modifier.fillMaxWidth()) {
|
||||
Button(
|
||||
val accent = cardInfo.color?.let { runCatching { Color(it.toColorInt()) }.getOrNull() }
|
||||
val bitmap = rememberIconBitmap(cardInfo.iconPath)
|
||||
val base = MaterialTheme.colorScheme.surfaceContainer
|
||||
val container = if (accent != null) accent.copy(alpha = 0.18f).compositeOver(base) else base
|
||||
val tile = accent ?: MaterialTheme.colorScheme.secondaryContainer
|
||||
val onTile = if (accent != null) {
|
||||
if (accent.luminance() > 0.5f) Color.Black.copy(alpha = 0.8f) else Color.White
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onSecondaryContainer
|
||||
}
|
||||
|
||||
Button(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = container
|
||||
),
|
||||
onClick = {
|
||||
val intent = Intent(context, ShowCardActivity::class.java)
|
||||
intent.putExtra("cardId", cardInfo.id)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = MaterialTheme.colorScheme.primaryContainer
|
||||
),
|
||||
contentPadding = PaddingValues(0.dp),
|
||||
onClick = {
|
||||
val intent = Intent(context, ShowCardActivity::class.java)
|
||||
|
||||
intent.putExtra("cardId", cardInfo.id)
|
||||
|
||||
context.startActivity(intent)
|
||||
}
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Row(
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(IntrinsicSize.Min)
|
||||
.clip(ButtonDefaults.shape)
|
||||
.size(30.dp)
|
||||
.clip(RoundedCornerShape(9.dp))
|
||||
.background(if (bitmap != null) Color.White else tile),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
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
|
||||
)
|
||||
if (bitmap != null) {
|
||||
Image(
|
||||
bitmap = bitmap,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(24.dp),
|
||||
contentScale = ContentScale.Fit,
|
||||
colorFilter = if (cardInfo.monochrome) {
|
||||
ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(0f) })
|
||||
} else {
|
||||
null
|
||||
}
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
imageVector = autoGlyph(cardInfo.codeType),
|
||||
contentDescription = null,
|
||||
tint = onTile,
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
Text(
|
||||
cardInfo.name,
|
||||
modifier = Modifier
|
||||
.padding(start = 10.dp)
|
||||
.fillMaxWidth(),
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
textAlign = TextAlign.Start,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
package ru.omni_devel.cards.presentation
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
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.PaddingValues
|
||||
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.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.wear.compose.foundation.lazy.TransformingLazyColumn
|
||||
import androidx.wear.compose.foundation.lazy.rememberTransformingLazyColumnState
|
||||
import androidx.wear.compose.material3.AppScaffold
|
||||
import androidx.wear.compose.material3.MaterialTheme
|
||||
import androidx.wear.compose.material3.ScreenScaffold
|
||||
import androidx.wear.compose.material3.Text
|
||||
import ru.omni_devel.cards.R
|
||||
import ru.omni_devel.cards.presentation.theme.OmniCardsTheme
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
private lateinit var db: DbHelper
|
||||
@@ -27,36 +30,39 @@ class MainActivity : ComponentActivity() {
|
||||
db = DbHelper(this, null)
|
||||
|
||||
setContent {
|
||||
App(db.getCards())
|
||||
OmniCardsTheme {
|
||||
App(db.getCards())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("UnusedBoxWithConstraintsScope")
|
||||
@Composable
|
||||
fun App(cards: List<CardInfo>) {
|
||||
BoxWithConstraints(modifier = Modifier.fillMaxSize()) {
|
||||
val topBottomPadding = maxHeight * 0.4f
|
||||
|
||||
AppScaffold {
|
||||
if (cards.isEmpty()) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(stringResource(R.string.sync_with_mobile_app_to_get_started))
|
||||
Text(
|
||||
stringResource(R.string.sync_with_mobile_app_to_get_started),
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
} else {
|
||||
TransformingLazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(
|
||||
top = topBottomPadding,
|
||||
bottom = topBottomPadding
|
||||
),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
for (card in cards) {
|
||||
item {
|
||||
CardButton(card)
|
||||
val listState = rememberTransformingLazyColumnState()
|
||||
ScreenScaffold(scrollState = listState) { contentPadding ->
|
||||
TransformingLazyColumn(
|
||||
state = listState,
|
||||
contentPadding = contentPadding,
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
for (card in cards) {
|
||||
item {
|
||||
CardButton(card)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
package ru.omni_devel.cards.presentation
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.widget.Toast
|
||||
import android.util.Base64
|
||||
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
|
||||
import java.io.File
|
||||
|
||||
class MessagesReceiver : WearableListenerService() {
|
||||
private lateinit var db: DbHelper
|
||||
private val json = Json { ignoreUnknownKeys = true }
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
@@ -21,22 +19,29 @@ class MessagesReceiver : WearableListenerService() {
|
||||
override fun onMessageReceived(event: MessageEvent) {
|
||||
when (event.path) {
|
||||
"/updateCards" -> {
|
||||
val json = String(event.data, Charsets.UTF_8)
|
||||
val jsonText = String(event.data, Charsets.UTF_8)
|
||||
|
||||
val cards = Json.decodeFromString<List<CardInfo>>(json)
|
||||
val cards = json.decodeFromString<List<CardInfo>>(jsonText)
|
||||
|
||||
val iconsDir = File(filesDir, "icons")
|
||||
iconsDir.deleteRecursively()
|
||||
iconsDir.mkdirs()
|
||||
|
||||
val processed = cards.map { c ->
|
||||
val localPath = c.iconData?.let { data ->
|
||||
runCatching {
|
||||
val bytes = Base64.decode(data, Base64.NO_WRAP)
|
||||
val file = File(iconsDir, "${c.id}.png")
|
||||
file.writeBytes(bytes)
|
||||
file.absolutePath
|
||||
}.getOrNull()
|
||||
}
|
||||
CardInfo(c.id, c.name, c.codeType, c.codeValue, c.color, c.position, localPath, c.monochrome)
|
||||
}
|
||||
|
||||
db.clearDatabase()
|
||||
db.addCards(cards)
|
||||
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
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
|
||||
}
|
||||
startActivity(intent)
|
||||
}
|
||||
db.addCards(processed)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,22 +8,37 @@ import android.widget.Toast
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
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.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
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.Color
|
||||
import androidx.compose.ui.graphics.FilterQuality
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.graphics.lerp
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.graphics.toColorInt
|
||||
import androidx.wear.compose.material3.MaterialTheme
|
||||
import androidx.wear.compose.material3.Text
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import ru.omni_devel.cards.R
|
||||
import ru.omni_devel.cards.presentation.theme.OmniCardsTheme
|
||||
|
||||
class ShowCardActivity : ComponentActivity() {
|
||||
private lateinit var db: DbHelper
|
||||
@@ -39,72 +54,103 @@ class ShowCardActivity : ComponentActivity() {
|
||||
db = DbHelper(this, null)
|
||||
|
||||
val cardId = intent.getIntExtra("cardId", 0)
|
||||
|
||||
val cardInfo = db.getCard(cardId)
|
||||
|
||||
if (cardInfo == null) {
|
||||
Toast.makeText(this, "cardInfo is null", Toast.LENGTH_LONG).show()
|
||||
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
setContent {
|
||||
ShowCardPage(cardInfo)
|
||||
OmniCardsTheme {
|
||||
ShowCardPage(cardInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
setBrightness(1.0f)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
|
||||
setBrightness(WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE)
|
||||
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun setBrightness(level: Float) {
|
||||
val params = window.attributes
|
||||
|
||||
params.screenBrightness = level
|
||||
|
||||
window.attributes = params
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ShowCardPage(cardInfo: CardInfo) {
|
||||
val seed = cardInfo.color?.let { runCatching { Color(it.toColorInt()) }.getOrNull() } ?: Color(0xFFC7BFFF)
|
||||
val codeBg = lerp(seed, Color.White, 0.80f)
|
||||
val codeModule = lerp(seed, Color.Black, 0.55f)
|
||||
|
||||
BoxWithConstraints(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.background),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
val screenSize = minOf(maxWidth, maxHeight)
|
||||
val safeSize = (screenSize * 0.65f)
|
||||
val sizePx = with(LocalDensity.current) { safeSize.roundToPx() }
|
||||
|
||||
val bitmap = remember(cardInfo, sizePx) {
|
||||
try {
|
||||
generateCode(cardInfo.codeValue, cardInfo.codeType, sizePx, sizePx).asImageBitmap()
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
val bitmap = remember(cardInfo, codeModule, codeBg) {
|
||||
styledCodeBitmap(cardInfo.codeValue, cardInfo.codeType, codeModule.toArgb(), codeBg.toArgb())?.asImageBitmap()
|
||||
}
|
||||
val aspect = bitmap?.let { it.width.toFloat() / it.height.toFloat() } ?: 1f
|
||||
val is1d = cardInfo.codeType != BarcodeFormat.QR_CODE &&
|
||||
cardInfo.codeType != BarcodeFormat.DATA_MATRIX &&
|
||||
cardInfo.codeType != BarcodeFormat.AZTEC &&
|
||||
cardInfo.codeType != BarcodeFormat.PDF_417
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
verticalArrangement = Arrangement.Center,
|
||||
modifier = Modifier.padding(horizontal = screenSize * 0.06f)
|
||||
) {
|
||||
if (bitmap != null) {
|
||||
Image(
|
||||
bitmap = bitmap,
|
||||
contentDescription = cardInfo.codeValue,
|
||||
modifier = Modifier.size(safeSize)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colorScheme.surfaceContainer, RoundedCornerShape(14.dp))
|
||||
.padding(horizontal = 14.dp, vertical = 6.dp)
|
||||
) {
|
||||
Text(
|
||||
cardInfo.name,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
textAlign = TextAlign.Center,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(10.dp))
|
||||
|
||||
if (bitmap != null) {
|
||||
val codeModifier = when {
|
||||
is1d -> Modifier.size(screenSize * 0.74f, screenSize * 0.30f)
|
||||
cardInfo.codeType == BarcodeFormat.PDF_417 -> Modifier.size(screenSize * 0.74f, screenSize * 0.74f / aspect)
|
||||
else -> Modifier.size(screenSize * 0.56f)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(codeBg, RoundedCornerShape(16.dp))
|
||||
.padding(12.dp)
|
||||
) {
|
||||
Image(
|
||||
bitmap = bitmap,
|
||||
contentDescription = cardInfo.codeValue,
|
||||
modifier = codeModifier,
|
||||
contentScale = ContentScale.FillBounds,
|
||||
filterQuality = FilterQuality.None
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Text(
|
||||
stringResource(R.string.failed_to_generate_card_code),
|
||||
|
||||
@@ -2,12 +2,41 @@ package ru.omni_devel.cards.presentation
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import com.google.zxing.EncodeHintType
|
||||
import com.google.zxing.MultiFormatWriter
|
||||
import com.journeyapps.barcodescanner.BarcodeEncoder
|
||||
import com.google.zxing.datamatrix.encoder.SymbolShapeHint
|
||||
|
||||
fun generateCode(value: String, format: BarcodeFormat, width: Int, height: Int): Bitmap {
|
||||
val writer = MultiFormatWriter()
|
||||
val matrix = writer.encode(value, format, width, height)
|
||||
fun styledCodeBitmap(value: String, format: BarcodeFormat, onArgb: Int, bgArgb: Int): Bitmap? {
|
||||
val is2d = format == BarcodeFormat.QR_CODE || format == BarcodeFormat.DATA_MATRIX ||
|
||||
format == BarcodeFormat.AZTEC || format == BarcodeFormat.PDF_417
|
||||
|
||||
return BarcodeEncoder().createBitmap(matrix)
|
||||
val matrix = runCatching {
|
||||
if (!is2d) {
|
||||
MultiFormatWriter().encode(value, format, 0, 1)
|
||||
} else {
|
||||
val hints: Map<EncodeHintType, Any>? = if (format == BarcodeFormat.DATA_MATRIX) {
|
||||
mapOf(EncodeHintType.DATA_MATRIX_SHAPE to SymbolShapeHint.FORCE_SQUARE)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
MultiFormatWriter().encode(value, format, 0, 0, hints)
|
||||
}
|
||||
}.getOrNull() ?: return null
|
||||
|
||||
val rect = matrix.enclosingRectangle ?: intArrayOf(0, 0, matrix.width, matrix.height)
|
||||
val left = rect[0]
|
||||
val top = rect[1]
|
||||
val w = rect[2]
|
||||
val h = rect[3]
|
||||
if (w <= 0 || h <= 0) return null
|
||||
|
||||
val pixels = IntArray(w * h)
|
||||
for (y in 0 until h) {
|
||||
for (x in 0 until w) {
|
||||
pixels[y * w + x] = if (matrix.get(left + x, top + y)) onArgb else bgArgb
|
||||
}
|
||||
}
|
||||
val bmp = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
|
||||
bmp.setPixels(pixels, 0, w, 0, 0, w, h)
|
||||
return bmp
|
||||
}
|
||||
|
||||
@@ -1,17 +1,31 @@
|
||||
package ru.omni_devel.cards.presentation.theme
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.wear.compose.material3.ColorScheme
|
||||
import androidx.wear.compose.material3.MaterialTheme
|
||||
|
||||
private val OmniColorScheme = ColorScheme(
|
||||
primary = Color(0xFFC7BFFF),
|
||||
onPrimary = Color(0xFF2A1A8F),
|
||||
primaryContainer = Color(0xFF4236B6),
|
||||
onPrimaryContainer = Color(0xFFE4DFFF),
|
||||
secondary = Color(0xFFC8C3DD),
|
||||
onSecondary = Color(0xFF302E42),
|
||||
secondaryContainer = Color(0xFF474459),
|
||||
onSecondaryContainer = Color(0xFFE5DFF9),
|
||||
surfaceContainer = Color(0xFF1F1F25),
|
||||
surfaceContainerHigh = Color(0xFF2A2930),
|
||||
onSurface = Color(0xFFE4E1E9),
|
||||
onSurfaceVariant = Color(0xFFC8C5D0),
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun OmniCardsTheme(
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
/**
|
||||
* Empty theme to customize for your app.
|
||||
* See: https://developer.android.com/jetpack/compose/designsystems/custom
|
||||
*/
|
||||
MaterialTheme(
|
||||
colorScheme = OmniColorScheme,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<resources>
|
||||
<string-array name="android_wear_capabilities">
|
||||
<item>omnicards_wear_app</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user