Skip to content

[Summit prep] Add a search feature #19

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ fun CountryListAdapter(
viewModel.onCountrySelect(country)
},
onRefreshTap = { viewModel.onRefreshTap() },
onFailOtherTap = { viewModel.onFailOtherTap() }
onFailOtherTap = { viewModel.onFailOtherTap() },
onSearchQueryChange = { query ->
viewModel.updateSearchQuery(query)
},
filteredContinents = viewModel.filteredContinents
)

FloatingAlertNotifier(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
Expand All @@ -15,8 +16,8 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.example.domainmodels.Continent
import com.example.domainmodels.Country
import com.example.feature.countrylist.componenets.CountryListButton
import com.example.feature.countrylist.componenets.CountryListList
import com.example.feature.countrylist.components.CountryListButton
import com.example.feature.countrylist.components.CountryListList
import com.example.features.R
import com.example.uicomponents.library.ProgressIndicator
import com.example.viewmodels.CountryListViewModel
Expand All @@ -26,14 +27,30 @@ fun CountryListPage(
listUiState: CountryListViewModel.UiState,
onCountrySelect: ((Country) -> Unit)? = null,
onRefreshTap: (() -> Unit)? = null,
onFailOtherTap: (() -> Unit)? = null
onFailOtherTap: (() -> Unit)? = null,
onSearchQueryChange: ((String) -> Unit)? = null,
filteredContinents: List<Continent> = emptyList()
) {
Box {
ProgressIndicator(isLoading = listUiState.isLoading)
Column(modifier = Modifier.fillMaxHeight()) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
TextField(
value = listUiState.searchQuery,
onValueChange = { onSearchQueryChange?.invoke(it) },
label = { Text(text = stringResource(R.string.country_list_search_hint)) },
modifier = Modifier.fillMaxWidth(),
singleLine = true
)
}
Column(modifier = Modifier.weight(1f)) {
CountryListList(
list = listUiState.continents,
list = filteredContinents,
onClick = onCountrySelect
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.example.feature.countrylist.componenets
package com.example.feature.countrylist.components

import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.example.feature.countrylist.componenets
package com.example.feature.countrylist.components

import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
Expand Down
1 change: 1 addition & 0 deletions feature/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

<string name="country_details_save_as_favorite">Save as favorite (will always fail)</string>

<string name="country_list_search_hint">Search for a country</string>
<string name="country_list_blocked_error_title">This country is blocked</string>
<string name="country_list_blocked_error_message">Would you like to go to a random country instead?</string>
<string name="country_list_blocked_error_positive_button">Go to random</string>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.example.viewmodels

import androidx.annotation.VisibleForTesting
import com.example.domainmodels.Continent
import com.example.domainmodels.Country
import com.example.domainmodels.ServerStatus
Expand All @@ -20,8 +21,35 @@ class CountryListViewModel(
val error: CountryListError? = null,
val serverStatus: ServerStatus? = null,
val navigationTarget: Country? = null,
val searchQuery: String = "",
)

val filteredContinents: List<Continent>
get() {
val query = _state.value?.searchQuery.orEmpty()
return _state.value?.continents
?.map { continent ->
val filteredCountries = continent.countries.filter {
it.countryName.contains(query, ignoreCase = true) ||
it.regionCode.contains(query, ignoreCase = true)
}
continent.copy(countries = filteredCountries)
}
?.filter { it.countries.isNotEmpty() }
?: emptyList()
}

fun updateSearchQuery(query: String) {
_state.value?.let { currentState ->
_state.onNext(currentState.copy(searchQuery = query))
}
}

@VisibleForTesting
fun setStateForTest(state: UiState) {
_state.onNext(state)
}

private val _state: BehaviorSubject<UiState> = BehaviorSubject.createDefault(UiState())
val state: Observable<UiState> = _state

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.example.viewmodels

import com.example.AutoCloseKoinAfterEachExtension
import com.example.domainmodels.Continent
import com.example.domainmodels.Country
import com.example.domainmodels.ServerStatus
import com.example.interfaces.networkLogicApiMocks
import com.example.logic.logicModule
Expand Down Expand Up @@ -80,4 +82,23 @@ class CountryListViewModelTests: KoinTest {
}
}
}

@Nested
@DisplayName("filteredContinents")
inner class FilteredContinents {
@Test
fun `returns only continents with matching countries`() {
val continents = listOf(
Continent("Europe", listOf(Country("FR"), Country("DE"))),
Continent("Asia", listOf(Country("JP"), Country("CN")))
)
viewModel.setStateForTest(CountryListViewModel.UiState(continents = continents))
viewModel.updateSearchQuery("france")
val filtered = viewModel.filteredContinents
filtered.size.shouldBeEqualTo(1)
filtered[0].name.shouldBeEqualTo("Europe")
filtered[0].countries.size.shouldBeEqualTo(1)
filtered[0].countries[0].regionCode.shouldBeEqualTo("FR")
}
}
}