I have simple animation by AnimatedVisibility (slideInVertically/SlideOut).
When i press "nextScreenButton" i make new navigate by navController.
The transition is done instantly, so no time for exit animation.
How to make waiting until animations ends
I can enter some delay for animation time, but its not good way.
Code:
Scaffold() {
AnimatedVisibility(
//Boolean State for open animation
OpenChooseProfilePageAnim.value,
initiallyVisible= false,
enter = slideInVertically(
initialOffsetY = { fullHeight -> fullHeight },
animationSpec = tween(
durationMillis = 3000,
easing = LinearOutSlowInEasing
)
),
exit = slideOutVertically(
targetOffsetY = { fullHeight -> fullHeight },
animationSpec = tween(
durationMillis = 3000,
easing = LinearOutSlowInEasing
)
)
) {
ConstraintLayout() {
Card() {
Column() {
//Some other Composable items
//Composable button
NextScreenButton() {
//calling navigation here
}
}
}
}
}
}
Ty for help.
NextScreenButton code:
fun navigateToMainListPage(navController: NavController) {
//change State of visibility for "AnimatedVisibility"
AnimationsState.OpenChooseProfilePageAnim.value = false
//navigate to another route in NavHost
navController.navigate(ROUTE_MAIN_LIST)
}
NavHost:
#Composable
fun LoginGroupNavigation(startDestination: String) {
val navController = rememberNavController()
NavHost(navController, startDestination = startDestination) {
composable(LoginScreens.LoginScreen.route) {
LoginMainPage(navController)
}
composable(LoginScreens.EnteringPhoneNumScreen.route,
arguments = listOf(navArgument("title") { type = NavType.StringType },
)) {
val title = it.arguments?.getString("title") ?: ""
EnterPhoneNumberForSmsPage(
navController = navController,
title
)
}
//more composable screens
Here is the main idea to do this:
val animVisibleState = remember { MutableTransitionState(false) }
.apply { targetState = true }
//Note: Once the exit transition is finished,
//the content composable will be removed from the tree,
//and disposed.
//Both currentState and targetState will be false for
//visibleState.
if (!animVisibleState.targetState &&
!animVisibleState.currentState
) {
//navigate to another route in NavHost
navController.navigate(ROUTE_MAIN_LIST)
return
}
AnimatedVisibility(
visibleState = animVisibleState,
enter = fadeIn(
animationSpec = tween(durationMillis = 200)
),
exit = fadeOut(
animationSpec = tween(durationMillis = 200)
)
) {
NextButton() {
//start exit animation
animVisibleState.targetState = false
}
}
to answer your question: "how to wait for animation ends".
just check if the transition.targetState is the same as transition.currentState.
if it is the same, then the animation have ended.
AnimatedVisibility(
initiallyVisible= ...,
enter = ...,
exit = ...
) {
...<your layout>
//to detect if your animation have completed just check the following
if (this.transition.currentState == this.transition.targetState){
//Animation is completed when current state is the same as target state.
//you can call whatever you like here -> e.g. start music, show toasts, enable buttons, etc
callback.invoke() //we invoke a callback as an example.
}
}
this will also work for other animations like AnimatedContent, etc
Crossfade was introduced as the default transition for navigation in Navigation 2.4.0-alpha05. With the latest release of Navigation 2.4.0-alpha06, you can add custom transitions via Accompanist
val animVisibleState = remember { MutableTransitionState(false) }
val nextButtonPressState = remember { mutableStateOf(false) }
LaunchedEffect(key1 = true) {
animVisibleState.targetState = true
}
if ( !animVisibleState.targetState &&
!animVisibleState.currentState &&
nextButtonPressState.value
) {
navController.navigate(ROUTE_MAIN_LIST)
}
AnimatedVisibility(
visibleState = animVisibleState,
enter = fadeIn(),
exit = fadeOut()
) {
NextButton() {
nextButtonPressState.value = true
}
}
.apply {animVisibleState.target = true} at the declaration of the variable will apply this on every recompositions so LaunchedEffect can be a solution, there also much better way to handle the nextButtonPressionState
Related
In my android project, I'm doing a simple Floating Action Button that can expand and show a list of buttons to perform different actions.
To track the current state of the FAB, I have the next enum class
enum class FabState {
Expanded,
Collapsed
}
For displaying the Floating Action Button, I have the following Composable function:
#Composable
fun MultiFloatingActionButton(
icon: ImageVector,
iconTint: Color = Color.White,
miniFabItems: List<MinFabItem>,
fabState: FabState, //The initial state of the FAB
onFabStateChanged: (FabState) -> Unit,
onItemClick: (MinFabItem) -> Unit
) {
val transition = updateTransition(targetState = fabState, label = "transition")
val rotate by transition.animateFloat(label = "rotate") {
when (it) {
FabState.Collapsed -> 0f
FabState.Expanded -> 315f
}
}
val fabScale by transition.animateFloat(label = "fabScale") {
when (it) {
FabState.Collapsed -> 0f
FabState.Expanded -> 1f
}
}
val alpha by transition.animateFloat(label = "alpha") {
when (it) {
FabState.Collapsed -> 0f
FabState.Expanded -> 1f
}
}
val shadow by transition.animateDp(label = "shadow", transitionSpec = { tween(50) }) { state ->
when (state) {
FabState.Expanded -> 2.dp
FabState.Collapsed -> 0.dp
}
}
Column(
horizontalAlignment = Alignment.End
) { // This is where I have my question, in the if condition
if (fabState == FabState.Expanded || transition.currentState == FabState.Expanded) {
miniFabItems.forEach { minFabItem ->
MinFab( //Composable for creating sub action buttons
fabItem = minFabItem,
alpha = alpha,
textShadow = shadow,
fabScale = fabScale,
onMinFabItemClick = {
onItemClick(minFabItem)
}
)
Spacer(modifier = Modifier.size(16.dp))
}
}
FloatingActionButton(
onClick = {
onFabStateChanged(
when (fabState) {
FabState.Expanded -> {
FabState.Collapsed
}
FabState.Collapsed -> {
FabState.Expanded
}
}
)
}
) {
Icon(
imageVector = icon,
tint = iconTint,
contentDescription = null,
modifier = Modifier.rotate(rotate)
)
}
}
}
The constants I defined are for animating the buttons that will show/hide depending on the FAB state.
When I first made the function, the original condition was giving me a different behavior, and playing around with all the posible conditions, I got 3 different results:
1st condition:
if (transition.currentState == FabState.Expanded) {...}
Result: animation not loading from collapsed to expanded, but it does from expanded to collapsed
2nd condition: if (fabState == FabState.Expanded) {...}
Result: animation loading from collapsed to expanded, but not from expanded to collapsed
3rd condition (the one I'm using right now):
if (fabState == FabState.Expanded || transition.currentState == FabState.Expanded) {...}
Result: animation loading in both ways
So my question is: how does every condition change the behavior of the animations?
Any help would be appreciated. Thanks in advance
fabState is updated as soon as onFabStateChanged is called and transition.currentState is updated when it ends the transition and transition.isRunning returns false
Animation only happens if the composable is present in the tree. When the condition is false in the if block, the elements are not available for animation.
condition 1 false during the enter perion which breaks the enter animation and condition 2 is false during the exit period which breaks the exit animation and both are false after exit. Therefore merging them solved your issue and also removes the composables from the tree when not wanted.
Better approach
AnimatedVisibility(
visible = fabState == FabState.Expanded,
enter = fadeIn()+ scaleIn(),
exit = fadeOut() + scaleOut(),
) {
miniFabItems.forEach { minFabItem ->
MinFab(
fabItem = minFabItem,
textShadow = 0.dp,
onMinFabItemClick = {
onItemClick(minFabItem)
}
)
Spacer(modifier = Modifier.size(16.dp))
}
}
And use graphicsLayer modifier to instead of rotate
Icon(
imageVector = Icons.Default.Add,
tint = Color.White,
contentDescription = null,
modifier = Modifier
.graphicsLayer {
this.rotationZ = rotate
}
)
I Have a ModalBottomSheet and I want to change a boolean value after clicking on the bottom sheet transparent background. How can I access this view click listener?
I don't want to use BottomSheetScaffold.
fun ScheduleJobFromQueuedBottomSheet(
viewModel: ProductViewModel,
onClickCancel: () -> Unit,
onQueuedScheduleRequestResultIsBack: () -> Unit,
) {
**var state by remember { mutableStateOf(false) }**
val bottomSheetState: ModalBottomSheetState =
rememberModalBottomSheetState(ModalBottomSheetValue.Expanded, confirmStateChange = {
it != ModalBottomSheetValue.HalfExpanded
}, animationSpec = TweenSpec(durationMillis = 300, delay = 10))
ModalBottomSheetLayout(
sheetState = bottomSheetState,
sheetShape = RoundedCornerShape(topStart = custom, topEnd = custom),
scrimColor = ModalBottomSheetDefaults.scrimColor.copy(alpha = 0.40f),
sheetContent = {
ReviewBookingScheduledBottomSheet(modifier = Modifier
.fillMaxWidth()
.wrapContentHeight(),
viewModel = viewModel,
onClickCancel = {
onClickCancel.invoke()
}, onScheduledRequestResultIsBack = {
onQueuedScheduleRequestResultIsBack.invoke()
})
}
) {
}
}
I want to change the state value by clicking on the bottom sheet background (Not bottomsheet content)
Add one boolean mutableState variable and change this value when your bottom sheet state change
var isBottomSheetDismissed = remember { mutableStateOf(false) }
val bottomSheetState: ModalBottomSheetState =
rememberModalBottomSheetState(ModalBottomSheetValue.Expanded, confirmStateChange = {
isBottomSheetDismissed.value = it==ModalBottomSheetValue.Hidden
it != ModalBottomSheetValue.HalfExpanded
}, animationSpec = TweenSpec(durationMillis = 300, delay = 10))
In Compose observability is preferred over callbacks, thus there isn't an explicit listener available.
We can observe the transparent background click by observing the bottomSheetState state change when hidden:
fun ScheduleJobFromQueuedBottomSheet(
viewModel: ProductViewModel,
onClickCancel: () -> Unit,
onQueuedScheduleRequestResultIsBack: () -> Unit,
) {
var state by remember { mutableStateOf(false) }
val bottomSheetState: ModalBottomSheetState =
rememberModalBottomSheetState(ModalBottomSheetValue.Expanded, confirmStateChange = {
it != ModalBottomSheetValue.HalfExpanded
}, animationSpec = TweenSpec(durationMillis = 300, delay = 10))
LaunchedEffect(bottomSheetState) {
snapshotFlow { bottomSheetState.currentValue }
.filter { it == Hidden }
.collect { state = true }
}
ModalBottomSheetLayout(
sheetState = bottomSheetState,
sheetShape = RoundedCornerShape(topStart = custom, topEnd = custom),
scrimColor = ModalBottomSheetDefaults.scrimColor.copy(alpha = 0.40f),
sheetContent = {
ReviewBookingScheduledBottomSheet(modifier = Modifier
.fillMaxWidth()
.wrapContentHeight(),
viewModel = viewModel,
onClickCancel = {
onClickCancel.invoke()
}, onScheduledRequestResultIsBack = {
onQueuedScheduleRequestResultIsBack.invoke()
})
}
) {
}
}
Unfortunately, this can't differentiate if the bottomSheetState change is due to click on the transparent background or due to other change by the state. If you have a use case that requires observing only for transparent background click but not other dismiss action, please leave a comment here: https://buganizer.corp.google.com/issues/256755735
I have implemented the accompanist navigation animation library in my project and have stumbled into two issues. The first issue is that the animations aren't being applied when navigating from one screen to another. The second issue is that the "back" of the system closes the app to the background instead of returning to the previous screen.
Here is the layout of the app starting from the MainActivity.
MainActivity.kt
#ExperimentalAnimationApi
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val preDrawListener = ViewTreeObserver.OnPreDrawListener { false }
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(R.style.MyHomeTheme)
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
val content: View = findViewById(android.R.id.content)
content.viewTreeObserver.addOnPreDrawListener(preDrawListener)
lifecycleScope.launch {
setContent {
val systemUiController = rememberSystemUiController()
SideEffect {
systemUiController.setStatusBarColor(
color = Color.Transparent,
darkIcons = true
)
systemUiController.setNavigationBarColor(
color = Color(0x40000000),
darkIcons = false
)
}
MyHomeApp(
currentRoute = Destinations.Welcome.WELCOME_ROUTE
)
}
unblockDrawing()
}
}
private fun unblockDrawing() {
val content: View = findViewById(android.R.id.content)
content.viewTreeObserver.removeOnPreDrawListener(preDrawListener)
content.viewTreeObserver.addOnPreDrawListener { true }
}
}
MyHomeApp.kt
#ExperimentalAnimationApi
#Composable
fun MyHomeApp(currentRoute: String) {
MyHomeTheme {
ProvideWindowInsets {
val navController = rememberAnimatedNavController()
val scaffoldState = rememberScaffoldState()
val darkTheme = isSystemInDarkTheme()
val items = listOf(
HomeTab.Dashboard,
HomeTab.Details,
HomeTab.Settings
)
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
val bottomPaddingModifier = if (currentDestination?.route?.contains("welcome") == true) {
Modifier
} else {
Modifier.navigationBarsPadding()
}
Scaffold(
modifier = Modifier
.fillMaxSize()
.then(bottomPaddingModifier),
scaffoldState = scaffoldState,
bottomBar = {
if (currentDestination?.route in items.map { it.route }) {
BottomNavigation {
items.forEach { screen ->
BottomNavigationItem(
label = { Text(screen.title) },
icon = {},
selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
onClick = {
navController.navigate(screen.route) {
// Pop up to the start destination of the graph to
// avoid building up a large stack of destinations
// on the back stack as users select items
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
// Avoid multiple copies of the same destination when
// reselecting the same item
launchSingleTop = true
// Restore state when reselecting a previously selected item
restoreState = true
}
}
)
}
}
}
}
) { innerPadding ->
MyHomeNavGraph(
modifier = Modifier.padding(innerPadding),
navController = navController,
startDestination = navBackStackEntry?.destination?.route ?: currentRoute
)
}
}
}
}
sealed class HomeTab(
val route: String,
val title: String
) {
object Dashboard : HomeTab(
route = Destinations.Home.HOME_DASHBOARD,
title = "Dashboard"
)
object Details : HomeTab(
route = Destinations.Home.HOME_DETAILS,
title = "Details"
)
object Settings : HomeTab(
route = Destinations.Home.HOME_SETTINGS,
title = "Settings"
)
}
MyHomeNavGraph.kt
#ExperimentalAnimationApi
#Composable
fun MyHomeNavGraph(
modifier: Modifier = Modifier,
navController: NavHostController,
startDestination: String
) {
val actions = remember(navController) { Actions(navController = navController) }
AnimatedNavHost(
modifier = modifier,
navController = navController,
startDestination = startDestination
) {
composable(
route = Destinations.Welcome.WELCOME_ROUTE,
enterTransition = {
when (initialState.destination.route) {
Destinations.Welcome.WELCOME_LOGIN_ROUTE ->
slideIntoContainer(towards = AnimatedContentScope.SlideDirection.Left, animationSpec = tween(700))
else -> null
}
},
exitTransition = {
when (targetState.destination.route) {
Destinations.Welcome.WELCOME_LOGIN_ROUTE ->
slideOutOfContainer(towards = AnimatedContentScope.SlideDirection.Left, animationSpec = tween(700))
else -> null
}
},
popEnterTransition = {
when (initialState.destination.route) {
Destinations.Welcome.WELCOME_LOGIN_ROUTE ->
slideIntoContainer(towards = AnimatedContentScope.SlideDirection.Right, animationSpec = tween(700))
else -> null
}
},
popExitTransition = {
when (targetState.destination.route) {
Destinations.Welcome.WELCOME_LOGIN_ROUTE ->
slideOutOfContainer(towards = AnimatedContentScope.SlideDirection.Right, animationSpec = tween(700))
else -> null
}
}
) {
WelcomeScreen(
navigateToLogin = actions.navigateToWelcomeLogin,
navigateToRegister = actions.navigateToWelcomeRegister,
)
}
composable(
route = Destinations.Welcome.WELCOME_LOGIN_ROUTE,
enterTransition = {
when (initialState.destination.route) {
Destinations.Welcome.WELCOME_ROUTE ->
slideIntoContainer(towards = AnimatedContentScope.SlideDirection.Left, animationSpec = tween(700))
else -> null
}
},
exitTransition = {
when (targetState.destination.route) {
Destinations.Welcome.WELCOME_ROUTE ->
slideOutOfContainer(towards = AnimatedContentScope.SlideDirection.Left, animationSpec = tween(700))
else -> null
}
},
popEnterTransition = {
when (initialState.destination.route) {
Destinations.Welcome.WELCOME_ROUTE ->
slideIntoContainer(towards = AnimatedContentScope.SlideDirection.Right, animationSpec = tween(700))
else -> null
}
},
popExitTransition = {
when (targetState.destination.route) {
Destinations.Welcome.WELCOME_ROUTE ->
slideOutOfContainer(towards = AnimatedContentScope.SlideDirection.Right, animationSpec = tween(700))
else -> null
}
}
) {
WelcomeLoginScreen(
// Arguments will be passed to navigate to the home screen or other
)
}
}
}
class Actions(val navController: NavHostController) {
// Welcome
val navigateToWelcome = {
navController.navigate(Destinations.Welcome.WELCOME_ROUTE)
}
val navigateToWelcomeLogin = {
navController.navigate(Destinations.Welcome.WELCOME_LOGIN_ROUTE)
}
}
For simplicity's sake, you can assume that the screens are juste a box with a button in the middle which executes the navigation when they are clicked.
The accompanist version I am using is 0.24.1-alpha (the latest as of this question) and I am using compose version 1.2.0-alpha02 and kotlin 1.6.10.
In terms of animation, the only difference I can see with the accompanist samples is that I don't pass the navController to the screens but I don't see how that could be an issue.
And in terms of using the system back which should return to a previous, I'm genuinely stuck in terms of what could cause the navigation to close the app instead of going back. On other projects, the system back works just fine but not with this one. Is the use of the accompanist navigation incompatible ? I'm not sure.
Any help is appreciated!
I found the source of the issue.
The fact that I was setting the startDestination parameter to navBackStackEntry?.destination?.route ?: currentRoute meant that each change to the navBackStackEntry recomposed the MyHomeNavGraph and hence the backstack was reset upon the recomposition.
Note to self, watch out when copying navigation from multiple sources!
Okay so I've been trying to implement swipe to delete function in my app. Whenever I swipe one of the items from the list I'm able to see a RedBackground behind and everything works fine. Also the swipe animation when I delete an item is triggered successfully. (Even though I'm not sure if it's a good idea to use delay for that? I can't think of any other way to do it).
But the enter animation when I add an item to the database/list is not working, and I'm not sure why. Here's the code of my Lazy Column
#Composable
fun DisplayTasks(
tasks: List<ToDoTask>,
onSwipeToDelete: (Action, ToDoTask) -> Unit,
navigateToTaskScreen: (Int) -> Unit
) {
LazyColumn {
items(
items = tasks,
key = { task ->
task.id
}
) { task ->
val dismissState = rememberDismissState()
val dismissDirection = dismissState.dismissDirection
val isDismissed = dismissState.isDismissed(DismissDirection.EndToStart)
if (isDismissed && dismissDirection == DismissDirection.EndToStart
) {
val scope = rememberCoroutineScope()
scope.launch {
delay(300)
onSwipeToDelete(Action.DELETE, task)
}
}
AnimatedVisibility(
visible = !isDismissed,
exit = shrinkVertically(
animationSpec = tween(
durationMillis = 300,
)
),
enter = expandVertically(
animationSpec = tween(
durationMillis = 300
)
)
) {
SwipeToDismiss(
state = dismissState,
directions = setOf(DismissDirection.EndToStart),
dismissThresholds = { FractionalThreshold(0.2f) },
background = { RedBackground() },
dismissContent = {
LazyColumnItem(
toDoTask = task,
navigateToTaskScreen = navigateToTaskScreen
)
}
)
}
}
}
}
First of all, you shouldn't perform any state changing actions inside composable. Instead use one of side effects, usually LaunchedEffect(key) { }: content of the block will be called on the first render and each time key is different from the last render. Also inside you're already in a coroutine scope, so no need to launch it. Check out more about side-effects in the documentation.
Item animation in the list is not yet supported. It's as simple as adding AnimatedVisibility to the items.
When compose firstly sees AnimatedVisibility in the compose tree, it draws(or not draws) it without animation.
And when on next recomposition visible is different from the last render time, it animates.
So to make it work as you wish you can do the following:
Add itemAppeared state value, which will make item in the list initially hidden, and using side effect make it visible right after render
Add columnAppeared which will prevent initial appearance animation - without it when screen renders all items will appear animatedly too
#Composable
fun DisplayTasks(
tasks: List<ToDoTask>,
onSwipeToDelete: (Action, ToDoTask) -> Unit,
) {
var columnAppeared by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
columnAppeared = true
}
LazyColumn {
items(
items = tasks,
key = { task ->
task.id
}
) { task ->
val dismissState = rememberDismissState()
val dismissDirection = dismissState.dismissDirection
val isDismissed = dismissState.isDismissed(DismissDirection.EndToStart)
if (isDismissed && dismissDirection == DismissDirection.EndToStart
) {
LaunchedEffect(Unit) {
delay(300)
onSwipeToDelete(Action.DELETE, task)
}
}
var itemAppeared by remember { mutableStateOf(!columnAppeared) }
LaunchedEffect(Unit) {
itemAppeared = true
}
AnimatedVisibility(
visible = itemAppeared && !isDismissed,
exit = shrinkVertically(
animationSpec = tween(
durationMillis = 300,
)
),
enter = expandVertically(
animationSpec = tween(
durationMillis = 300
)
)
) {
SwipeToDismiss(
state = dismissState,
directions = setOf(DismissDirection.EndToStart),
dismissThresholds = { FractionalThreshold(0.2f) },
background = {
Box(
Modifier
.background(Color.Red)
.fillMaxSize()
)
},
dismissContent = {
Text(task.id)
}
)
}
}
}
}
I have this Text:
Text(
text = stringResource(id = R.string.hello)
)
How can I show and hide this component?
I'm using Jetpack Compose version '1.0.0-alpha03'
As CommonsWare stated, compose being a declarative toolkit you tie your component to a state (for ex: isVisible), then compose will intelligently decide which composables depend on that state and recompose them. For ex:
#Composable
fun MyText(isVisible: Boolean){
if(isVisible){
Text(text = stringResource(id = R.string.hello))
}
}
Also you could use the AnimatedVisibility() composable for animations.
You can simply add a condition like:
if(isVisible){
Text("....")
}
Something like:
var visible by remember { mutableStateOf(true) }
Column() {
if (visible) {
Text("Text")
}
Button(onClick = { visible = !visible }) { Text("Toggle") }
}
If you want to animate the appearance and disappearance of its content you can use the AnimatedVisibility
var visible by remember { mutableStateOf(true) }
Column() {
AnimatedVisibility(
visible = visible,
enter = fadeIn(
// Overwrites the initial value of alpha to 0.4f for fade in, 0 by default
initialAlpha = 0.4f
),
exit = fadeOut(
// Overwrites the default animation with tween
animationSpec = tween(durationMillis = 250)
)
) {
// Content that needs to appear/disappear goes here:
Text("....")
}
Button(onClick = { visible = !visible }) { Text("Toggle") }
}
As stated above, you could use AnimatedVisibility like:
AnimatedVisibility(visible = yourCondition) { Text(text = getString(R.string.yourString)) }
/**
* #param visible if false content is invisible ie. space is still occupied
*/
#Composable
fun Visibility(
visible: Boolean,
content: #Composable () -> Unit
) {
val contentSize = remember { mutableStateOf(IntSize.Zero) }
Box(modifier = Modifier
.onSizeChanged { size -> contentSize.value = size }) {
if (visible || contentSize.value == IntSize.Zero) {
content()
} else {
Spacer(modifier = Modifier.size(contentSize.value.width.pxToDp(), contentSize.value.height.pxToDp()))
}
}
}
fun Int.pxToDp(): Dp {
return (this / getSystem().displayMetrics.density).dp
}
usage:
Visibility(text.value.isNotEmpty()) {
IconButton(
onClick = { text.value = "" },
modifier = Modifier
.padding(bottom = 8.dp)
.height(30.dp),
) {
Icon(Icons.Filled.Close, contentDescription = "Clear text")
}
}