feat: added companion app for WearOS, and setted up icons

This commit is contained in:
2026-06-10 11:06:35 +03:00
parent 70ba200d7a
commit c21708331c
57 changed files with 566 additions and 116 deletions
+3
View File
@@ -1,5 +1,6 @@
plugins { plugins {
alias(libs.plugins.android.application) alias(libs.plugins.android.application)
kotlin("plugin.serialization") version "2.2.20"
} }
android { android {
@@ -47,4 +48,6 @@ dependencies {
androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.junit)
implementation(libs.zxing.core) implementation(libs.zxing.core)
implementation(libs.zxing.android) 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")
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

@@ -1,7 +1,9 @@
package ru.omni_devel.cards package ru.omni_devel.cards
import com.google.zxing.BarcodeFormat import com.google.zxing.BarcodeFormat
import kotlinx.serialization.Serializable
@Serializable
class CardInfo ( class CardInfo (
val id: Int, val id: Int,
val name: String, val name: String,
@@ -7,11 +7,13 @@ import android.widget.Button
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.PopupMenu import android.widget.PopupMenu
import android.widget.TextView import android.widget.TextView
import android.widget.Toast
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import kotlinx.serialization.json.Json
import org.w3c.dom.Text import org.w3c.dom.Text
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
@@ -32,9 +34,23 @@ class MainActivity : AppCompatActivity() {
cardsList = findViewById(R.id.cardsList) cardsList = findViewById(R.id.cardsList)
val addCardButton: Button = findViewById(R.id.addCardButton) val addCardButton: Button = findViewById(R.id.addCardButton)
val syncWithWearOSButton: Button = findViewById(R.id.syncWithWearOSButton)
addCardButton.setOnClickListener { addCardButton.setOnClickListener {
startActivity(Intent(this, AddCardActivity::class.java)) startActivity(Intent(this, AddCardActivity::class.java))
} }
syncWithWearOSButton.setOnClickListener {
Toast.makeText(this, "Sync in progress...", Toast.LENGTH_SHORT).show()
val isSuccess = sendToWatch(this, "/updateCards", Json.encodeToString(db.getCards()))
if (isSuccess) {
Toast.makeText(this, "Synced!", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "Sync failed! Please, check your connection", Toast.LENGTH_LONG).show()
}
}
} }
override fun onResume() { override fun onResume() {
@@ -1,6 +1,9 @@
package ru.omni_devel.cards package ru.omni_devel.cards
import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.widget.Toast
import com.google.android.gms.wearable.Wearable
import com.google.zxing.BarcodeFormat import com.google.zxing.BarcodeFormat
import com.google.zxing.MultiFormatWriter import com.google.zxing.MultiFormatWriter
import com.journeyapps.barcodescanner.BarcodeEncoder import com.journeyapps.barcodescanner.BarcodeEncoder
@@ -11,3 +14,29 @@ fun generateCode(value: String, format: BarcodeFormat): Bitmap {
return BarcodeEncoder().createBitmap(matrix) return BarcodeEncoder().createBitmap(matrix)
} }
fun sendToWatch(context: Context, path: String, message: String): Boolean {
val nodeClient = Wearable.getNodeClient(context)
var isSuccess = false
nodeClient.connectedNodes.addOnSuccessListener { nodes ->
if (nodes.isEmpty()) {
Toast.makeText(context, "Watch is not connected", Toast.LENGTH_SHORT).show()
return@addOnSuccessListener
}
nodes.forEach { node ->
Wearable.getMessageClient(context).sendMessage(
node.id,
path,
message.toByteArray(Charsets.UTF_8)
).addOnSuccessListener {
isSuccess = true
}
}
}
return isSuccess
}
@@ -58,4 +58,16 @@
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" /> app:layout_constraintEnd_toEndOf="parent" />
<Button
android:id="@+id/syncWithWearOSButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:backgroundTint="#0E000000"
android:text="Sync with WearOS"
app:cornerRadius="12dp"
app:layout_constraintBottom_toBottomOf="@+id/toolbar"
app:layout_constraintEnd_toEndOf="@+id/toolbar"
app:layout_constraintTop_toTopOf="@+id/toolbar" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
@@ -0,0 +1,21 @@
<!--
~ Copyright (C) 2026 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>
@@ -0,0 +1,21 @@
<!--
~ Copyright (C) 2026 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 982 B

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 20 KiB

@@ -0,0 +1,19 @@
<!--
~ Copyright (C) 2026 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#000000</color>
</resources>
+7
View File
@@ -1,6 +1,7 @@
plugins { plugins {
alias(libs.plugins.android.application) alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.compose) alias(libs.plugins.kotlin.compose)
kotlin("plugin.serialization") version "2.2.20"
} }
android { android {
@@ -42,11 +43,14 @@ android {
dependencies { dependencies {
implementation(platform(libs.androidx.compose.bom)) implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.activity.compose) implementation(libs.androidx.activity.compose)
implementation(libs.androidx.activity.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.compose.foundation) implementation(libs.androidx.compose.foundation)
implementation(libs.androidx.compose.material3) implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.ui) implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.graphics) implementation(libs.androidx.compose.ui.graphics)
implementation(libs.androidx.compose.ui.tooling.preview) implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.core.splashscreen) implementation(libs.androidx.core.splashscreen)
implementation(libs.androidx.wear.tooling.preview) implementation(libs.androidx.wear.tooling.preview)
implementation(libs.compose.ui.tooling) implementation(libs.compose.ui.tooling)
@@ -57,4 +61,7 @@ dependencies {
debugImplementation(libs.androidx.compose.ui.tooling) debugImplementation(libs.androidx.compose.ui.tooling)
implementation(libs.zxing.core) implementation(libs.zxing.core)
implementation(libs.zxing.android) implementation(libs.zxing.android)
implementation(libs.material)
implementation("com.google.android.gms:play-services-wearable:18.1.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
} }
+11 -1
View File
@@ -11,13 +11,23 @@
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@android:style/Theme.DeviceDefault"> android:theme="@android:style/Theme.DeviceDefault">
<service android:name=".presentation.MessagesReceiver" android:exported="true">
<intent-filter>
<action android:name="com.google.android.gms.wearable.BIND_LISTENER" />
</intent-filter>
</service>
<activity
android:name=".presentation.ShowCardActivity"
android:exported="false"
android:theme="@android:style/Theme.DeviceDefault.NoActionBar" />
<uses-library <uses-library
android:name="com.google.android.wearable" android:name="com.google.android.wearable"
android:required="true" /> android:required="true" />
<uses-library <uses-library
android:name="wear-sdk" android:name="wear-sdk"
android:required="false" /> android:required="false" />
<!-- <!--
Set to true if your app is Standalone, that is, it does not require the handheld Set to true if your app is Standalone, that is, it does not require the handheld
app to run. app to run.
Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

@@ -0,0 +1,12 @@
package ru.omni_devel.cards.presentation
import com.google.zxing.BarcodeFormat
import kotlinx.serialization.Serializable
@Serializable
class CardInfo (
val id: Int,
val name: String,
val codeType: BarcodeFormat,
val codeValue: String,
)
@@ -0,0 +1,92 @@
package ru.omni_devel.cards.presentation
import android.content.ContentValues
import android.content.Context
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) {
override fun onCreate(db: SQLiteDatabase?) {
db!!.execSQL("CREATE TABLE IF NOT EXISTS cards (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, codeType TEXT, codeValue TEXT)")
}
override fun onUpgrade(
db: SQLiteDatabase?,
p1: Int,
p2: Int
) {
db!!.execSQL("DROP TABLE IF EXISTS cards")
}
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) {
val values = ContentValues()
values.put("id", card.id)
values.put("name", card.name)
values.put("codeValue", card.codeValue)
values.put("codeType", card.codeType.name)
db.insert("cards", null, values)
}
db.close()
}
fun getCards(): List<CardInfo> {
val db = this.readableDatabase
val cursor = db.rawQuery("SELECT * FROM cards", 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"))
))
}
cursor.close()
db.close()
return cards
}
fun getCard(id: Int): CardInfo? {
val db = this.readableDatabase
val cursor = db.rawQuery("SELECT * FROM cards WHERE id = ?", arrayOf(id.toString()))
if (!cursor.moveToFirst()) {
cursor.close()
db.close()
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"))
)
cursor.close()
db.close()
return cardInfo
}
}
@@ -1,115 +1,100 @@
/* While this template provides a good starting point for using Wear Compose, you can always
* take a look at https://github.com/android/wear-os-samples/tree/main/ComposeStarter to find the
* most up to date changes to the libraries and their usages.
*/
package ru.omni_devel.cards.presentation package ru.omni_devel.cards.presentation
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent 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.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
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.platform.LocalContext
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.TransformingLazyColumn
import androidx.wear.compose.foundation.lazy.rememberTransformingLazyColumnState
import androidx.wear.compose.material3.AppScaffold
import androidx.wear.compose.material3.Button import androidx.wear.compose.material3.Button
import androidx.wear.compose.material3.ButtonDefaults
import androidx.wear.compose.material3.EdgeButton
import androidx.wear.compose.material3.ListHeader
import androidx.wear.compose.material3.MaterialTheme
import androidx.wear.compose.material3.ScreenScaffold
import androidx.wear.compose.material3.SurfaceTransformation
import androidx.wear.compose.material3.Text import androidx.wear.compose.material3.Text
import androidx.wear.compose.material3.lazy.rememberTransformationSpec
import androidx.wear.compose.material3.lazy.transformedHeight
import androidx.wear.compose.ui.tooling.preview.WearPreviewDevices
import androidx.wear.compose.ui.tooling.preview.WearPreviewFontScales
import ru.omni_devel.cards.R
import ru.omni_devel.cards.presentation.theme.OmniCardsTheme
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
private lateinit var db: DbHelper
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
db = DbHelper(this, null)
setContent { setContent {
WearApp("Android") App(db.getCards())
} }
} }
} }
@Composable @Composable
fun WearApp(greetingName: String) { fun App(cards: List<CardInfo>) {
OmniCardsTheme { BoxWithConstraints(modifier = Modifier.fillMaxSize()) {
AppScaffold { val context = LocalContext.current
val listState = rememberTransformingLazyColumnState()
val transformationSpec = rememberTransformationSpec()
ScreenScaffold(
scrollState = listState,
edgeButton = {
EdgeButton(
onClick = { /*TODO*/ },
colors =
ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.secondaryContainer,
contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
),
) {
Text("More")
}
},
) { contentPadding -> // ScreenScaffold provides default padding; adjust as needed
TransformingLazyColumn(contentPadding = contentPadding, state = listState) {
item {
ListHeader(
modifier =
Modifier.fillMaxWidth().transformedHeight(this, transformationSpec),
transformation = SurfaceTransformation(transformationSpec),
) {
Text(text = stringResource(R.string.hello_world, greetingName))
}
}
item {
Button(
onClick = { /*TODO*/ },
modifier = Modifier.fillMaxWidth()
.transformedHeight(this, transformationSpec),
transformation = SurfaceTransformation(transformationSpec),
) {
Text("Button A")
}
}
item {
Button(
onClick = { /*TODO*/ },
modifier = Modifier.fillMaxWidth()
.transformedHeight(this, transformationSpec),
transformation = SurfaceTransformation(transformationSpec),
) {
Text("Button B")
}
}
item {
Button(
onClick = { /*TODO*/ },
modifier = Modifier.fillMaxWidth()
.transformedHeight(this, transformationSpec),
transformation = SurfaceTransformation(transformationSpec),
) {
Text("Button C")
}
}
TransformingLazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(
top = maxHeight * 0.4f,
bottom = maxHeight * 0.4f
),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
if (cards.isEmpty()) {
item {
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
val intent = Intent(context, ShowCardActivity::class.java)
intent.putExtra("cardId", 1)
context.startActivity(intent)
}
) {
Text("Debug")
}
}
item {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text("Sync with mobile APP to get started")
}
}
} else {
for (card in cards) {
item {
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
val intent = Intent(context, ShowCardActivity::class.java)
intent.putExtra("cardId", card.id)
context.startActivity(intent)
}
) {
Text(
text = card.name,
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center
)
}
}
} }
} }
} }
} }
} }
@WearPreviewDevices
@WearPreviewFontScales
@Composable
fun DefaultPreview() {
WearApp("Preview Android")
}
@@ -0,0 +1,43 @@
package ru.omni_devel.cards.presentation
import android.content.Intent
import android.os.Handler
import android.os.Looper
import android.widget.Toast
import com.google.android.gms.wearable.MessageEvent
import com.google.android.gms.wearable.WearableListenerService
import kotlinx.serialization.json.Json
class MessagesReceiver : WearableListenerService() {
private lateinit var db: DbHelper
override fun onCreate() {
super.onCreate()
db = DbHelper(this, null)
}
override fun onMessageReceived(event: MessageEvent) {
when (event.path) {
"/updateCards" -> {
val json = String(event.data, Charsets.UTF_8)
val cards = Json.decodeFromString<List<CardInfo>>(json)
Toast.makeText(this, "Updating database...", Toast.LENGTH_SHORT).show()
db.clearDatabase()
db.addCards(cards)
Handler(Looper.getMainLooper()).post {
Toast.makeText(this, "Success!", 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)
}
}
}
}
}
@@ -0,0 +1,93 @@
package ru.omni_devel.cards.presentation
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.WindowManager
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
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.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalDensity
class ShowCardActivity : ComponentActivity() {
private lateinit var db: DbHelper
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
Handler(Looper.getMainLooper()).postDelayed({
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}, 15_000L)
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 {
CardView(cardInfo)
}
}
override fun onResume() {
super.onResume()
setBrightness(1.0f)
}
override fun onPause() {
super.onPause()
setBrightness(WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE)
}
private fun setBrightness(level: Float) {
val params = window.attributes
params.screenBrightness = level
window.attributes = params
}
}
@Composable
fun CardView(cardInfo: CardInfo) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
BoxWithConstraints(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
val density = LocalDensity.current
val widthPx = with(density) { maxWidth.roundToPx() }
val heightPx = with(density) { maxHeight.roundToPx() }
Image(
bitmap = generateCode(cardInfo.codeValue, cardInfo.codeType, widthPx, heightPx).asImageBitmap(),
contentDescription = "Card code"
)
}
}
}
@@ -0,0 +1,13 @@
package ru.omni_devel.cards.presentation
import android.graphics.Bitmap
import com.google.zxing.BarcodeFormat
import com.google.zxing.MultiFormatWriter
import com.journeyapps.barcodescanner.BarcodeEncoder
fun generateCode(value: String, format: BarcodeFormat, width: Int, height: Int): Bitmap {
val writer = MultiFormatWriter()
val matrix = writer.encode(value, format, width, height)
return BarcodeEncoder().createBitmap(matrix)
}
@@ -0,0 +1,21 @@
<!--
~ Copyright (C) 2026 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>
@@ -0,0 +1,21 @@
<!--
~ Copyright (C) 2026 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 982 B

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 20 KiB

