From d2e862f8046f2585c57d55b86592d5491ae2bf08 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 30 Apr 2025 15:44:05 +0200 Subject: [PATCH 01/15] Removed old animation --- .../composeui/component/WooPosSearchInput.kt | 356 ++++++------------ 1 file changed, 117 insertions(+), 239 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt index aee24df926c..ddce9b6fada 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt @@ -1,17 +1,8 @@ package com.woocommerce.android.ui.woopos.common.composeui.component -import android.annotation.SuppressLint import androidx.activity.compose.BackHandler -import androidx.compose.animation.core.CubicBezierEasing -import androidx.compose.animation.core.FastOutSlowInEasing -import androidx.compose.animation.core.Transition -import androidx.compose.animation.core.animateDp -import androidx.compose.animation.core.animateFloat -import androidx.compose.animation.core.tween -import androidx.compose.animation.core.updateTransition import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth @@ -39,7 +30,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.res.stringResource @@ -47,7 +37,6 @@ import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.TextFieldValue -import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.woocommerce.android.R import com.woocommerce.android.ui.woopos.common.composeui.WooPosPreview @@ -56,11 +45,8 @@ import com.woocommerce.android.ui.woopos.common.composeui.designsystem.WooPosCor import com.woocommerce.android.ui.woopos.common.composeui.designsystem.WooPosSpacing import com.woocommerce.android.ui.woopos.common.composeui.designsystem.WooPosTheme import com.woocommerce.android.ui.woopos.common.composeui.designsystem.WooPosTypography -import kotlinx.coroutines.delay -private val BUTTON_SIZE = 40.dp private val INPUT_FIELD_HEIGHT = 56.dp -private const val ANIMATION_TIME = 300L @Composable fun WooPosSearchInput( @@ -82,7 +68,7 @@ fun WooPosSearchInput( ) { when (state) { is WooPosSearchInputState.Open -> { - AnimatedSearchInput( + SearchInput( state = state, onEvent = onEvent, ) @@ -102,256 +88,148 @@ fun WooPosSearchInput( } } -@SuppressLint("UnusedBoxWithConstraintsScope") @Composable -private fun AnimatedSearchInput( +private fun SearchInput( state: WooPosSearchInputState.Open, onEvent: (WooPosSearchUIEvent) -> Unit, ) { - BoxWithConstraints( - modifier = Modifier.fillMaxWidth(), - contentAlignment = Alignment.CenterEnd, + val focusRequester = remember { FocusRequester() } + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.End, + modifier = Modifier.fillMaxWidth() ) { - val focusRequester = remember { FocusRequester() } - var isExpanded by remember { mutableStateOf(state.hasAnimationPlayed) } - var isClosing by remember { mutableStateOf(false) } + IconButton( + onClick = { onEvent(WooPosSearchUIEvent.Close) }, + modifier = Modifier.size(48.dp) + ) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = stringResource( + R.string.woopos_search_back_content_description + ), + tint = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.size(28.dp) + ) + } - val transition = updateTransition( - targetState = if (isClosing) false else isExpanded, - label = "searchTransition" - ) + Spacer(modifier = Modifier.width(WooPosSpacing.Small.value)) - val width = animateSearchWidth(transition, maxWidth) - val height = animateSearchHeight(transition) - val cornerRadius = animateCornerRadius(transition) - val iconAlpha = animateIconAlpha(transition, isClosing) - val backButtonAlpha = animateIconAlpha(transition, isClosing) + val (hint, query) = when (state.input) { + is Input.Query -> "" to state.input.text + is Input.Hint -> state.input.text to "" + } - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.End, - modifier = Modifier.width(width) - ) { - IconButton( - onClick = { - isClosing = true - }, - modifier = Modifier.alpha(backButtonAlpha) - .size(48.dp) - ) { - Icon( - imageVector = Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = stringResource( - R.string.woopos_search_back_content_description - ), - tint = MaterialTheme.colorScheme.onSurface, - modifier = Modifier.size(28.dp) + var textFieldValue by remember { + mutableStateOf( + TextFieldValue( + text = query, + selection = TextRange(state.input.cursorPosition) ) - } - - Spacer(modifier = Modifier.width(WooPosSpacing.Small.value)) - - val (hint, query) = when (state.input) { - is Input.Query -> "" to state.input.text - is Input.Hint -> state.input.text to "" - } + ) + } - var textFieldValue by remember { - mutableStateOf( - TextFieldValue( - text = query, - selection = TextRange(state.input.cursorPosition) - ) + LaunchedEffect(query) { + if (query != textFieldValue.text) { + textFieldValue = TextFieldValue( + text = query, + selection = TextRange(state.input.cursorPosition) ) } + } - LaunchedEffect(query) { - if (query != textFieldValue.text) { - textFieldValue = TextFieldValue( - text = query, - selection = TextRange(state.input.cursorPosition) - ) - } + LaunchedEffect(Unit) { + if (!state.hasAnimationPlayed) { + focusRequester.requestFocus() + onEvent(WooPosSearchUIEvent.AnimationComplete) } + } - OutlinedTextField( - value = textFieldValue, - onValueChange = { newValue: TextFieldValue -> - textFieldValue = newValue + OutlinedTextField( + value = textFieldValue, + onValueChange = { newValue: TextFieldValue -> + textFieldValue = newValue + onEvent( + WooPosSearchUIEvent.Search( + newValue.text, + newValue.selection.start + ) + ) + }, + modifier = Modifier + .weight(1f) + .height(INPUT_FIELD_HEIGHT) + .focusRequester(focusRequester), + placeholder = { + WooPosText( + text = hint, + style = WooPosTypography.BodyMedium, + fontWeight = FontWeight.Bold, + color = WooPosTheme.colors.onSurfaceVariantLowest, + ) + }, + textStyle = WooPosTypography.BodyMedium.style + .copy(fontWeight = FontWeight.Bold), + singleLine = true, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), + shape = RoundedCornerShape(WooPosCornerRadius.Medium.value), + keyboardActions = KeyboardActions( + onSearch = { onEvent( WooPosSearchUIEvent.Search( - newValue.text, - newValue.selection.start + textFieldValue.text, + textFieldValue.selection.start ) ) - }, - modifier = Modifier - .weight(1f) - .height(height) - .focusRequester(focusRequester), - placeholder = { - WooPosText( - text = hint, - modifier = Modifier.alpha(iconAlpha), - style = WooPosTypography.BodyMedium, - fontWeight = FontWeight.Bold, - color = WooPosTheme.colors.onSurfaceVariantLowest, + } + ), + colors = OutlinedTextFieldDefaults.colors( + unfocusedBorderColor = MaterialTheme.colorScheme.surface, + focusedBorderColor = MaterialTheme.colorScheme.primary, + unfocusedContainerColor = MaterialTheme.colorScheme.surfaceBright, + focusedContainerColor = MaterialTheme.colorScheme.surfaceBright, + cursorColor = MaterialTheme.colorScheme.primary, + unfocusedTextColor = MaterialTheme.colorScheme.onSurface, + focusedTextColor = MaterialTheme.colorScheme.onSurface, + ), + leadingIcon = { + IconButton( + onClick = {}, + modifier = Modifier.size(32.dp) + ) { + Icon( + imageVector = Icons.Default.Search, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurface, ) - }, - textStyle = WooPosTypography.BodyMedium.style - .copy(fontWeight = FontWeight.Bold), - singleLine = true, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), - shape = RoundedCornerShape(cornerRadius), - keyboardActions = KeyboardActions( - onSearch = { - onEvent( - WooPosSearchUIEvent.Search( - textFieldValue.text, - textFieldValue.selection.start - ) - ) - } - ), - colors = OutlinedTextFieldDefaults.colors( - unfocusedBorderColor = MaterialTheme.colorScheme.surface, - focusedBorderColor = MaterialTheme.colorScheme.primary, - unfocusedContainerColor = MaterialTheme.colorScheme.surfaceBright, - focusedContainerColor = MaterialTheme.colorScheme.surfaceBright, - cursorColor = MaterialTheme.colorScheme.primary, - unfocusedTextColor = MaterialTheme.colorScheme.onSurface, - focusedTextColor = MaterialTheme.colorScheme.onSurface, - ), - leadingIcon = { - IconButton( - onClick = {}, - modifier = Modifier.size(32.dp) - ) { - Icon( - imageVector = Icons.Default.Search, - contentDescription = null, - tint = MaterialTheme.colorScheme.onSurface, + } + }, + trailingIcon = { + when { + state.isLoading -> { + WooPosCircularLoadingIndicator( + modifier = Modifier.size(24.dp) ) } - }, - trailingIcon = { - when { - state.isLoading -> { - WooPosCircularLoadingIndicator( - modifier = Modifier - .size(24.dp) - .alpha(iconAlpha) + textFieldValue.text.isNotEmpty() -> { + IconButton( + onClick = { onEvent(WooPosSearchUIEvent.Clear) }, + modifier = Modifier.size(32.dp) + ) { + Icon( + imageVector = Icons.Outlined.Cancel, + contentDescription = stringResource( + R.string.woopos_search_clear_content_description + ), + tint = MaterialTheme.colorScheme.onSurface, ) } - - textFieldValue.text.isNotEmpty() -> { - IconButton( - onClick = { onEvent(WooPosSearchUIEvent.Clear) }, - modifier = Modifier - .alpha(iconAlpha) - .size(32.dp) - ) { - Icon( - imageVector = Icons.Outlined.Cancel, - contentDescription = stringResource( - R.string.woopos_search_clear_content_description - ), - tint = MaterialTheme.colorScheme.onSurface, - ) - } - } } - }, - ) - } - - LaunchedEffect(Unit) { - if (!state.hasAnimationPlayed) { - isExpanded = true - delay(ANIMATION_TIME) - focusRequester.requestFocus() - onEvent(WooPosSearchUIEvent.AnimationComplete) - } - } - - LaunchedEffect(isClosing) { - if (isClosing) { - delay(ANIMATION_TIME) - onEvent(WooPosSearchUIEvent.Close) - } - } - } -} - -@Composable -private fun animateSearchWidth( - transition: Transition, - maxWidth: Dp, -): Dp { - val width by transition.animateDp( - label = "width", - transitionSpec = { - tween( - durationMillis = ANIMATION_TIME.toInt(), - easing = CubicBezierEasing(0.2f, 0.0f, 0.0f, 1.0f) - ) - } - ) { expanded -> - if (expanded) maxWidth else BUTTON_SIZE - } - return width -} - -@Composable -private fun animateSearchHeight(transition: Transition): Dp { - val height by transition.animateDp( - label = "height", - transitionSpec = { - tween( - durationMillis = ANIMATION_TIME.toInt(), - easing = CubicBezierEasing(0.2f, 0.0f, 0.2f, 1.0f) - ) - } - ) { expanded -> - if (expanded) INPUT_FIELD_HEIGHT else BUTTON_SIZE - } - return height -} - -@Composable -private fun animateCornerRadius(transition: Transition): Dp { - val cornerRadius by transition.animateDp( - label = "cornerRadius", - transitionSpec = { - tween( - durationMillis = ANIMATION_TIME.toInt(), - easing = CubicBezierEasing(0.2f, 0.0f, 0.2f, 1.0f) - ) - } - ) { expanded -> - if (expanded) WooPosCornerRadius.Medium.value else BUTTON_SIZE / 2 - } - return cornerRadius -} - -@Composable -private fun animateIconAlpha( - transition: Transition, - isClosing: Boolean -): Float { - val iconAlpha by transition.animateFloat( - label = "iconAlpha", - transitionSpec = { - tween( - durationMillis = (ANIMATION_TIME * 0.7).toInt(), - easing = FastOutSlowInEasing, - delayMillis = if (isClosing) 0 else (ANIMATION_TIME * 0.3).toInt() - ) - } - ) { expanded -> - if (expanded) 1f else 0f + } + }, + ) } - return iconAlpha } sealed class WooPosSearchInputState { From 46c964f17253db32a323a648c1f69a75d2201b4d Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 30 Apr 2025 16:22:19 +0200 Subject: [PATCH 02/15] Enhance search input with animated visibility transitions --- .../composeui/component/WooPosSearchInput.kt | 76 +++++++------- .../woopos/home/items/WooPosItemsToolbar.kt | 98 +++++++++++++------ 2 files changed, 113 insertions(+), 61 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt index ddce9b6fada..7e1109bc9a7 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt @@ -1,7 +1,10 @@ package com.woocommerce.android.ui.woopos.common.composeui.component import androidx.activity.compose.BackHandler -import androidx.compose.foundation.layout.Arrangement +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -59,31 +62,38 @@ fun WooPosSearchInput( onBack = { onEvent(WooPosSearchUIEvent.Close) } ) - Row( + Box( modifier = modifier .fillMaxWidth() .height(INPUT_FIELD_HEIGHT), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Start, + contentAlignment = Alignment.CenterEnd ) { - when (state) { - is WooPosSearchInputState.Open -> { + AnimatedVisibility( + visible = state is WooPosSearchInputState.Open, + enter = fadeIn(animationSpec = tween(300)), + exit = fadeOut(animationSpec = tween(300)), + modifier = Modifier.fillMaxWidth() + ) { + if (state is WooPosSearchInputState.Open) { SearchInput( state = state, onEvent = onEvent, ) } + } - WooPosSearchInputState.Closed -> { - Spacer(modifier = Modifier.weight(1f)) - WooPosCircularIconButton( - icon = Icons.Default.Search, - contentDescription = stringResource( - id = R.string.woopos_search_products, - ), - onClick = { onEvent(WooPosSearchUIEvent.Search("", 0)) } - ) - } + AnimatedVisibility( + visible = state is WooPosSearchInputState.Closed, + enter = fadeIn(animationSpec = tween(300)), + exit = fadeOut(animationSpec = tween(300)) + ) { + WooPosCircularIconButton( + icon = Icons.Default.Search, + contentDescription = stringResource( + id = R.string.woopos_search_products, + ), + onClick = { onEvent(WooPosSearchUIEvent.Search("", 0)) } + ) } } } @@ -97,7 +107,6 @@ private fun SearchInput( Row( verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth() ) { IconButton( @@ -130,22 +139,6 @@ private fun SearchInput( ) } - LaunchedEffect(query) { - if (query != textFieldValue.text) { - textFieldValue = TextFieldValue( - text = query, - selection = TextRange(state.input.cursorPosition) - ) - } - } - - LaunchedEffect(Unit) { - if (!state.hasAnimationPlayed) { - focusRequester.requestFocus() - onEvent(WooPosSearchUIEvent.AnimationComplete) - } - } - OutlinedTextField( value = textFieldValue, onValueChange = { newValue: TextFieldValue -> @@ -212,6 +205,7 @@ private fun SearchInput( modifier = Modifier.size(24.dp) ) } + textFieldValue.text.isNotEmpty() -> { IconButton( onClick = { onEvent(WooPosSearchUIEvent.Clear) }, @@ -229,6 +223,22 @@ private fun SearchInput( } }, ) + + LaunchedEffect(query) { + if (query != textFieldValue.text) { + textFieldValue = TextFieldValue( + text = query, + selection = TextRange(state.input.cursorPosition) + ) + } + } + + LaunchedEffect(Unit) { + if (!state.hasAnimationPlayed) { + focusRequester.requestFocus() + onEvent(WooPosSearchUIEvent.AnimationComplete) + } + } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsToolbar.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsToolbar.kt index bd9fb0e90e3..f2474cc6054 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsToolbar.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsToolbar.kt @@ -7,6 +7,7 @@ import androidx.compose.animation.fadeOut 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.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth @@ -25,67 +26,79 @@ import com.woocommerce.android.R import com.woocommerce.android.ui.woopos.common.composeui.WooPosPreview import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosSearchInput import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosSearchInputState +import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosSearchInputState.Open.Input import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosSearchUIEvent import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosText import com.woocommerce.android.ui.woopos.common.composeui.designsystem.WooPosSpacing import com.woocommerce.android.ui.woopos.common.composeui.designsystem.WooPosTheme import com.woocommerce.android.ui.woopos.common.composeui.designsystem.WooPosTypography +import com.woocommerce.android.ui.woopos.home.items.WooPosItemsViewState.SearchState import com.woocommerce.android.ui.woopos.home.items.WooPosItemsViewState.Tab.HighlightLevel.Full import com.woocommerce.android.ui.woopos.home.items.WooPosItemsViewState.Tab.HighlightLevel.Normal +private const val ANIMATION_DURATION = 200 + @Composable fun WooPosItemsToolbar( state: WooPosItemsViewState, onTabClicked: (WooPosItemsViewState.Tab) -> Unit, onSearchEvent: (WooPosSearchUIEvent) -> Unit, ) { - val isSearchExpanded = state is WooPosItemsViewState.ProductList && - state.search is WooPosItemsViewState.SearchState.Visible && - (state.search as WooPosItemsViewState.SearchState.Visible).state is WooPosSearchInputState.Open + val isSearchExpanded = (state.search as? SearchState.Visible)?.let { + it.state is WooPosSearchInputState.Open + } == true - Row( + Box( modifier = Modifier .fillMaxWidth() .height(56.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween, + contentAlignment = Alignment.CenterStart ) { AnimatedVisibility( visible = !isSearchExpanded, - enter = fadeIn(animationSpec = tween(200)), - exit = fadeOut(animationSpec = tween(200)), + enter = fadeIn(animationSpec = tween(ANIMATION_DURATION)), + exit = fadeOut(animationSpec = tween(ANIMATION_DURATION)), + modifier = Modifier.fillMaxWidth() ) { - Row { - state.tabs.forEach { tab -> - WooPosText( - text = stringResource(id = tab.stringId), - style = WooPosTypography.Heading, - fontWeight = FontWeight.Bold, - color = tab.highlightLevel.titleColor(), - modifier = Modifier.clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = null, - onClick = { onTabClicked(tab) } + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + state.tabs.forEach { tab -> + WooPosText( + text = stringResource(id = tab.stringId), + style = WooPosTypography.Heading, + fontWeight = FontWeight.Bold, + color = tab.highlightLevel.titleColor(), + modifier = Modifier.clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null, + onClick = { onTabClicked(tab) } + ) ) - ) - Spacer(modifier = Modifier.width(WooPosSpacing.Large.value)) + Spacer(modifier = Modifier.width(WooPosSpacing.Large.value)) + } } } } - Row( - horizontalArrangement = Arrangement.End, - verticalAlignment = Alignment.CenterVertically, + val isSearchVisible = state.search is SearchState.Visible + AnimatedVisibility( + visible = isSearchVisible, + enter = fadeIn(animationSpec = tween(ANIMATION_DURATION)), + exit = fadeOut(animationSpec = tween(ANIMATION_DURATION)), + modifier = Modifier.fillMaxWidth() ) { when (state) { is WooPosItemsViewState.ProductList -> { when (val searchState = state.search) { - WooPosItemsViewState.SearchState.Hidden -> Unit - is WooPosItemsViewState.SearchState.Visible -> { + SearchState.Hidden -> Unit + is SearchState.Visible -> { WooPosSearchInput( state = searchState.state, onEvent = onSearchEvent, - modifier = Modifier.weight(1f) ) } } @@ -115,7 +128,36 @@ fun WooPosItemsToolbarPreview() { WooPosItemsToolbar( state = WooPosItemsViewState.ProductList( tabs = tabs, - search = WooPosItemsViewState.SearchState.Hidden, + search = SearchState.Hidden, + ), + onTabClicked = {}, + onSearchEvent = {} + ) + } +} + +@Composable +@WooPosPreview +fun WooPosItemsToolbarWithSearchPreview() { + val tabs = listOf( + WooPosItemsViewState.Tab(R.string.woopos_products_screen_title, highlightLevel = Full), + WooPosItemsViewState.Tab(R.string.woopos_coupons_screen_title, highlightLevel = Normal), + ) + + WooPosTheme { + WooPosItemsToolbar( + state = WooPosItemsViewState.ProductList( + tabs = tabs, + search = SearchState.Visible( + state = WooPosSearchInputState.Open( + input = Input.Query( + query = "", + cursorPosition = 1, + ), + isLoading = false, + hasAnimationPlayed = false, + ) + ) ), onTabClicked = {}, onSearchEvent = {} From a48543ce8f7fea40acff5cfcdb37e2def8a23776 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 30 Apr 2025 16:50:32 +0200 Subject: [PATCH 03/15] Refactor search input animations to allow customizable duration --- .../composeui/component/WooPosSearchInput.kt | 238 +++++++++--------- .../woopos/home/items/WooPosItemsToolbar.kt | 50 ++-- 2 files changed, 142 insertions(+), 146 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt index 7e1109bc9a7..81949beb334 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt @@ -48,6 +48,7 @@ import com.woocommerce.android.ui.woopos.common.composeui.designsystem.WooPosCor import com.woocommerce.android.ui.woopos.common.composeui.designsystem.WooPosSpacing import com.woocommerce.android.ui.woopos.common.composeui.designsystem.WooPosTheme import com.woocommerce.android.ui.woopos.common.composeui.designsystem.WooPosTypography +import kotlinx.coroutines.delay private val INPUT_FIELD_HEIGHT = 56.dp @@ -55,6 +56,7 @@ private val INPUT_FIELD_HEIGHT = 56.dp fun WooPosSearchInput( modifier: Modifier = Modifier, state: WooPosSearchInputState = WooPosSearchInputState.Closed, + animationDuration: Int = 300, onEvent: (WooPosSearchUIEvent) -> Unit = {}, ) { BackHandler( @@ -70,13 +72,14 @@ fun WooPosSearchInput( ) { AnimatedVisibility( visible = state is WooPosSearchInputState.Open, - enter = fadeIn(animationSpec = tween(300)), - exit = fadeOut(animationSpec = tween(300)), + enter = fadeIn(animationSpec = tween(animationDuration)), + exit = fadeOut(animationSpec = tween(animationDuration)), modifier = Modifier.fillMaxWidth() ) { if (state is WooPosSearchInputState.Open) { SearchInput( state = state, + animationDuration = animationDuration.toLong(), onEvent = onEvent, ) } @@ -84,8 +87,8 @@ fun WooPosSearchInput( AnimatedVisibility( visible = state is WooPosSearchInputState.Closed, - enter = fadeIn(animationSpec = tween(300)), - exit = fadeOut(animationSpec = tween(300)) + enter = fadeIn(animationSpec = tween(animationDuration)), + exit = fadeOut(animationSpec = tween(animationDuration)) ) { WooPosCircularIconButton( icon = Icons.Default.Search, @@ -101,7 +104,8 @@ fun WooPosSearchInput( @Composable private fun SearchInput( state: WooPosSearchInputState.Open, - onEvent: (WooPosSearchUIEvent) -> Unit, + animationDuration: Long, + onEvent: (WooPosSearchUIEvent) -> Unit ) { val focusRequester = remember { FocusRequester() } @@ -109,134 +113,140 @@ private fun SearchInput( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth() ) { - IconButton( - onClick = { onEvent(WooPosSearchUIEvent.Close) }, - modifier = Modifier.size(48.dp) + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth() ) { - Icon( - imageVector = Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = stringResource( - R.string.woopos_search_back_content_description - ), - tint = MaterialTheme.colorScheme.onSurface, - modifier = Modifier.size(28.dp) - ) - } - - Spacer(modifier = Modifier.width(WooPosSpacing.Small.value)) + IconButton( + onClick = { onEvent(WooPosSearchUIEvent.Close) }, + modifier = Modifier.size(48.dp) + ) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = stringResource( + R.string.woopos_search_back_content_description + ), + tint = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.size(28.dp) + ) + } - val (hint, query) = when (state.input) { - is Input.Query -> "" to state.input.text - is Input.Hint -> state.input.text to "" - } + Spacer(modifier = Modifier.width(WooPosSpacing.Small.value)) - var textFieldValue by remember { - mutableStateOf( - TextFieldValue( - text = query, - selection = TextRange(state.input.cursorPosition) - ) - ) - } + val (hint, query) = when (state.input) { + is Input.Query -> "" to state.input.text + is Input.Hint -> state.input.text to "" + } - OutlinedTextField( - value = textFieldValue, - onValueChange = { newValue: TextFieldValue -> - textFieldValue = newValue - onEvent( - WooPosSearchUIEvent.Search( - newValue.text, - newValue.selection.start + var textFieldValue by remember { + mutableStateOf( + TextFieldValue( + text = query, + selection = TextRange(state.input.cursorPosition) ) ) - }, - modifier = Modifier - .weight(1f) - .height(INPUT_FIELD_HEIGHT) - .focusRequester(focusRequester), - placeholder = { - WooPosText( - text = hint, - style = WooPosTypography.BodyMedium, - fontWeight = FontWeight.Bold, - color = WooPosTheme.colors.onSurfaceVariantLowest, - ) - }, - textStyle = WooPosTypography.BodyMedium.style - .copy(fontWeight = FontWeight.Bold), - singleLine = true, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), - shape = RoundedCornerShape(WooPosCornerRadius.Medium.value), - keyboardActions = KeyboardActions( - onSearch = { + } + + OutlinedTextField( + value = textFieldValue, + onValueChange = { newValue: TextFieldValue -> + textFieldValue = newValue onEvent( WooPosSearchUIEvent.Search( - textFieldValue.text, - textFieldValue.selection.start + newValue.text, + newValue.selection.start ) ) - } - ), - colors = OutlinedTextFieldDefaults.colors( - unfocusedBorderColor = MaterialTheme.colorScheme.surface, - focusedBorderColor = MaterialTheme.colorScheme.primary, - unfocusedContainerColor = MaterialTheme.colorScheme.surfaceBright, - focusedContainerColor = MaterialTheme.colorScheme.surfaceBright, - cursorColor = MaterialTheme.colorScheme.primary, - unfocusedTextColor = MaterialTheme.colorScheme.onSurface, - focusedTextColor = MaterialTheme.colorScheme.onSurface, - ), - leadingIcon = { - IconButton( - onClick = {}, - modifier = Modifier.size(32.dp) - ) { - Icon( - imageVector = Icons.Default.Search, - contentDescription = null, - tint = MaterialTheme.colorScheme.onSurface, + }, + modifier = Modifier + .weight(1f) + .height(INPUT_FIELD_HEIGHT) + .focusRequester(focusRequester), + placeholder = { + WooPosText( + text = hint, + style = WooPosTypography.BodyMedium, + fontWeight = FontWeight.Bold, + color = WooPosTheme.colors.onSurfaceVariantLowest, ) - } - }, - trailingIcon = { - when { - state.isLoading -> { - WooPosCircularLoadingIndicator( - modifier = Modifier.size(24.dp) + }, + textStyle = WooPosTypography.BodyMedium.style + .copy(fontWeight = FontWeight.Bold), + singleLine = true, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), + shape = RoundedCornerShape(WooPosCornerRadius.Medium.value), + keyboardActions = KeyboardActions( + onSearch = { + onEvent( + WooPosSearchUIEvent.Search( + textFieldValue.text, + textFieldValue.selection.start + ) ) } - - textFieldValue.text.isNotEmpty() -> { - IconButton( - onClick = { onEvent(WooPosSearchUIEvent.Clear) }, - modifier = Modifier.size(32.dp) - ) { - Icon( - imageVector = Icons.Outlined.Cancel, - contentDescription = stringResource( - R.string.woopos_search_clear_content_description - ), - tint = MaterialTheme.colorScheme.onSurface, + ), + colors = OutlinedTextFieldDefaults.colors( + unfocusedBorderColor = MaterialTheme.colorScheme.surface, + focusedBorderColor = MaterialTheme.colorScheme.primary, + unfocusedContainerColor = MaterialTheme.colorScheme.surfaceBright, + focusedContainerColor = MaterialTheme.colorScheme.surfaceBright, + cursorColor = MaterialTheme.colorScheme.primary, + unfocusedTextColor = MaterialTheme.colorScheme.onSurface, + focusedTextColor = MaterialTheme.colorScheme.onSurface, + ), + leadingIcon = { + IconButton( + onClick = {}, + modifier = Modifier.size(32.dp) + ) { + Icon( + imageVector = Icons.Default.Search, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurface, + ) + } + }, + trailingIcon = { + when { + state.isLoading -> { + WooPosCircularLoadingIndicator( + modifier = Modifier.size(24.dp) ) } + + textFieldValue.text.isNotEmpty() -> { + IconButton( + onClick = { onEvent(WooPosSearchUIEvent.Clear) }, + modifier = Modifier.size(32.dp) + ) { + Icon( + imageVector = Icons.Outlined.Cancel, + contentDescription = stringResource( + R.string.woopos_search_clear_content_description + ), + tint = MaterialTheme.colorScheme.onSurface, + ) + } + } } - } - }, - ) + }, + ) - LaunchedEffect(query) { - if (query != textFieldValue.text) { - textFieldValue = TextFieldValue( - text = query, - selection = TextRange(state.input.cursorPosition) - ) + LaunchedEffect(query) { + if (query != textFieldValue.text) { + textFieldValue = TextFieldValue( + text = query, + selection = TextRange(state.input.cursorPosition) + ) + } } - } - LaunchedEffect(Unit) { - if (!state.hasAnimationPlayed) { - focusRequester.requestFocus() - onEvent(WooPosSearchUIEvent.AnimationComplete) + LaunchedEffect(Unit) { + if (!state.hasAnimationPlayed) { + delay(animationDuration) + focusRequester.requestFocus() + onEvent(WooPosSearchUIEvent.AnimationComplete) + } } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsToolbar.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsToolbar.kt index f2474cc6054..32038019fb4 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsToolbar.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsToolbar.kt @@ -36,7 +36,7 @@ import com.woocommerce.android.ui.woopos.home.items.WooPosItemsViewState.SearchS import com.woocommerce.android.ui.woopos.home.items.WooPosItemsViewState.Tab.HighlightLevel.Full import com.woocommerce.android.ui.woopos.home.items.WooPosItemsViewState.Tab.HighlightLevel.Normal -private const val ANIMATION_DURATION = 200 +private const val ANIMATION_DURATION = 300 @Composable fun WooPosItemsToolbar( @@ -44,7 +44,7 @@ fun WooPosItemsToolbar( onTabClicked: (WooPosItemsViewState.Tab) -> Unit, onSearchEvent: (WooPosSearchUIEvent) -> Unit, ) { - val isSearchExpanded = (state.search as? SearchState.Visible)?.let { + val isSearchOpen = (state.search as? SearchState.Visible)?.let { it.state is WooPosSearchInputState.Open } == true @@ -54,16 +54,15 @@ fun WooPosItemsToolbar( .height(56.dp), contentAlignment = Alignment.CenterStart ) { - AnimatedVisibility( - visible = !isSearchExpanded, - enter = fadeIn(animationSpec = tween(ANIMATION_DURATION)), - exit = fadeOut(animationSpec = tween(ANIMATION_DURATION)), - modifier = Modifier.fillMaxWidth() + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween ) { - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween + AnimatedVisibility( + visible = !isSearchOpen, + enter = fadeIn(animationSpec = tween(ANIMATION_DURATION)), + exit = fadeOut(animationSpec = tween(ANIMATION_DURATION)), ) { Row(verticalAlignment = Alignment.CenterVertically) { state.tabs.forEach { tab -> @@ -84,27 +83,14 @@ fun WooPosItemsToolbar( } } - val isSearchVisible = state.search is SearchState.Visible - AnimatedVisibility( - visible = isSearchVisible, - enter = fadeIn(animationSpec = tween(ANIMATION_DURATION)), - exit = fadeOut(animationSpec = tween(ANIMATION_DURATION)), - modifier = Modifier.fillMaxWidth() - ) { - when (state) { - is WooPosItemsViewState.ProductList -> { - when (val searchState = state.search) { - SearchState.Hidden -> Unit - is SearchState.Visible -> { - WooPosSearchInput( - state = searchState.state, - onEvent = onSearchEvent, - ) - } - } - } - - is WooPosItemsViewState.CouponList -> Unit + when (val searchState = state.search) { + SearchState.Hidden -> Unit + is SearchState.Visible -> { + WooPosSearchInput( + state = searchState.state, + animationDuration = ANIMATION_DURATION, + onEvent = onSearchEvent, + ) } } } From 155ee919f89f4f5937ad74acb5d8e9b8ebb0635c Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 30 Apr 2025 17:00:14 +0200 Subject: [PATCH 04/15] Refactor search state handling in WooPosItemsToolbar for clarity --- .../android/ui/woopos/home/items/WooPosItemsToolbar.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsToolbar.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsToolbar.kt index 32038019fb4..4e451f45b93 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsToolbar.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsToolbar.kt @@ -83,13 +83,15 @@ fun WooPosItemsToolbar( } } - when (val searchState = state.search) { + when (val search = state.search) { SearchState.Hidden -> Unit is SearchState.Visible -> { WooPosSearchInput( - state = searchState.state, + state = search.state, animationDuration = ANIMATION_DURATION, - onEvent = onSearchEvent, + onEvent = { event -> + onSearchEvent(event) + }, ) } } From 9b38903fd8808a022da1bfb0531786fe17e36593 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 30 Apr 2025 17:04:29 +0200 Subject: [PATCH 05/15] Refactor search state handling in WooPosItemsToolbar for clarity --- .../android/ui/woopos/home/items/WooPosItemsToolbar.kt | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsToolbar.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsToolbar.kt index 4e451f45b93..dd06872f08e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsToolbar.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsToolbar.kt @@ -6,7 +6,6 @@ import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut 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.Row import androidx.compose.foundation.layout.Spacer @@ -54,11 +53,7 @@ fun WooPosItemsToolbar( .height(56.dp), contentAlignment = Alignment.CenterStart ) { - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { + Box(modifier = Modifier.fillMaxWidth()) { AnimatedVisibility( visible = !isSearchOpen, enter = fadeIn(animationSpec = tween(ANIMATION_DURATION)), From dd5f36c0391edc75d63b8137d768e15c103404bf Mon Sep 17 00:00:00 2001 From: Andrey Date: Thu, 1 May 2025 08:29:24 +0200 Subject: [PATCH 06/15] Both ways working animation --- .../composeui/component/WooPosSearchInput.kt | 250 +++++++++--------- .../woopos/home/items/WooPosItemsToolbar.kt | 40 +-- 2 files changed, 150 insertions(+), 140 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt index 81949beb334..95c43475aae 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt @@ -2,6 +2,7 @@ package com.woocommerce.android.ui.woopos.common.composeui.component import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.MutableTransitionState import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut @@ -30,6 +31,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -64,6 +66,20 @@ fun WooPosSearchInput( onBack = { onEvent(WooPosSearchUIEvent.Close) } ) + var lastOpenState by rememberSaveable { mutableStateOf(null) } + + val searchVisibleState = remember { MutableTransitionState(state is WooPosSearchInputState.Closed) } + val inputVisibleState = remember { MutableTransitionState(state is WooPosSearchInputState.Open) } + + LaunchedEffect(state) { + searchVisibleState.targetState = state is WooPosSearchInputState.Closed + inputVisibleState.targetState = state is WooPosSearchInputState.Open + + if (state is WooPosSearchInputState.Open) { + lastOpenState = state + } + } + Box( modifier = modifier .fillMaxWidth() @@ -71,14 +87,13 @@ fun WooPosSearchInput( contentAlignment = Alignment.CenterEnd ) { AnimatedVisibility( - visible = state is WooPosSearchInputState.Open, + visibleState = inputVisibleState, enter = fadeIn(animationSpec = tween(animationDuration)), exit = fadeOut(animationSpec = tween(animationDuration)), - modifier = Modifier.fillMaxWidth() ) { - if (state is WooPosSearchInputState.Open) { + lastOpenState?.let { SearchInput( - state = state, + state = it, animationDuration = animationDuration.toLong(), onEvent = onEvent, ) @@ -86,7 +101,7 @@ fun WooPosSearchInput( } AnimatedVisibility( - visible = state is WooPosSearchInputState.Closed, + visibleState = searchVisibleState, enter = fadeIn(animationSpec = tween(animationDuration)), exit = fadeOut(animationSpec = tween(animationDuration)) ) { @@ -113,140 +128,135 @@ private fun SearchInput( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth() ) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth() + IconButton( + onClick = { onEvent(WooPosSearchUIEvent.Close) }, + modifier = Modifier.size(48.dp) ) { - IconButton( - onClick = { onEvent(WooPosSearchUIEvent.Close) }, - modifier = Modifier.size(48.dp) - ) { - Icon( - imageVector = Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = stringResource( - R.string.woopos_search_back_content_description - ), - tint = MaterialTheme.colorScheme.onSurface, - modifier = Modifier.size(28.dp) - ) - } + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = stringResource( + R.string.woopos_search_back_content_description + ), + tint = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.size(28.dp) + ) + } - Spacer(modifier = Modifier.width(WooPosSpacing.Small.value)) + Spacer(modifier = Modifier.width(WooPosSpacing.Small.value)) - val (hint, query) = when (state.input) { - is Input.Query -> "" to state.input.text - is Input.Hint -> state.input.text to "" - } + val (hint, query) = when (state.input) { + is Input.Query -> "" to state.input.text + is Input.Hint -> state.input.text to "" + } - var textFieldValue by remember { - mutableStateOf( - TextFieldValue( - text = query, - selection = TextRange(state.input.cursorPosition) - ) + var textFieldValue by remember { + mutableStateOf( + TextFieldValue( + text = query, + selection = TextRange(state.input.cursorPosition) ) - } + ) + } - OutlinedTextField( - value = textFieldValue, - onValueChange = { newValue: TextFieldValue -> - textFieldValue = newValue + OutlinedTextField( + value = textFieldValue, + onValueChange = { newValue: TextFieldValue -> + textFieldValue = newValue + onEvent( + WooPosSearchUIEvent.Search( + newValue.text, + newValue.selection.start + ) + ) + }, + modifier = Modifier + .weight(1f) + .height(INPUT_FIELD_HEIGHT) + .focusRequester(focusRequester), + placeholder = { + WooPosText( + text = hint, + style = WooPosTypography.BodyMedium, + fontWeight = FontWeight.Bold, + color = WooPosTheme.colors.onSurfaceVariantLowest, + ) + }, + textStyle = WooPosTypography.BodyMedium.style + .copy(fontWeight = FontWeight.Bold), + singleLine = true, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), + shape = RoundedCornerShape(WooPosCornerRadius.Medium.value), + keyboardActions = KeyboardActions( + onSearch = { onEvent( WooPosSearchUIEvent.Search( - newValue.text, - newValue.selection.start + textFieldValue.text, + textFieldValue.selection.start ) ) - }, - modifier = Modifier - .weight(1f) - .height(INPUT_FIELD_HEIGHT) - .focusRequester(focusRequester), - placeholder = { - WooPosText( - text = hint, - style = WooPosTypography.BodyMedium, - fontWeight = FontWeight.Bold, - color = WooPosTheme.colors.onSurfaceVariantLowest, + } + ), + colors = OutlinedTextFieldDefaults.colors( + unfocusedBorderColor = MaterialTheme.colorScheme.surface, + focusedBorderColor = MaterialTheme.colorScheme.primary, + unfocusedContainerColor = MaterialTheme.colorScheme.surfaceBright, + focusedContainerColor = MaterialTheme.colorScheme.surfaceBright, + cursorColor = MaterialTheme.colorScheme.primary, + unfocusedTextColor = MaterialTheme.colorScheme.onSurface, + focusedTextColor = MaterialTheme.colorScheme.onSurface, + ), + leadingIcon = { + IconButton( + onClick = {}, + modifier = Modifier.size(32.dp) + ) { + Icon( + imageVector = Icons.Default.Search, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurface, ) - }, - textStyle = WooPosTypography.BodyMedium.style - .copy(fontWeight = FontWeight.Bold), - singleLine = true, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), - shape = RoundedCornerShape(WooPosCornerRadius.Medium.value), - keyboardActions = KeyboardActions( - onSearch = { - onEvent( - WooPosSearchUIEvent.Search( - textFieldValue.text, - textFieldValue.selection.start - ) - ) - } - ), - colors = OutlinedTextFieldDefaults.colors( - unfocusedBorderColor = MaterialTheme.colorScheme.surface, - focusedBorderColor = MaterialTheme.colorScheme.primary, - unfocusedContainerColor = MaterialTheme.colorScheme.surfaceBright, - focusedContainerColor = MaterialTheme.colorScheme.surfaceBright, - cursorColor = MaterialTheme.colorScheme.primary, - unfocusedTextColor = MaterialTheme.colorScheme.onSurface, - focusedTextColor = MaterialTheme.colorScheme.onSurface, - ), - leadingIcon = { - IconButton( - onClick = {}, - modifier = Modifier.size(32.dp) - ) { - Icon( - imageVector = Icons.Default.Search, - contentDescription = null, - tint = MaterialTheme.colorScheme.onSurface, + } + }, + trailingIcon = { + when { + state.isLoading -> { + WooPosCircularLoadingIndicator( + modifier = Modifier.size(24.dp) ) } - }, - trailingIcon = { - when { - state.isLoading -> { - WooPosCircularLoadingIndicator( - modifier = Modifier.size(24.dp) - ) - } - textFieldValue.text.isNotEmpty() -> { - IconButton( - onClick = { onEvent(WooPosSearchUIEvent.Clear) }, - modifier = Modifier.size(32.dp) - ) { - Icon( - imageVector = Icons.Outlined.Cancel, - contentDescription = stringResource( - R.string.woopos_search_clear_content_description - ), - tint = MaterialTheme.colorScheme.onSurface, - ) - } + textFieldValue.text.isNotEmpty() -> { + IconButton( + onClick = { onEvent(WooPosSearchUIEvent.Clear) }, + modifier = Modifier.size(32.dp) + ) { + Icon( + imageVector = Icons.Outlined.Cancel, + contentDescription = stringResource( + R.string.woopos_search_clear_content_description + ), + tint = MaterialTheme.colorScheme.onSurface, + ) } } - }, - ) - - LaunchedEffect(query) { - if (query != textFieldValue.text) { - textFieldValue = TextFieldValue( - text = query, - selection = TextRange(state.input.cursorPosition) - ) } + }, + ) + + LaunchedEffect(query) { + if (query != textFieldValue.text) { + textFieldValue = TextFieldValue( + text = query, + selection = TextRange(state.input.cursorPosition) + ) } + } - LaunchedEffect(Unit) { - if (!state.hasAnimationPlayed) { - delay(animationDuration) - focusRequester.requestFocus() - onEvent(WooPosSearchUIEvent.AnimationComplete) - } + LaunchedEffect(Unit) { + if (!state.hasAnimationPlayed) { + delay(animationDuration) + focusRequester.requestFocus() + onEvent(WooPosSearchUIEvent.AnimationComplete) } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsToolbar.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsToolbar.kt index dd06872f08e..213a0cb19e5 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsToolbar.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsToolbar.kt @@ -53,31 +53,31 @@ fun WooPosItemsToolbar( .height(56.dp), contentAlignment = Alignment.CenterStart ) { - Box(modifier = Modifier.fillMaxWidth()) { - AnimatedVisibility( - visible = !isSearchOpen, - enter = fadeIn(animationSpec = tween(ANIMATION_DURATION)), - exit = fadeOut(animationSpec = tween(ANIMATION_DURATION)), - ) { - Row(verticalAlignment = Alignment.CenterVertically) { - state.tabs.forEach { tab -> - WooPosText( - text = stringResource(id = tab.stringId), - style = WooPosTypography.Heading, - fontWeight = FontWeight.Bold, - color = tab.highlightLevel.titleColor(), - modifier = Modifier.clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = null, - onClick = { onTabClicked(tab) } - ) + AnimatedVisibility( + visible = !isSearchOpen, + enter = fadeIn(animationSpec = tween(ANIMATION_DURATION)), + exit = fadeOut(animationSpec = tween(ANIMATION_DURATION)), + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + state.tabs.forEach { tab -> + WooPosText( + text = stringResource(id = tab.stringId), + style = WooPosTypography.Heading, + fontWeight = FontWeight.Bold, + color = tab.highlightLevel.titleColor(), + modifier = Modifier.clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null, + onClick = { onTabClicked(tab) } ) - Spacer(modifier = Modifier.width(WooPosSpacing.Large.value)) - } + ) + Spacer(modifier = Modifier.width(WooPosSpacing.Large.value)) } } } + println("state.search: ${state.search}") + when (val search = state.search) { SearchState.Hidden -> Unit is SearchState.Visible -> { From df93834308755a11b4835039a0cefc487da1b902 Mon Sep 17 00:00:00 2001 From: Andrey Date: Thu, 1 May 2025 08:37:54 +0200 Subject: [PATCH 07/15] Animation with proper delays --- .../composeui/component/WooPosSearchInput.kt | 28 ++++++++++++++++--- .../woopos/home/items/WooPosItemsToolbar.kt | 18 +++++++++--- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt index 95c43475aae..926c0502a82 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt @@ -2,6 +2,7 @@ package com.woocommerce.android.ui.woopos.common.composeui.component import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.MutableTransitionState import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn @@ -88,8 +89,17 @@ fun WooPosSearchInput( ) { AnimatedVisibility( visibleState = inputVisibleState, - enter = fadeIn(animationSpec = tween(animationDuration)), - exit = fadeOut(animationSpec = tween(animationDuration)), + enter = fadeIn( + animationSpec = tween( + durationMillis = animationDuration, + easing = FastOutSlowInEasing + ) + ), + exit = fadeOut( + animationSpec = tween( + durationMillis = animationDuration / 2 + ) + ), ) { lastOpenState?.let { SearchInput( @@ -102,8 +112,18 @@ fun WooPosSearchInput( AnimatedVisibility( visibleState = searchVisibleState, - enter = fadeIn(animationSpec = tween(animationDuration)), - exit = fadeOut(animationSpec = tween(animationDuration)) + enter = fadeIn( + animationSpec = tween( + durationMillis = animationDuration, + delayMillis = animationDuration / 3, + easing = FastOutSlowInEasing + ) + ), + exit = fadeOut( + animationSpec = tween( + durationMillis = animationDuration / 3 + ) + ) ) { WooPosCircularIconButton( icon = Icons.Default.Search, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsToolbar.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsToolbar.kt index 213a0cb19e5..ed8dc7aa0bb 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsToolbar.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsToolbar.kt @@ -1,6 +1,7 @@ package com.woocommerce.android.ui.woopos.home.items import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut @@ -55,8 +56,19 @@ fun WooPosItemsToolbar( ) { AnimatedVisibility( visible = !isSearchOpen, - enter = fadeIn(animationSpec = tween(ANIMATION_DURATION)), - exit = fadeOut(animationSpec = tween(ANIMATION_DURATION)), + enter = fadeIn( + animationSpec = tween( + durationMillis = ANIMATION_DURATION, + delayMillis = ANIMATION_DURATION / 3, + easing = FastOutSlowInEasing + ) + ), + exit = fadeOut( + animationSpec = tween( + durationMillis = ANIMATION_DURATION / 2, + easing = FastOutSlowInEasing + ) + ), ) { Row(verticalAlignment = Alignment.CenterVertically) { state.tabs.forEach { tab -> @@ -76,8 +88,6 @@ fun WooPosItemsToolbar( } } - println("state.search: ${state.search}") - when (val search = state.search) { SearchState.Hidden -> Unit is SearchState.Visible -> { From 8fcae9c638574a7076d3ae572fcd64fa02953d45 Mon Sep 17 00:00:00 2001 From: Andrey Date: Thu, 1 May 2025 08:49:48 +0200 Subject: [PATCH 08/15] Better delays --- .../ui/woopos/common/composeui/component/WooPosSearchInput.kt | 3 ++- .../android/ui/woopos/home/items/WooPosItemsToolbar.kt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt index 926c0502a82..914e279accf 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt @@ -97,7 +97,8 @@ fun WooPosSearchInput( ), exit = fadeOut( animationSpec = tween( - durationMillis = animationDuration / 2 + durationMillis = animationDuration / 2, + easing = FastOutSlowInEasing, ) ), ) { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsToolbar.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsToolbar.kt index ed8dc7aa0bb..abe8ed00342 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsToolbar.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsToolbar.kt @@ -59,7 +59,7 @@ fun WooPosItemsToolbar( enter = fadeIn( animationSpec = tween( durationMillis = ANIMATION_DURATION, - delayMillis = ANIMATION_DURATION / 3, + delayMillis = ANIMATION_DURATION / 2, easing = FastOutSlowInEasing ) ), From 56d00db604616cb50bc52101d3d7026113160830 Mon Sep 17 00:00:00 2001 From: Andrey Date: Thu, 1 May 2025 09:06:55 +0200 Subject: [PATCH 09/15] Animate focus --- .../composeui/component/WooPosSearchInput.kt | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt index 914e279accf..a1ebd9f5ea3 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt @@ -4,6 +4,7 @@ import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.MutableTransitionState +import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut @@ -38,6 +39,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.graphics.lerp import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.font.FontWeight @@ -144,6 +147,24 @@ private fun SearchInput( onEvent: (WooPosSearchUIEvent) -> Unit ) { val focusRequester = remember { FocusRequester() } + var isFocused by remember { mutableStateOf(false) } + + val borderColor by animateFloatAsState( + targetValue = if (isFocused) 1f else 0f, + animationSpec = tween(durationMillis = 150, easing = FastOutSlowInEasing), + label = "borderColorAnimation" + ) + + val colorSurface = MaterialTheme.colorScheme.surface + val colorPrimary = MaterialTheme.colorScheme.primary + + val animatedFocusedBorderColor = remember(borderColor) { + lerp( + colorSurface, + colorPrimary, + borderColor + ) + } Row( verticalAlignment = Alignment.CenterVertically, @@ -193,7 +214,10 @@ private fun SearchInput( modifier = Modifier .weight(1f) .height(INPUT_FIELD_HEIGHT) - .focusRequester(focusRequester), + .focusRequester(focusRequester) + .onFocusChanged { focusState -> + isFocused = focusState.isFocused + }, placeholder = { WooPosText( text = hint, @@ -219,7 +243,7 @@ private fun SearchInput( ), colors = OutlinedTextFieldDefaults.colors( unfocusedBorderColor = MaterialTheme.colorScheme.surface, - focusedBorderColor = MaterialTheme.colorScheme.primary, + focusedBorderColor = animatedFocusedBorderColor, unfocusedContainerColor = MaterialTheme.colorScheme.surfaceBright, focusedContainerColor = MaterialTheme.colorScheme.surfaceBright, cursorColor = MaterialTheme.colorScheme.primary, From be0d2ac2425c3431ea8715018180c4ca90b5860f Mon Sep 17 00:00:00 2001 From: Andrey Date: Thu, 1 May 2025 09:44:28 +0200 Subject: [PATCH 10/15] Crossfade between lists --- .../ui/woopos/home/items/WooPosItemsScreen.kt | 52 +++++++++++++------ 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsScreen.kt index 59379f893a0..730bee882b0 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsScreen.kt @@ -1,5 +1,8 @@ package com.woocommerce.android.ui.woopos.home.items +import androidx.compose.animation.Crossfade +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxHeight @@ -104,28 +107,43 @@ private fun MainItemsList( onSearchEvent = onSearchEvent, ) - when (val itemsState = state.value) { - is WooPosItemsViewState.ProductList -> { - Column { - when (val searchState = itemsState.search) { - WooPosItemsViewState.SearchState.Hidden -> { - WooPosProductsScreen(modifier = Modifier, listState = listState) - } - is WooPosItemsViewState.SearchState.Visible -> { - when (searchState.state) { - WooPosSearchInputState.Closed -> { - WooPosProductsScreen(modifier = Modifier, listState = listState) - } + val currentState = state.value - is WooPosSearchInputState.Open -> WooPosItemsSearchScreen() - } - } - } + Crossfade( + targetState = getScreenState(currentState), + animationSpec = tween( + durationMillis = 300, + easing = LinearEasing, + ), + ) { screenState -> + when (screenState) { + ScreenState.PRODUCTS -> WooPosProductsScreen(modifier = Modifier, listState = listState) + ScreenState.SEARCH -> WooPosItemsSearchScreen() + ScreenState.COUPONS -> WooPosCouponsScreen(modifier = Modifier) + } + } + } + } +} + +private enum class ScreenState { + PRODUCTS, SEARCH, COUPONS +} + +private fun getScreenState(state: WooPosItemsViewState): ScreenState { + return when (state) { + is WooPosItemsViewState.ProductList -> { + when (val searchState = state.search) { + WooPosItemsViewState.SearchState.Hidden -> ScreenState.PRODUCTS + is WooPosItemsViewState.SearchState.Visible -> { + when (searchState.state) { + WooPosSearchInputState.Closed -> ScreenState.PRODUCTS + is WooPosSearchInputState.Open -> ScreenState.SEARCH } } - is WooPosItemsViewState.CouponList -> WooPosCouponsScreen(modifier = Modifier) } } + is WooPosItemsViewState.CouponList -> ScreenState.COUPONS } } From b04a563c9a807c538a4d572edbbc832035a0c6e8 Mon Sep 17 00:00:00 2001 From: Andrey Date: Thu, 1 May 2025 11:34:05 +0200 Subject: [PATCH 11/15] Better naming for internal enum --- .../android/ui/woopos/home/items/WooPosItemsScreen.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsScreen.kt index 730bee882b0..e31040fa6b5 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsScreen.kt @@ -118,7 +118,7 @@ private fun MainItemsList( ) { screenState -> when (screenState) { ScreenState.PRODUCTS -> WooPosProductsScreen(modifier = Modifier, listState = listState) - ScreenState.SEARCH -> WooPosItemsSearchScreen() + ScreenState.PRODUCTS_SEARCH -> WooPosItemsSearchScreen() ScreenState.COUPONS -> WooPosCouponsScreen(modifier = Modifier) } } @@ -127,7 +127,7 @@ private fun MainItemsList( } private enum class ScreenState { - PRODUCTS, SEARCH, COUPONS + PRODUCTS, PRODUCTS_SEARCH, COUPONS } private fun getScreenState(state: WooPosItemsViewState): ScreenState { @@ -138,7 +138,7 @@ private fun getScreenState(state: WooPosItemsViewState): ScreenState { is WooPosItemsViewState.SearchState.Visible -> { when (searchState.state) { WooPosSearchInputState.Closed -> ScreenState.PRODUCTS - is WooPosSearchInputState.Open -> ScreenState.SEARCH + is WooPosSearchInputState.Open -> ScreenState.PRODUCTS_SEARCH } } } From 9b693e39384fa98fb2532bc3789aaa09042c769d Mon Sep 17 00:00:00 2001 From: Andrey Date: Thu, 1 May 2025 11:55:53 +0200 Subject: [PATCH 12/15] Slower focus animation --- .../ui/woopos/common/composeui/component/WooPosSearchInput.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt index a1ebd9f5ea3..997ed833a0a 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt @@ -151,7 +151,7 @@ private fun SearchInput( val borderColor by animateFloatAsState( targetValue = if (isFocused) 1f else 0f, - animationSpec = tween(durationMillis = 150, easing = FastOutSlowInEasing), + animationSpec = tween(durationMillis = 200, easing = FastOutSlowInEasing), label = "borderColorAnimation" ) From bc570634f744d2103ec9a103536abb4c821ee5c0 Mon Sep 17 00:00:00 2001 From: Andrey Date: Thu, 1 May 2025 12:02:40 +0200 Subject: [PATCH 13/15] Fixed scrolling under the search for the empty query state --- .../items/search/WooPosItemsEmptySearchQueryStateScreen.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/search/WooPosItemsEmptySearchQueryStateScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/search/WooPosItemsEmptySearchQueryStateScreen.kt index 8ba9e56f941..7c4781d8c11 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/search/WooPosItemsEmptySearchQueryStateScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/search/WooPosItemsEmptySearchQueryStateScreen.kt @@ -58,10 +58,10 @@ fun WooPosItemsEmptySearchQueryStateScreen( Column( modifier .fillMaxHeight() - .verticalScroll(scrollState) .padding(WooPosSpacing.None.value.toAdaptivePadding()) + .padding(top = WooPosSpacing.Large.value.toAdaptivePadding()) + .verticalScroll(scrollState) ) { - Spacer(modifier = Modifier.height(WooPosSpacing.Large.value)) if (state.popularItems.isNotEmpty() || state.recentSearches.isNotEmpty()) { Row( modifier = Modifier.fillMaxWidth() From 50d54a11ec4cc1643590241127b97a17f547f904 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 5 May 2025 14:45:02 +0200 Subject: [PATCH 14/15] Fixed crash on configuration change --- .../common/composeui/component/WooPosSearchInput.kt | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt index 997ed833a0a..ba43e4146c7 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt @@ -1,5 +1,6 @@ package com.woocommerce.android.ui.woopos.common.composeui.component +import android.os.Parcelable import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.FastOutSlowInEasing @@ -30,6 +31,7 @@ import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -55,6 +57,7 @@ import com.woocommerce.android.ui.woopos.common.composeui.designsystem.WooPosSpa import com.woocommerce.android.ui.woopos.common.composeui.designsystem.WooPosTheme import com.woocommerce.android.ui.woopos.common.composeui.designsystem.WooPosTypography import kotlinx.coroutines.delay +import kotlinx.parcelize.Parcelize private val INPUT_FIELD_HEIGHT = 56.dp @@ -307,18 +310,24 @@ private fun SearchInput( } } -sealed class WooPosSearchInputState { +@Stable +sealed class WooPosSearchInputState : Parcelable { + @Parcelize data class Open( val input: Input, val isLoading: Boolean, val hasAnimationPlayed: Boolean = false, ) : WooPosSearchInputState() { - sealed class Input(val text: String, open val cursorPosition: Int) { + @Parcelize + sealed class Input(val text: String, open val cursorPosition: Int) : Parcelable { + @Parcelize data class Query(val query: String, override val cursorPosition: Int) : Input(query, cursorPosition) + @Parcelize data class Hint(val hint: String) : Input(hint, 0) } } + @Parcelize object Closed : WooPosSearchInputState() } From a5975ebe38d233689455f8c6fdd15385f2975b3e Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 5 May 2025 14:46:20 +0200 Subject: [PATCH 15/15] Fixed formatting --- .../ui/woopos/common/composeui/component/WooPosSearchInput.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt index ba43e4146c7..55872fadc2f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/common/composeui/component/WooPosSearchInput.kt @@ -322,6 +322,7 @@ sealed class WooPosSearchInputState : Parcelable { sealed class Input(val text: String, open val cursorPosition: Int) : Parcelable { @Parcelize data class Query(val query: String, override val cursorPosition: Int) : Input(query, cursorPosition) + @Parcelize data class Hint(val hint: String) : Input(hint, 0) }