From 13d6ba0b52c8c56ae281cf59479803b0b2fbb007 Mon Sep 17 00:00:00 2001 From: Omni Devel Date: Tue, 23 Jun 2026 11:53:30 +0300 Subject: [PATCH] refactor: replaced bottom actions list with floating action button --- .../main/java/ru/omni_devel/cards/Elements.kt | 138 +++++++++--------- .../java/ru/omni_devel/cards/MainActivity.kt | 52 +++---- .../ru/omni_devel/cards/NavigationPageData.kt | 2 + .../cards/presentation/MainActivity.kt | 4 +- 4 files changed, 103 insertions(+), 93 deletions(-) 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 483aaa2..7fc994c 100644 --- a/mobile/src/main/java/ru/omni_devel/cards/Elements.kt +++ b/mobile/src/main/java/ru/omni_devel/cards/Elements.kt @@ -3,6 +3,12 @@ package ru.omni_devel.cards import android.content.Intent import androidx.activity.compose.BackHandler import androidx.activity.compose.LocalActivity +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -29,9 +35,12 @@ import androidx.compose.material3.DrawerValue import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.Icon +import androidx.compose.material.icons.outlined.Add import androidx.compose.material.icons.outlined.QrCode import androidx.compose.material.icons.outlined.DensityMedium import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExtendedFloatingActionButton +import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalDrawerSheet import androidx.compose.material3.ModalNavigationDrawer @@ -50,6 +59,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource @@ -60,80 +70,76 @@ import androidx.core.graphics.toColorInt import kotlinx.coroutines.launch @Composable -fun Page(name: String, innerPadding: PaddingValues, pages: List? = null, content: @Composable () -> Unit) { - val activityContext = LocalActivity.current - - val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed) - val scope = rememberCoroutineScope() - - BackHandler( - enabled = pages != null && drawerState.isOpen - ) { - scope.launch { - drawerState.close() +fun Page( + name: String, + innerPadding: PaddingValues, + floatingActionButton: @Composable (() -> Unit) = {}, + content: @Composable () -> Unit +) { + Scaffold( + floatingActionButton = floatingActionButton + ) { padding -> + Box( + modifier = Modifier + .padding(padding) + .fillMaxSize() + .background(MaterialTheme.colorScheme.background) + .padding(horizontal = 16.dp) + .padding(top = 16.dp) + ) { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + content() + } } } +} - 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() } +@Composable +fun ExpandableFab( + items: List +) { + val activityContext = LocalActivity.current + var isExpanded by remember { mutableStateOf(false) } - if (page.pageClass != null) { - val intent = Intent(activityContext, page.pageClass) + val rotation by animateFloatAsState(if (isExpanded) 45f else 0f) - activityContext!!.startActivity(intent) - } else if (page.onClick != null) { - page.onClick() - } - } - ) + Column( + horizontalAlignment = Alignment.End, + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + items.forEach { page -> + AnimatedVisibility( + visible = isExpanded, + enter = fadeIn() + scaleIn(), + exit = fadeOut() + scaleOut() + ) { + ExtendedFloatingActionButton( + text = { Text(page.name) }, + icon = { Icon(page.icon, contentDescription = null) }, + onClick = { + isExpanded = false + + 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) { - { - Icon( - modifier = Modifier.clickable() { - scope.launch { - drawerState.open() - } - }, - imageVector = Icons.Outlined.DensityMedium, - contentDescription = null, - tint = MaterialTheme.colorScheme.onPrimaryContainer - ) - } - } else null) - } - ) { padding -> - Box( - modifier = Modifier - .padding(padding) - .fillMaxSize() - .background(MaterialTheme.colorScheme.background) - .padding(horizontal = 16.dp) - .padding(top = 16.dp) - ) { - Column( - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - content() - } - } + + FloatingActionButton( + onClick = { isExpanded = !isExpanded } + ) { + Icon( + modifier = Modifier.graphicsLayer { rotationZ = rotation }, + imageVector = Icons.Outlined.Add, + contentDescription = null + ) } } } 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 96c9ef2..077c8e4 100644 --- a/mobile/src/main/java/ru/omni_devel/cards/MainActivity.kt +++ b/mobile/src/main/java/ru/omni_devel/cards/MainActivity.kt @@ -5,31 +5,28 @@ import android.widget.Toast import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Add +import androidx.compose.material.icons.outlined.Sync +import androidx.compose.material.icons.outlined.Backup +import androidx.compose.material.icons.outlined.Code import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.compose.runtime.mutableStateListOf -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.serialization.json.Json import ru.omni_devel.cards.ui.theme.OmniCardsTheme -private const val ANIMATION_DELAY = 300L - class MainActivity : ComponentActivity() { private lateinit var db: DbHelper @@ -81,24 +78,27 @@ fun MainPage( Page( stringResource(R.string.app_name), 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") - }, - ) + floatingActionButton = { + ExpandableFab( + items = listOf( + NavigationPageData(stringResource(R.string.do_add_card), Icons.Outlined.Add, EditCardActivity::class.java), + NavigationPageData(stringResource(R.string.do_sync), Icons.Outlined.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), Icons.Outlined.Backup, BackupActivity::class.java), + NavigationPageData(stringResource(R.string.source_code), Icons.Outlined.Code) { + uriHandler.openUri("https://github.com/omni-devel/OmniCards") + }, + ) + ) + } ) { LazyColumn( verticalArrangement = Arrangement.spacedBy(8.dp) diff --git a/mobile/src/main/java/ru/omni_devel/cards/NavigationPageData.kt b/mobile/src/main/java/ru/omni_devel/cards/NavigationPageData.kt index d504f93..4e70743 100644 --- a/mobile/src/main/java/ru/omni_devel/cards/NavigationPageData.kt +++ b/mobile/src/main/java/ru/omni_devel/cards/NavigationPageData.kt @@ -1,9 +1,11 @@ package ru.omni_devel.cards import android.app.Activity +import androidx.compose.ui.graphics.vector.ImageVector data class NavigationPageData( val name: String, + val icon: ImageVector, val pageClass: Class? = null, val onClick: (() -> Unit)? = null ) diff --git a/wear/src/main/java/ru/omni_devel/cards/presentation/MainActivity.kt b/wear/src/main/java/ru/omni_devel/cards/presentation/MainActivity.kt index eeb1a77..6f1f8aa 100644 --- a/wear/src/main/java/ru/omni_devel/cards/presentation/MainActivity.kt +++ b/wear/src/main/java/ru/omni_devel/cards/presentation/MainActivity.kt @@ -4,6 +4,7 @@ import android.annotation.SuppressLint import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints @@ -15,6 +16,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.wear.compose.foundation.lazy.TransformingLazyColumn +import androidx.wear.compose.material3.MaterialTheme import androidx.wear.compose.material3.Text import ru.omni_devel.cards.R @@ -35,7 +37,7 @@ class MainActivity : ComponentActivity() { @SuppressLint("UnusedBoxWithConstraintsScope") @Composable fun App(cards: List) { - BoxWithConstraints(modifier = Modifier.fillMaxSize()) { + BoxWithConstraints(modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background)) { val topBottomPadding = maxHeight * 0.4f if (cards.isEmpty()) {