diff --git a/feature/timezone/src/main/java/com/sadellie/unitto/timezone/components/UnittoSearchBar.kt b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/UnittoSearchBar.kt similarity index 92% rename from feature/timezone/src/main/java/com/sadellie/unitto/timezone/components/UnittoSearchBar.kt rename to core/ui/src/main/java/com/sadellie/unitto/core/ui/common/UnittoSearchBar.kt index 9b4a44b3..ef4a6de4 100644 --- a/feature/timezone/src/main/java/com/sadellie/unitto/timezone/components/UnittoSearchBar.kt +++ b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/UnittoSearchBar.kt @@ -16,13 +16,14 @@ * along with this program. If not, see . */ -package com.sadellie.unitto.timezone.components +package com.sadellie.unitto.core.ui.common import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.Crossfade import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.text.BasicTextField @@ -46,6 +47,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf 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 @@ -54,7 +56,6 @@ import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import com.sadellie.unitto.core.base.R -import com.sadellie.unitto.core.ui.common.NavigateUpButton @Composable fun UnittoSearchBar( @@ -113,12 +114,14 @@ fun UnittoSearchBar( }, actions = { Crossfade(showSearch) { _showSearch -> - if (_showSearch) { - ClearButton(visible = query.isNotEmpty()) { onQueryChange("") } - searchActions() - } else { - SearchButton { showSearch = true } - noSearchActions() + Row(verticalAlignment = Alignment.CenterVertically) { + if (_showSearch) { + ClearButton(visible = query.isNotEmpty()) { onQueryChange("") } + searchActions() + } else { + SearchButton { showSearch = true } + noSearchActions() + } } } }, diff --git a/feature/timezone/src/main/java/com/sadellie/unitto/timezone/AddTimeZoneScreen.kt b/feature/timezone/src/main/java/com/sadellie/unitto/timezone/AddTimeZoneScreen.kt index a9f08764..ff852e96 100644 --- a/feature/timezone/src/main/java/com/sadellie/unitto/timezone/AddTimeZoneScreen.kt +++ b/feature/timezone/src/main/java/com/sadellie/unitto/timezone/AddTimeZoneScreen.kt @@ -48,7 +48,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.sadellie.unitto.core.base.R import com.sadellie.unitto.data.model.UnittoTimeZone import com.sadellie.unitto.timezone.components.SelectableTimeZone -import com.sadellie.unitto.timezone.components.UnittoSearchBar +import com.sadellie.unitto.core.ui.common.UnittoSearchBar import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import java.time.ZonedDateTime diff --git a/feature/unitslist/src/main/java/com/sadellie/unitto/feature/unitslist/LeftSideScreen.kt b/feature/unitslist/src/main/java/com/sadellie/unitto/feature/unitslist/LeftSideScreen.kt index 44531ac2..4befc26d 100644 --- a/feature/unitslist/src/main/java/com/sadellie/unitto/feature/unitslist/LeftSideScreen.kt +++ b/feature/unitslist/src/main/java/com/sadellie/unitto/feature/unitslist/LeftSideScreen.kt @@ -46,9 +46,10 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.sadellie.unitto.core.base.R +import com.sadellie.unitto.core.ui.common.UnittoSearchBar import com.sadellie.unitto.data.model.AbstractUnit import com.sadellie.unitto.feature.unitslist.components.ChipsRow -import com.sadellie.unitto.feature.unitslist.components.SearchBar +import com.sadellie.unitto.feature.unitslist.components.FavoritesButton import com.sadellie.unitto.feature.unitslist.components.SearchPlaceholder import com.sadellie.unitto.feature.unitslist.components.UnitGroupHeader import com.sadellie.unitto.feature.unitslist.components.UnitListItem @@ -91,15 +92,18 @@ internal fun LeftSideScreen( Column( Modifier.background(chipsBackground.value) ) { - SearchBar( + UnittoSearchBar( + query = uiState.value.searchQuery, + onQueryChange = { viewModel.onSearchQueryChange(it, false) }, + navigateUp = navigateUp, title = stringResource(R.string.units_screen_from), - value = uiState.value.searchQuery, - onValueChange = { viewModel.onSearchQueryChange(it, false) }, - favoritesOnly = uiState.value.favoritesOnly, - favoriteAction = { viewModel.toggleFavoritesOnly(false) }, - navigateUpAction = navigateUp, - focusManager = focusManager, - scrollBehavior = scrollBehavior + placeholder = stringResource(R.string.search_bar_placeholder), + noSearchActions = { + FavoritesButton( + favoritesOnly = uiState.value.favoritesOnly, + favoriteAction = { viewModel.toggleFavoritesOnly(false) } + ) + } ) ChipsRow( chosenUnitGroup = uiState.value.chosenUnitGroup, diff --git a/feature/unitslist/src/main/java/com/sadellie/unitto/feature/unitslist/RightSideScreen.kt b/feature/unitslist/src/main/java/com/sadellie/unitto/feature/unitslist/RightSideScreen.kt index d334d101..396d1a2d 100644 --- a/feature/unitslist/src/main/java/com/sadellie/unitto/feature/unitslist/RightSideScreen.kt +++ b/feature/unitslist/src/main/java/com/sadellie/unitto/feature/unitslist/RightSideScreen.kt @@ -33,12 +33,13 @@ import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.stringResource import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.sadellie.unitto.core.base.R +import com.sadellie.unitto.core.ui.common.UnittoSearchBar import com.sadellie.unitto.core.ui.common.textfield.FormatterSymbols import com.sadellie.unitto.core.ui.common.textfield.formatExpression import com.sadellie.unitto.data.model.AbstractUnit import com.sadellie.unitto.data.model.NumberBaseUnit import com.sadellie.unitto.data.model.UnitGroup -import com.sadellie.unitto.feature.unitslist.components.SearchBar +import com.sadellie.unitto.feature.unitslist.components.FavoritesButton import com.sadellie.unitto.feature.unitslist.components.SearchPlaceholder import com.sadellie.unitto.feature.unitslist.components.UnitGroupHeader import com.sadellie.unitto.feature.unitslist.components.UnitListItem @@ -92,19 +93,18 @@ internal fun RightSideScreen( Scaffold( modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { - SearchBar( + UnittoSearchBar( + query = uiState.value.searchQuery, + onQueryChange = { viewModel.onSearchQueryChange(it, true) }, + navigateUp = navigateUp, title = stringResource(R.string.units_screen_to), - value = uiState.value.searchQuery, - onValueChange = { - viewModel.onSearchQueryChange(it, true) - }, - favoritesOnly = uiState.value.favoritesOnly, - favoriteAction = { - viewModel.toggleFavoritesOnly(true) - }, - navigateUpAction = navigateUp, - focusManager = focusManager, - scrollBehavior = scrollBehavior + placeholder = stringResource(R.string.search_bar_placeholder), + noSearchActions = { + FavoritesButton( + favoritesOnly = uiState.value.favoritesOnly, + favoriteAction = { viewModel.toggleFavoritesOnly(true) } + ) + } ) } ) { paddingValues -> diff --git a/feature/unitslist/src/main/java/com/sadellie/unitto/feature/unitslist/components/FavoritesButton.kt b/feature/unitslist/src/main/java/com/sadellie/unitto/feature/unitslist/components/FavoritesButton.kt new file mode 100644 index 00000000..daab4773 --- /dev/null +++ b/feature/unitslist/src/main/java/com/sadellie/unitto/feature/unitslist/components/FavoritesButton.kt @@ -0,0 +1,53 @@ +/* + * Unitto is a unit converter for Android + * Copyright (c) 2022-2023 Elshan Agaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sadellie.unitto.feature.unitslist.components + +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.SizeTransform +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut +import androidx.compose.animation.togetherWith +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Favorite +import androidx.compose.material.icons.filled.FavoriteBorder +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import com.sadellie.unitto.core.base.R + +@Composable +internal fun FavoritesButton( + favoritesOnly: Boolean, + favoriteAction: () -> Unit +) { + IconButton(onClick = favoriteAction) { + AnimatedContent( + targetState = favoritesOnly, + transitionSpec = { + (scaleIn() togetherWith scaleOut()).using(SizeTransform(clip = false)) + } + ) { + Icon( + if (it) Icons.Filled.Favorite else Icons.Filled.FavoriteBorder, + contentDescription = stringResource(R.string.favorite_button_description) + ) + } + } +} diff --git a/feature/unitslist/src/main/java/com/sadellie/unitto/feature/unitslist/components/SearchBar.kt b/feature/unitslist/src/main/java/com/sadellie/unitto/feature/unitslist/components/SearchBar.kt deleted file mode 100644 index 6e78872c..00000000 --- a/feature/unitslist/src/main/java/com/sadellie/unitto/feature/unitslist/components/SearchBar.kt +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Unitto is a unit converter for Android - * Copyright (c) 2022-2023 Elshan Agaev - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.sadellie.unitto.feature.unitslist.components - -import android.annotation.SuppressLint -import androidx.activity.compose.BackHandler -import androidx.compose.animation.AnimatedContent -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.Crossfade -import androidx.compose.animation.SizeTransform -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.scaleIn -import androidx.compose.animation.scaleOut -import androidx.compose.animation.togetherWith -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.text.BasicTextField -import androidx.compose.foundation.text.KeyboardActionScope -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Favorite -import androidx.compose.material.icons.filled.FavoriteBorder -import androidx.compose.material.icons.filled.Search -import androidx.compose.material.icons.outlined.Clear -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarScrollBehavior -import androidx.compose.runtime.Composable -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 -import androidx.compose.ui.draw.alpha -import androidx.compose.ui.focus.FocusManager -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.input.ImeAction -import com.sadellie.unitto.core.base.R -import com.sadellie.unitto.core.ui.common.NavigateUpButton - -/** - * Search bar on the Second screen. Controls what will be shown in the list above this component - * - * @param title Search bar title - * @param value Current query - * @param onValueChange Action to perform when search query changes - * @param favoritesOnly Current filter state: On or Off - * @param favoriteAction Function to toggle favorite filter - * @param navigateUpAction Function to navigate to previous screen - * @param focusManager Used to hide keyboard when leaving unit selection screen - * @param scrollBehavior [TopAppBarScrollBehavior] that is used for collapsing and container color - */ -@Composable -internal fun SearchBar( - title: String, - value: String, - onValueChange: (String) -> Unit, - favoritesOnly: Boolean, - favoriteAction: () -> Unit, - navigateUpAction: () -> Unit, - focusManager: FocusManager, - scrollBehavior: TopAppBarScrollBehavior -) { - var showSearch by rememberSaveable { mutableStateOf(false) } - val focusRequester = remember { FocusRequester() } - - fun stagedNavigateUp() { - if (showSearch) { - // Search text field is open, need to close it and clear search query - onValueChange("") - focusManager.clearFocus() - showSearch = false - } else { - // No search text field is shown, can go back as usual - navigateUpAction() - } - } - - TopAppBar( - title = { - Crossfade(targetState = showSearch) { textFieldShown -> - Row(verticalAlignment = Alignment.CenterVertically) { - if (textFieldShown) { - SearchTextField( - modifier = Modifier - .fillMaxWidth() - .weight(1f) - .focusRequester(focusRequester), - value = value, - onValueChange = onValueChange, - onSearch = { - // Close searchbar if there is nothing in search query and user - // clicks search button on his keyboard - if (value.isEmpty()) { - showSearch = false - } else { - focusManager.clearFocus() - } - } - ) - LaunchedEffect(Unit) { - focusRequester.requestFocus() - } - } else { - Text( - modifier = Modifier - .fillMaxWidth() - .weight(1f), - text = title, - style = MaterialTheme.typography.titleLarge - ) - } - } - } - }, - actions = { - Crossfade(targetState = showSearch) { - Row( - verticalAlignment = Alignment.CenterVertically - ) { - when (it) { - false -> { - // Search button - SearchButton { - onValueChange("") - showSearch = true - } - // Favorites button - FavoritesButton(favoritesOnly, favoriteAction) - } - true -> { - // Clear button - ClearButton(value.isNotBlank()) { onValueChange("") } - } - } - } - } - }, - navigationIcon = { - NavigateUpButton { stagedNavigateUp() } - }, - scrollBehavior = scrollBehavior - ) - - BackHandler { stagedNavigateUp() } -} - -@Composable -private fun SearchTextField( - modifier: Modifier, - value: String, - onValueChange: (String) -> Unit, - onSearch: KeyboardActionScope.() -> Unit -) { - BasicTextField( - modifier = modifier, - value = value, - onValueChange = onValueChange, - singleLine = true, - textStyle = MaterialTheme.typography.titleLarge.copy(color = MaterialTheme.colorScheme.onSurface), - cursorBrush = SolidColor(MaterialTheme.colorScheme.onSurface), - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), - keyboardActions = KeyboardActions(onSearch = onSearch), - decorationBox = { innerTextField -> - innerTextField() - // Showing placeholder only when there is query is empty - value.ifEmpty { - Text( - modifier = Modifier.alpha(0.7f), - text = stringResource(R.string.search_bar_placeholder), - style = MaterialTheme.typography.titleLarge, - color = MaterialTheme.colorScheme.onSurface - ) - } - } - ) -} - -@Composable -private fun SearchButton( - onClick: () -> Unit -) { - IconButton(onClick) { - Icon( - Icons.Default.Search, - contentDescription = stringResource(R.string.search_button_description) - ) - } -} - -@SuppressLint("UnusedContentLambdaTargetStateParameter") -@Composable -private fun FavoritesButton( - favoritesOnly: Boolean, - favoriteAction: () -> Unit -) { - IconButton(onClick = favoriteAction) { - AnimatedContent( - targetState = favoritesOnly, - transitionSpec = { - (scaleIn() togetherWith scaleOut()).using(SizeTransform(clip = false)) - } - ) { - Icon( - if (favoritesOnly) Icons.Filled.Favorite else Icons.Filled.FavoriteBorder, - contentDescription = stringResource(R.string.favorite_button_description) - ) - } - } -} - -@Composable -private fun ClearButton( - visible: Boolean, - onClick: () -> Unit -) { - IconButton(onClick = onClick) { - AnimatedVisibility( - visible = visible, - enter = fadeIn(), - exit = fadeOut() - ) { - Icon( - imageVector = Icons.Outlined.Clear, - contentDescription = stringResource(R.string.clear_input_description) - ) - } - } -}