+33
View File
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Backgrounds -->
<color name="bg_app">#0B0E17</color>
<color name="bg_surface">#0E1118</color>
<color name="bg_card">#131726</color>
<color name="bg_elevated">#1A2235</color>
<color name="bg_hover">#212B42</color>
<!-- Accent (indigo) -->
<color name="accent_subtle">#152045</color>
<color name="accent_primary">#2952C8</color>
<color name="accent_hover">#3D6AE8</color>
<color name="accent_light">#6B90FF</color>
<!-- Text -->
<color name="text_primary">#DCE3F5</color>
<color name="text_secondary">#8A9BBC</color>
<color name="text_hint">#4E5E80</color>
<!-- Borders -->
<color name="border_subtle">#1C2030</color>
<color name="border_default">#1C2A42</color>
<color name="border_active">#2A3D60</color>
<!-- Semantic -->
<color name="success_bg">#0F3320</color>
<color name="success">#2ECC71</color>
<color name="error_bg">#3D1515</color>
<color name="error">#E05555</color>
<color name="warning_bg">#3D2E00</color>
<color name="warning">#F0A500</color>
</resources>
@@ -0,0 +1,19 @@
<!--
~ Copyright (C) 2026 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#000000</color>
</resources>
+5 -3
View File
@@ -1,8 +1,10 @@
<resources> <resources>
<style name="MainActivityTheme.Starting" parent="Theme.SplashScreen"> <style name="MainActivityTheme.Starting" parent="Theme.SplashScreen">
<item name="windowSplashScreenBackground">@android:color/black</item> <item name="android:windowBackground">@color/bg_app</item>
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_icon</item> <item name="android:textColorPrimary">@color/text_primary</item>
<item name="postSplashScreenTheme">@android:style/Theme.DeviceDefault</item> <item name="android:textColorSecondary">@color/text_secondary</item>
<item name="android:textColorHint">@color/text_hint</item>
<item name="android:colorBackground">@color/bg_app</item>
</style> </style>
</resources> </resources>