diff --git a/feature/src/main/java/com/example/feature/countrylist/CountryListAdapter.kt b/feature/src/main/java/com/example/feature/countrylist/CountryListAdapter.kt index 2eef933..7dc99af 100644 --- a/feature/src/main/java/com/example/feature/countrylist/CountryListAdapter.kt +++ b/feature/src/main/java/com/example/feature/countrylist/CountryListAdapter.kt @@ -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( diff --git a/feature/src/main/java/com/example/feature/countrylist/CountryListPage.kt b/feature/src/main/java/com/example/feature/countrylist/CountryListPage.kt index 2fc5ce8..b293ce6 100644 --- a/feature/src/main/java/com/example/feature/countrylist/CountryListPage.kt +++ b/feature/src/main/java/com/example/feature/countrylist/CountryListPage.kt @@ -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 @@ -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 @@ -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 = 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 ) } diff --git a/feature/src/main/java/com/example/feature/countrylist/componenets/CountrySelectingButton.kt b/feature/src/main/java/com/example/feature/countrylist/components/CountrySelectingButton.kt similarity index 93% rename from feature/src/main/java/com/example/feature/countrylist/componenets/CountrySelectingButton.kt rename to feature/src/main/java/com/example/feature/countrylist/components/CountrySelectingButton.kt index 0ba1211..82a73f3 100644 --- a/feature/src/main/java/com/example/feature/countrylist/componenets/CountrySelectingButton.kt +++ b/feature/src/main/java/com/example/feature/countrylist/components/CountrySelectingButton.kt @@ -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 diff --git a/feature/src/main/java/com/example/feature/countrylist/componenets/CountrySelectingList.kt b/feature/src/main/java/com/example/feature/countrylist/components/CountrySelectingList.kt similarity index 97% rename from feature/src/main/java/com/example/feature/countrylist/componenets/CountrySelectingList.kt rename to feature/src/main/java/com/example/feature/countrylist/components/CountrySelectingList.kt index dbeb901..5aea115 100644 --- a/feature/src/main/java/com/example/feature/countrylist/componenets/CountrySelectingList.kt +++ b/feature/src/main/java/com/example/feature/countrylist/components/CountrySelectingList.kt @@ -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 diff --git a/feature/src/main/res/values/strings.xml b/feature/src/main/res/values/strings.xml index 3895d2d..1b1fe68 100644 --- a/feature/src/main/res/values/strings.xml +++ b/feature/src/main/res/values/strings.xml @@ -20,6 +20,7 @@ Save as favorite (will always fail) + Search for a country This country is blocked Would you like to go to a random country instead? Go to random diff --git a/viewmodels/src/main/java/com/example/viewmodels/CountryListViewModel.kt b/viewmodels/src/main/java/com/example/viewmodels/CountryListViewModel.kt index 95d9c52..8e380e4 100644 --- a/viewmodels/src/main/java/com/example/viewmodels/CountryListViewModel.kt +++ b/viewmodels/src/main/java/com/example/viewmodels/CountryListViewModel.kt @@ -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 @@ -20,8 +21,35 @@ class CountryListViewModel( val error: CountryListError? = null, val serverStatus: ServerStatus? = null, val navigationTarget: Country? = null, + val searchQuery: String = "", ) + val filteredContinents: List + 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 = BehaviorSubject.createDefault(UiState()) val state: Observable = _state diff --git a/viewmodels/src/test/kotlin/com/example/viewmodels/CountryListViewModelTests.kt b/viewmodels/src/test/kotlin/com/example/viewmodels/CountryListViewModelTests.kt index 7174d5b..e9aa660 100644 --- a/viewmodels/src/test/kotlin/com/example/viewmodels/CountryListViewModelTests.kt +++ b/viewmodels/src/test/kotlin/com/example/viewmodels/CountryListViewModelTests.kt @@ -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 @@ -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") + } + } }