feat: added companion app for WearOS, and setted up icons
@@ -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")
|
||||||
}
|
}
|
||||||
|
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>
|
|
||||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 982 B After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
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>
|
||||||
@@ -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,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.
|
||||||
|
|||||||
|
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()
|
TransformingLazyColumn(
|
||||||
ScreenScaffold(
|
modifier = Modifier.fillMaxSize(),
|
||||||
scrollState = listState,
|
contentPadding = PaddingValues(
|
||||||
edgeButton = {
|
top = maxHeight * 0.4f,
|
||||||
EdgeButton(
|
bottom = maxHeight * 0.4f
|
||||||
onClick = { /*TODO*/ },
|
|
||||||
colors =
|
|
||||||
ButtonDefaults.buttonColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.secondaryContainer,
|
|
||||||
contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
|
|
||||||
),
|
),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
Text("More")
|
if (cards.isEmpty()) {
|
||||||
}
|
|
||||||
},
|
|
||||||
) { 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 {
|
item {
|
||||||
Button(
|
Button(
|
||||||
onClick = { /*TODO*/ },
|
modifier = Modifier.fillMaxWidth(),
|
||||||
modifier = Modifier.fillMaxWidth()
|
onClick = {
|
||||||
.transformedHeight(this, transformationSpec),
|
val intent = Intent(context, ShowCardActivity::class.java)
|
||||||
transformation = SurfaceTransformation(transformationSpec),
|
|
||||||
|
intent.putExtra("cardId", 1)
|
||||||
|
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
Text("Button A")
|
Text("Debug")
|
||||||
}
|
|
||||||
}
|
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
@WearPreviewDevices
|
intent.putExtra("cardId", card.id)
|
||||||
@WearPreviewFontScales
|
|
||||||
@Composable
|
context.startActivity(intent)
|
||||||
fun DefaultPreview() {
|
}
|
||||||
WearApp("Preview Android")
|
) {
|
||||||
|
Text(
|
||||||
|
text = card.name,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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>
|
|
||||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 982 B After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 20 KiB |
@@ -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>
|
||||||
@@ -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>
|
||||||