diff --git a/app/src/main/java/ru/spbu/depnav/ui/component/MapSearchBar.kt b/app/src/main/java/ru/spbu/depnav/ui/component/MapSearchBar.kt index af8d1e6f..7f7b8ef1 100644 --- a/app/src/main/java/ru/spbu/depnav/ui/component/MapSearchBar.kt +++ b/app/src/main/java/ru/spbu/depnav/ui/component/MapSearchBar.kt @@ -73,7 +73,14 @@ private val ACTIVATION_EXIT_SPEC = tween( easing = CubicBezierEasing(0.0f, 1.0f, 0.0f, 1.0f) ) +/** + * Search bar for querying map markers on [ru.spbu.depnav.ui.screen.MapScreen]. + */ @Composable +@Suppress( + "LongMethod", // No point in further shrinking + "LongParameterList" // Considered OK for composables +) @OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class) fun MapSearchBar( query: String, @@ -116,66 +123,19 @@ fun MapSearchBar( .then(modifier), placeholder = { Text(stringResource(R.string.search_markers), maxLines = 1) }, leadingIcon = { - AnimatedContent( - active, - modifier = Modifier.padding(start = innerStartPadding), - label = "Map search bar leading icon change" - ) { active -> - if (active) { - IconButton(onClick = { onActiveChange(false) }) { - Icon( - Icons.Rounded.ArrowBack, - contentDescription = stringResource(R.string.label_navigate_back) - ) - } - } else { - Icon( - Icons.Rounded.Search, - contentDescription = null, - // To have the same size as the back button above - modifier = Modifier.minimumInteractiveComponentSize() - ) - } + AnimatedLeadingIcon(active, modifier = Modifier.padding(start = innerStartPadding)) { + onActiveChange(false) } }, trailingIcon = { - AnimatedContent( - active to query.isEmpty(), - modifier = Modifier.padding(end = innerEndPadding), - transitionSpec = { fadeIn() togetherWith fadeOut() }, - label = "Map search bar trailing icon change" - ) { (active, emptyQuery) -> - if (active) { - if (emptyQuery) { - Spacer(modifier = Modifier.minimumInteractiveComponentSize()) - } else { - IconButton( - onClick = { onQueryChange("") } - ) { - Icon( - Icons.Rounded.Clear, - contentDescription = stringResource(R.string.label_clear_text_field) - ) - } - } - } else { - Row { - IconButton(onClick = onInfoClick) { - Icon( - Icons.Rounded.Info, - contentDescription = stringResource(R.string.label_open_map_info) - ) - } - - IconButton(onClick = onSettingsClick) { - Icon( - Icons.Rounded.Settings, - contentDescription = stringResource(R.string.label_open_settings) - ) - } - } - } - } + AnimatedTrailingIcons( + active, + query.isEmpty(), + onClearClick = { onQueryChange("") }, + onInfoClick = onInfoClick, + onSettingsClick = onSettingsClick, + modifier = Modifier.padding(end = innerEndPadding) + ) } ) { val keyboard = LocalSoftwareKeyboardController.current @@ -203,3 +163,79 @@ fun MapSearchBar( ) } } + +@Composable +private fun AnimatedLeadingIcon( + searchBarActive: Boolean, + modifier: Modifier = Modifier, + onNavigateBackClick: () -> Unit +) { + AnimatedContent( + searchBarActive, + modifier = modifier, + label = "Map search bar leading icon change" + ) { showBackButton -> + if (showBackButton) { + IconButton(onClick = onNavigateBackClick) { + Icon( + Icons.Rounded.ArrowBack, + contentDescription = stringResource(R.string.label_navigate_back) + ) + } + } else { + Icon( + Icons.Rounded.Search, + contentDescription = null, + // To have the same size as the back button above + modifier = Modifier.minimumInteractiveComponentSize() + ) + } + } +} + +@Composable +@Suppress("LongParameterList") // Considered OK for composables +private fun AnimatedTrailingIcons( + searchBarActive: Boolean, + queryEmpty: Boolean, + onClearClick: () -> Unit, + onInfoClick: () -> Unit, + onSettingsClick: () -> Unit, + modifier: Modifier = Modifier +) { + AnimatedContent( + searchBarActive to queryEmpty, + modifier = modifier, + transitionSpec = { fadeIn() togetherWith fadeOut() }, + label = "Map search bar trailing icon change" + ) { (active, emptyQuery) -> + if (active) { + if (emptyQuery) { + Spacer(modifier = Modifier.minimumInteractiveComponentSize()) + } else { + IconButton(onClick = onClearClick) { + Icon( + Icons.Rounded.Clear, + contentDescription = stringResource(R.string.label_clear_text_field) + ) + } + } + } else { + Row { + IconButton(onClick = onInfoClick) { + Icon( + Icons.Rounded.Info, + contentDescription = stringResource(R.string.label_open_map_info) + ) + } + + IconButton(onClick = onSettingsClick) { + Icon( + Icons.Rounded.Settings, + contentDescription = stringResource(R.string.label_open_settings) + ) + } + } + } + } +} diff --git a/app/src/main/java/ru/spbu/depnav/ui/screen/MapScreen.kt b/app/src/main/java/ru/spbu/depnav/ui/screen/MapScreen.kt index 98e19b1f..a0b599ad 100644 --- a/app/src/main/java/ru/spbu/depnav/ui/screen/MapScreen.kt +++ b/app/src/main/java/ru/spbu/depnav/ui/screen/MapScreen.kt @@ -136,6 +136,7 @@ fun MapScreen(vm: MapViewModel = viewModel()) { } @Composable +@Suppress("LongParameterList") // Considered OK for composables private fun BoxScope.AnimatedSearchBar( visible: Boolean, query: String, diff --git a/app/src/main/java/ru/spbu/depnav/ui/viewmodel/MapViewModel.kt b/app/src/main/java/ru/spbu/depnav/ui/viewmodel/MapViewModel.kt index e308cd5f..4ae5ee53 100644 --- a/app/src/main/java/ru/spbu/depnav/ui/viewmodel/MapViewModel.kt +++ b/app/src/main/java/ru/spbu/depnav/ui/viewmodel/MapViewModel.kt @@ -123,16 +123,18 @@ class MapViewModel @Inject constructor( val searchQuery: String = "", val searchResults: Map = emptyMap() ) { - fun toMapUiState(): MapUiState = when (mapState) { - null -> MapUiState.Loading - else -> MapUiState.Ready( - mapState = mapState, - floorsNum = floors.size, - currentFloor = currentFloorId, - pinnedMarker = pinnedMarker, - showOnMapUi = showOnMapUi - ) - } + fun toMapUiState() = + if (mapState == null) { + MapUiState.Loading + } else { + MapUiState.Ready( + mapState = mapState, + floorsNum = floors.size, + currentFloor = currentFloorId, + pinnedMarker = pinnedMarker, + showOnMapUi = showOnMapUi + ) + } fun toSearchUiState() = SearchUiState(query = searchQuery, results = searchResults) } @@ -388,9 +390,12 @@ class MapViewModel @Inject constructor( } } +/** Describes states of map UI. */ sealed interface MapUiState { + /** Map has not yet been loaded. */ data object Loading : MapUiState + /** Map has been loaded. */ data class Ready( /** State of the map. */ val mapState: MapState, @@ -405,7 +410,16 @@ sealed interface MapUiState { ) : MapUiState } +/** Map markers search UI state. */ data class SearchUiState( + /** Marker search query entered by the user. */ val query: String = "", + /** + * Either the actual results of the query or a history of previous searches if the query was + * empty. + * + * Note that these result mey correspond not to the current query, but to some query in the + * past. + * */ val results: Map = emptyMap() )