refactor: replaced bottom actions list with floating action button

This commit is contained in:
2026-06-23 11:53:30 +03:00
parent 3cf6b876f9
commit 13d6ba0b52
4 changed files with 103 additions and 93 deletions
@@ -3,6 +3,12 @@ package ru.omni_devel.cards
import android.content.Intent import android.content.Intent
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.activity.compose.LocalActivity 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.BorderStroke
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
@@ -29,9 +35,12 @@ import androidx.compose.material3.DrawerValue
import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon 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.QrCode
import androidx.compose.material.icons.outlined.DensityMedium import androidx.compose.material.icons.outlined.DensityMedium
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalDrawerSheet import androidx.compose.material3.ModalDrawerSheet
import androidx.compose.material3.ModalNavigationDrawer import androidx.compose.material3.ModalNavigationDrawer
@@ -50,6 +59,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@@ -60,80 +70,76 @@ import androidx.core.graphics.toColorInt
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@Composable @Composable
fun Page(name: String, innerPadding: PaddingValues, pages: List<NavigationPageData>? = null, content: @Composable () -> Unit) { fun Page(
val activityContext = LocalActivity.current name: String,
innerPadding: PaddingValues,
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed) floatingActionButton: @Composable (() -> Unit) = {},
val scope = rememberCoroutineScope() content: @Composable () -> Unit
) {
BackHandler( Scaffold(
enabled = pages != null && drawerState.isOpen floatingActionButton = floatingActionButton
) { ) { padding ->
scope.launch { Box(
drawerState.close() 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( @Composable
drawerState = drawerState, fun ExpandableFab(
drawerContent = { items: List<NavigationPageData>
if (pages != null) { ) {
ModalDrawerSheet( val activityContext = LocalActivity.current
modifier = Modifier.background(MaterialTheme.colorScheme.background).padding(8.dp) var isExpanded by remember { mutableStateOf(false) }
) {
for (page in pages) {
NavigationDrawerItem(
label = { Text(page.name) },
selected = false,
onClick = {
scope.launch { drawerState.close() }
if (page.pageClass != null) { val rotation by animateFloatAsState(if (isExpanded) 45f else 0f)
val intent = Intent(activityContext, page.pageClass)
activityContext!!.startActivity(intent) Column(
} else if (page.onClick != null) { horizontalAlignment = Alignment.End,
page.onClick() 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( FloatingActionButton(
topBar = { onClick = { isExpanded = !isExpanded }
TopBar(name, innerPadding, if (pages != null) { ) {
{ Icon(
Icon( modifier = Modifier.graphicsLayer { rotationZ = rotation },
modifier = Modifier.clickable() { imageVector = Icons.Outlined.Add,
scope.launch { contentDescription = null
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()
}
}
} }
} }
} }
@@ -5,31 +5,28 @@ import android.widget.Toast
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons
import androidx.compose.foundation.verticalScroll 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.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateListOf
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import ru.omni_devel.cards.ui.theme.OmniCardsTheme import ru.omni_devel.cards.ui.theme.OmniCardsTheme
private const val ANIMATION_DELAY = 300L
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
private lateinit var db: DbHelper private lateinit var db: DbHelper
@@ -81,24 +78,27 @@ fun MainPage(
Page( Page(
stringResource(R.string.app_name), stringResource(R.string.app_name),
innerPadding, innerPadding,
pages = listOf( floatingActionButton = {
NavigationPageData(stringResource(R.string.do_add_card), EditCardActivity::class.java), ExpandableFab(
NavigationPageData(stringResource(R.string.do_sync)) { items = listOf(
Toast.makeText(context, context.getString(R.string.sync_in_progress), Toast.LENGTH_SHORT).show() NavigationPageData(stringResource(R.string.do_add_card), Icons.Outlined.Add, EditCardActivity::class.java),
NavigationPageData(stringResource(R.string.do_sync), Icons.Outlined.Sync) {
sendToWatch(context, "/updateCards", Json.encodeToString(getDbFun().getCards())) { isSuccess -> Toast.makeText(context, context.getString(R.string.sync_in_progress), Toast.LENGTH_SHORT).show()
if (isSuccess) { sendToWatch(context, "/updateCards", Json.encodeToString(getDbFun().getCards())) { isSuccess ->
Toast.makeText(context, context.getString(R.string.sync_is_successful), Toast.LENGTH_SHORT).show() if (isSuccess) {
} else { Toast.makeText(context, context.getString(R.string.sync_is_successful), Toast.LENGTH_SHORT).show()
Toast.makeText(context, context.getString(R.string.sync_is_fail), Toast.LENGTH_LONG).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)) { NavigationPageData(stringResource(R.string.open_backup_menu), Icons.Outlined.Backup, BackupActivity::class.java),
uriHandler.openUri("https://github.com/omni-devel/OmniCards") NavigationPageData(stringResource(R.string.source_code), Icons.Outlined.Code) {
}, uriHandler.openUri("https://github.com/omni-devel/OmniCards")
) },
)
)
}
) { ) {
LazyColumn( LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp) verticalArrangement = Arrangement.spacedBy(8.dp)
@@ -1,9 +1,11 @@
package ru.omni_devel.cards package ru.omni_devel.cards
import android.app.Activity import android.app.Activity
import androidx.compose.ui.graphics.vector.ImageVector
data class NavigationPageData( data class NavigationPageData(
val name: String, val name: String,
val icon: ImageVector,
val pageClass: Class<out Activity>? = null, val pageClass: Class<out Activity>? = null,
val onClick: (() -> Unit)? = null val onClick: (() -> Unit)? = null
) )
@@ -4,6 +4,7 @@ import android.annotation.SuppressLint
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.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints 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.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.wear.compose.foundation.lazy.TransformingLazyColumn import androidx.wear.compose.foundation.lazy.TransformingLazyColumn
import androidx.wear.compose.material3.MaterialTheme
import androidx.wear.compose.material3.Text import androidx.wear.compose.material3.Text
import ru.omni_devel.cards.R import ru.omni_devel.cards.R
@@ -35,7 +37,7 @@ class MainActivity : ComponentActivity() {
@SuppressLint("UnusedBoxWithConstraintsScope") @SuppressLint("UnusedBoxWithConstraintsScope")
@Composable @Composable
fun App(cards: List<CardInfo>) { fun App(cards: List<CardInfo>) {
BoxWithConstraints(modifier = Modifier.fillMaxSize()) { BoxWithConstraints(modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background)) {
val topBottomPadding = maxHeight * 0.4f val topBottomPadding = maxHeight * 0.4f
if (cards.isEmpty()) { if (cards.isEmpty()) {