diff --git a/mobile/build.gradle.kts b/mobile/build.gradle.kts index 3362026..a60c4d9 100644 --- a/mobile/build.gradle.kts +++ b/mobile/build.gradle.kts @@ -23,7 +23,7 @@ android { minSdk = 30 targetSdk = 36 versionCode = generateVersionCode() - versionName = "2.5.1" + versionName = "4.0.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } @@ -77,4 +77,5 @@ dependencies { implementation("com.google.android.gms:play-services-wearable:18.1.0") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0") implementation(libs.compose.colorpicker) + implementation("org.burnoutcrew.composereorderable:reorderable:0.9.6") } \ No newline at end of file diff --git a/mobile/release/baselineProfiles/0/mobile-release.dm b/mobile/release/baselineProfiles/0/mobile-release.dm index 487162c..d7f4484 100644 Binary files a/mobile/release/baselineProfiles/0/mobile-release.dm and b/mobile/release/baselineProfiles/0/mobile-release.dm differ diff --git a/mobile/release/baselineProfiles/1/mobile-release.dm b/mobile/release/baselineProfiles/1/mobile-release.dm index b5828ea..1c65a70 100644 Binary files a/mobile/release/baselineProfiles/1/mobile-release.dm and b/mobile/release/baselineProfiles/1/mobile-release.dm differ diff --git a/mobile/src/main/java/ru/omni_devel/cards/CardInfo.kt b/mobile/src/main/java/ru/omni_devel/cards/CardInfo.kt index eebe6d3..c321a96 100644 --- a/mobile/src/main/java/ru/omni_devel/cards/CardInfo.kt +++ b/mobile/src/main/java/ru/omni_devel/cards/CardInfo.kt @@ -14,5 +14,5 @@ class CardInfo ( val name: String, val codeType: BarcodeFormat, val codeValue: String, - val color: String?, + val color: String? ) {} diff --git a/mobile/src/main/java/ru/omni_devel/cards/DbHelper.kt b/mobile/src/main/java/ru/omni_devel/cards/DbHelper.kt index 0b67f0d..f8843fd 100644 --- a/mobile/src/main/java/ru/omni_devel/cards/DbHelper.kt +++ b/mobile/src/main/java/ru/omni_devel/cards/DbHelper.kt @@ -6,9 +6,9 @@ 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, 5) { 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)") } override fun onUpgrade( @@ -19,9 +19,27 @@ class DbHelper(val context: Context, val factory: SQLiteDatabase.CursorFactory?) 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") + } + + if (oldVersion < 4) { + db!!.execSQL("ALTER TABLE cards ADD COLUMN monochrome INTEGER DEFAULT 0") + } + + if (oldVersion < 5) { + db!!.execSQL("ALTER TABLE cards DROP COLUMN iconPath") + db.execSQL("ALTER TABLE cards DROP COLUMN monochrome") + db.execSQL("ALTER TABLE cards DROP COLUMN position") + } } fun addCard(name: String, codeValue: String, codeType: BarcodeFormat, color: String?) { + val db = this.writableDatabase + val values = ContentValues() values.put("name", name) @@ -29,8 +47,6 @@ class DbHelper(val context: Context, val factory: SQLiteDatabase.CursorFactory?) values.put("codeType", codeType.name) values.put("color", color) - val db = this.writableDatabase - db.insert("cards", null, values) db.close() @@ -54,7 +70,7 @@ class DbHelper(val context: Context, val factory: SQLiteDatabase.CursorFactory?) fun getCards(): List { val db = this.readableDatabase - val cursor = db.rawQuery("SELECT * FROM cards", null) + val cursor = db.rawQuery("SELECT * FROM cards ORDER BY id ASC", null) val cards = mutableListOf() while (cursor.moveToNext()) { diff --git a/mobile/src/main/java/ru/omni_devel/cards/Elements.kt b/mobile/src/main/java/ru/omni_devel/cards/Elements.kt index b008911..1fca4ed 100644 --- a/mobile/src/main/java/ru/omni_devel/cards/Elements.kt +++ b/mobile/src/main/java/ru/omni_devel/cards/Elements.kt @@ -1,7 +1,10 @@ package ru.omni_devel.cards import android.content.Intent +import androidx.activity.compose.BackHandler +import androidx.activity.compose.LocalActivity import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -15,19 +18,28 @@ 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.shape.RoundedCornerShape import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.DrawerValue import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalDrawerSheet +import androidx.compose.material3.ModalNavigationDrawer +import androidx.compose.material3.NavigationDrawerItem +import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text +import androidx.compose.material3.rememberDrawerState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext @@ -36,36 +48,103 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.graphics.toColorInt +import kotlinx.coroutines.launch @Composable -fun Page(name: String, innerPadding: PaddingValues, content: @Composable () -> Unit) { - Column() { - TopBar(name, innerPadding) +fun Page(name: String, innerPadding: PaddingValues, pages: List? = null, content: @Composable () -> Unit) { + val activityContext = LocalActivity.current - Column( - modifier = Modifier - .fillMaxSize() - .padding(horizontal = 16.dp) - .background(MaterialTheme.colorScheme.background) - ) { - content() + val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed) + val scope = rememberCoroutineScope() + + BackHandler( + enabled = pages != null && drawerState.isOpen + ) { + scope.launch { + drawerState.close() + } + } + + ModalNavigationDrawer( + drawerState = drawerState, + drawerContent = { + if (pages != null) { + ModalDrawerSheet( + modifier = Modifier.background(MaterialTheme.colorScheme.background).padding(8.dp) + ) { + for (page in pages) { + NavigationDrawerItem( + label = { Text(page.name) }, + selected = false, + onClick = { + scope.launch { drawerState.close() } + + if (page.pageClass != null) { + val intent = Intent(activityContext, page.pageClass) + + activityContext!!.startActivity(intent) + } else if (page.onClick != null) { + page.onClick() + } + } + ) + } + } + } + } + ) { + Scaffold( + topBar = { + TopBar(name, innerPadding, if (pages != null) { + { + Text( + "☰", + fontSize = 24.sp, + color = MaterialTheme.colorScheme.onPrimaryContainer, + modifier = Modifier.clickable() { + scope.launch { + drawerState.open() + } + } + ) + } + } else null) + } + ) { padding -> + Box( + modifier = Modifier + .padding(padding) + .fillMaxSize() + .background(MaterialTheme.colorScheme.background) + .padding(horizontal = 16.dp) + .padding(top = 16.dp) + ) { + Column() { + content() + } + } } } } @Composable -fun TopBar(text: String, innerPadding: PaddingValues) { +fun TopBar(text: String, innerPadding: PaddingValues, openMenuButton: (@Composable () -> Unit)? = null) { Row( modifier = Modifier .fillMaxWidth() .background(MaterialTheme.colorScheme.primaryContainer) .padding(top = innerPadding.calculateTopPadding()) - .padding(horizontal = 8.dp) - .padding(bottom = 8.dp) + .padding(vertical = 12.dp, horizontal = 12.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) ) { + if (openMenuButton != null) { + openMenuButton() + } + Text( text, - modifier = Modifier.padding(vertical = 4.dp, horizontal = 12.dp), + modifier = Modifier, fontSize = 24.sp, color = MaterialTheme.colorScheme.onPrimaryContainer ) @@ -93,7 +172,7 @@ fun CardButton(cardInfo: CardInfo, onDeleteCard: (Int) -> Unit) { } ) }, - shape = ButtonDefaults.shape, + shape = RoundedCornerShape(12.dp), color = MaterialTheme.colorScheme.primaryContainer, tonalElevation = 2.dp ) { diff --git a/mobile/src/main/java/ru/omni_devel/cards/MainActivity.kt b/mobile/src/main/java/ru/omni_devel/cards/MainActivity.kt index 7e407a5..eb69469 100644 --- a/mobile/src/main/java/ru/omni_devel/cards/MainActivity.kt +++ b/mobile/src/main/java/ru/omni_devel/cards/MainActivity.kt @@ -1,52 +1,29 @@ package ru.omni_devel.cards -import android.content.Intent import android.os.Bundle import android.widget.Toast import androidx.activity.ComponentActivity -import androidx.activity.compose.BackHandler import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.scaleIn -import androidx.compose.animation.scaleOut -import androidx.compose.animation.slideInVertically -import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.Button -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateListOf -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch import kotlinx.serialization.json.Json import ru.omni_devel.cards.ui.theme.OmniCardsTheme @@ -96,25 +73,34 @@ fun MainPage( ) { val context = LocalContext.current val uriHandler = LocalUriHandler.current - val scope = rememberCoroutineScope() - - var isActionBarShowed by remember { mutableStateOf(false) } Box( modifier = Modifier.fillMaxSize() ) { - BackHandler( - enabled = isActionBarShowed, - ) { - isActionBarShowed = false - } - Page( stringResource(R.string.app_name), - innerPadding + innerPadding, + pages = listOf( + NavigationPageData(stringResource(R.string.do_add_card), EditCardActivity::class.java), + NavigationPageData(stringResource(R.string.do_sync)) { + Toast.makeText(context, context.getString(R.string.sync_in_progress), Toast.LENGTH_SHORT).show() + + sendToWatch(context, "/updateCards", Json.encodeToString(getDbFun().getCards())) { isSuccess -> + if (isSuccess) { + Toast.makeText(context, context.getString(R.string.sync_is_successful), Toast.LENGTH_SHORT).show() + } else { + Toast.makeText(context, context.getString(R.string.sync_is_fail), Toast.LENGTH_LONG).show() + } + } + }, + NavigationPageData(stringResource(R.string.open_backup_menu), BackupActivity::class.java), + NavigationPageData(stringResource(R.string.source_code)) { + uriHandler.openUri("https://github.com/omni-devel/OmniCards") + }, + ) ) { Column( - modifier = Modifier.verticalScroll(rememberScrollState()).padding(bottom = 64.dp).background(Color.Transparent).padding(top = 16.dp), + modifier = Modifier.verticalScroll(rememberScrollState()).padding(bottom = 64.dp).background(Color.Transparent), verticalArrangement = Arrangement.spacedBy(8.dp) ) { if (cards.isEmpty()) { @@ -128,125 +114,5 @@ fun MainPage( } } } - - if (isActionBarShowed) { - Box( - modifier = Modifier - .fillMaxSize() - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = null - ) { - isActionBarShowed = false - } - ) { } - } - - AnimatedVisibility( - modifier = Modifier - .align(Alignment.BottomEnd) - .padding(innerPadding) - .padding(16.dp), - visible = isActionBarShowed, - enter = slideInVertically(initialOffsetY = { it }) + fadeIn(), - exit = slideOutVertically(targetOffsetY = { it }) + fadeOut() - ) { - Column( - modifier = Modifier - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = null - ) {} - .background( - MaterialTheme.colorScheme.inversePrimary, - RoundedCornerShape(24.dp) - ) - .padding(16.dp) - .fillMaxWidth() - ) { - FullWidthButton( - onClick = { - isActionBarShowed = false - - scope.launch { - delay(ANIMATION_DELAY) - - val intent = Intent(context, EditCardActivity::class.java) - - context.startActivity(intent) - } - } - ) { - Text(stringResource(R.string.do_add_card)) - } - FullWidthButton( - onClick = { - Toast.makeText(context, context.getString(R.string.sync_in_progress), Toast.LENGTH_SHORT).show() - - sendToWatch(context, "/updateCards", Json.encodeToString(getDbFun().getCards())) { isSuccess -> - if (isSuccess) { - Toast.makeText(context, context.getString(R.string.sync_is_successful), Toast.LENGTH_SHORT).show() - } else { - Toast.makeText(context, context.getString(R.string.sync_is_fail), Toast.LENGTH_LONG).show() - } - } - - isActionBarShowed = false - } - ) { - Text(stringResource(R.string.do_sync)) - } - FullWidthButton( - onClick = { - isActionBarShowed = false - - scope.launch { - delay(ANIMATION_DELAY) - - val intent = Intent(context, BackupActivity::class.java) - - context.startActivity(intent) - } - } - ) { - Text(stringResource(R.string.open_backup_menu)) - } - FullWidthButton( - onClick = { - uriHandler.openUri("https://github.com/omni-devel/OmniCards") - - isActionBarShowed = false - } - ) { - Text(stringResource(R.string.source_code)) - } - FullWidthButton( - onClick = { - isActionBarShowed = false - } - ) { - Text(">") - } - } - } - - AnimatedVisibility( - modifier = Modifier - .align(Alignment.BottomEnd) - .padding(innerPadding) - .padding(32.dp), - visible = !isActionBarShowed, - enter = fadeIn() + scaleIn(), - exit = fadeOut() + scaleOut() - ){ - Button( - modifier = Modifier, - onClick = { - isActionBarShowed = true - } - ) { - Text("<") - } - } } } diff --git a/mobile/src/main/java/ru/omni_devel/cards/NavigationPageData.kt b/mobile/src/main/java/ru/omni_devel/cards/NavigationPageData.kt new file mode 100644 index 0000000..d504f93 --- /dev/null +++ b/mobile/src/main/java/ru/omni_devel/cards/NavigationPageData.kt @@ -0,0 +1,9 @@ +package ru.omni_devel.cards + +import android.app.Activity + +data class NavigationPageData( + val name: String, + val pageClass: Class? = null, + val onClick: (() -> Unit)? = null +) diff --git a/wear/build.gradle.kts b/wear/build.gradle.kts index 385a844..ba3b8a7 100644 --- a/wear/build.gradle.kts +++ b/wear/build.gradle.kts @@ -23,7 +23,7 @@ android { minSdk = 30 targetSdk = 36 versionCode = generateVersionCode() - versionName = "1.4.0" + versionName = "3.0.0" } buildTypes {