From d285960d54d3329d7531b5a6cf98d4412584068b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20G=C3=B6ransson?= Date: Tue, 23 Jul 2024 14:12:33 +0200 Subject: [PATCH] working --- .../relaylist/RelayListExtensions.kt | 39 +++-- .../viewmodel/SelectLocationViewModel.kt | 159 ++++++++++-------- 2 files changed, 104 insertions(+), 94 deletions(-) diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayListExtensions.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayListExtensions.kt index f89378e3b80e..94b5ddac0689 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayListExtensions.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayListExtensions.kt @@ -10,34 +10,33 @@ fun List.findByGeoLocationId(geoLocationId: GeoLocat fun List.findByGeoLocationId(geoLocationId: GeoLocationId.City) = flatMap { it.cities }.firstOrNull { it.id == geoLocationId } -fun List.newFilterOnSearch(searchTerm: String): Pair, List> { +fun List.newFilterOnSearch( + searchTerm: String +): Pair, List> { val matchesIds = withDescendants().filter { it.name.contains(searchTerm, ignoreCase = true) }.map { it.id } val expansionSet = matchesIds.flatMap { it.parents() }.toSet() Logger.d("Expansion Set: $expansionSet") - val filteredCountryList = filter { it.id in expansionSet || it.id in matchesIds } - .map { - it.copy( + val filteredCountryList = mapNotNull { country -> + if (country.id in matchesIds) { + country + } else if (country.id in expansionSet) { + country.copy( cities = - it.cities - .filter { - it.id in expansionSet || - it.id in matchesIds || - it.id.country in matchesIds - } - .map { - it.copy( - relays = - it.relays.filter { - it.id in expansionSet || - it.id in matchesIds || - it.id.city in matchesIds || - it.id.country in matchesIds - }) - }) + country.cities.mapNotNull { city -> + if (city.id in matchesIds) { + city + } else if (city.id in expansionSet) { + city.copy( + relays = city.relays.filter { relay -> relay.id in matchesIds }) + } else null + }) + } else { + null } + } return expansionSet to filteredCountryList } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModel.kt index 5fa777ab5644..08c6255d19c7 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModel.kt @@ -6,6 +6,8 @@ import arrow.core.raise.either import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update @@ -45,7 +47,7 @@ class SelectLocationViewModel( customListsRelayItemUseCase: CustomListsRelayItemUseCase, private val customListsRepository: CustomListsRepository, private val customListActionUseCase: CustomListActionUseCase, - filteredRelayListUseCase: FilteredRelayListUseCase, + private val filteredRelayListUseCase: FilteredRelayListUseCase, private val relayListRepository: RelayListRepository ) : ViewModel() { private val _searchTerm = MutableStateFlow(EMPTY_SEARCH_TERM) @@ -62,18 +64,31 @@ class SelectLocationViewModel( } private val _expandedItems = MutableStateFlow(initialExpand()) - private val _searchExpandedItems = MutableStateFlow(setOf()) - private val _searchCollapsedItems = MutableStateFlow(setOf()) + + fun searchRelayListLocations() = + kotlinx.coroutines.flow + .combine( + _searchTerm, + filteredRelayListUseCase(), + ) { searchTerm, relayCountries -> + val isSearching = searchTerm.length >= MIN_SEARCH_LENGTH + if (isSearching) { + val (exp, filteredRelayCountries) = relayCountries.newFilterOnSearch(searchTerm) + exp.map { it.expandKey() }.toSet() to filteredRelayCountries + } else { + initialExpand() to relayCountries + } + } + .onEach { _expandedItems.value = it.first } + .map { it.second } @Suppress("DestructuringDeclarationWithTooManyEntries") val uiState = combine( - filteredRelayListUseCase(), + searchRelayListLocations(), customListsRelayItemUseCase(), relayListRepository.selectedLocation, _expandedItems, - _searchExpandedItems, - _searchCollapsedItems, _searchTerm, relayListFilterRepository.selectedOwnership, availableProvidersUseCase(), @@ -83,8 +98,6 @@ class SelectLocationViewModel( customLists, selectedItem, expandedItems, - searchExpandedItems, - searchCollapsedItems, searchTerm, selectedOwnership, allProviders, @@ -102,17 +115,6 @@ class SelectLocationViewModel( .size } - val isSearching = searchTerm.length >= MIN_SEARCH_LENGTH - val (expansionList, relayCountries1) = - if (isSearching) { - val (exp, filteredRelayCountries) = - relayCountries.newFilterOnSearch(searchTerm) - (searchExpandedItems + exp.map { it.expandKey() } - searchCollapsedItems) to - filteredRelayCountries - } else { - expandedItems to relayCountries - } - val filteredCustomLists = customLists .filterOnSearchTerm(searchTerm) @@ -127,11 +129,18 @@ class SelectLocationViewModel( selectedProvidersCount = selectedProvidersCount, relayListItems = createRelayListItems( - isSearching, - selectedItem.getOrNull(), - filteredCustomLists, - relayCountries1, - expansionList), + searchTerm.length >= MIN_SEARCH_LENGTH, + selectedItem.getOrNull(), + filteredCustomLists, + relayCountries, + expandedItems) + .let { + if (it.isEmpty()) { + listOf(RelayListItem.LocationsEmptyText(searchTerm)) + } else { + it + } + }, customLists = customLists, selectedItem = selectRelayItemId, ) @@ -151,40 +160,57 @@ class SelectLocationViewModel( customLists: List, countries: List, expandedkeys: Set - ): List { + ): List = + createCustomListRelayListItems(isSearching, selectedItem, customLists, expandedkeys) + + createLocationRelayListItems(isSearching, selectedItem, countries, expandedkeys) - val customListItems: List = - if (isSearching && customLists.isEmpty()) emptyList() - else { - listOf(CustomListHeader) + - customLists.flatMap { customList -> - val expanded = customList.id.expandKey() in expandedkeys - val item = - listOf( - RelayListItem.CustomListItem( - customList, - isSelected = selectedItem == customList.id, - expanded)) - - if (expanded) { - item + - customList.locations.flatMap { - createCustomListEntry( - parent = customList.id, item = it, 1, expandedkeys) - } - } else { - item - } + fun createCustomListRelayListItems( + isSearching: Boolean, + selectedItem: RelayItemId?, + customLists: List, + expandedkeys: Set + ): List { + return if (isSearching && customLists.isEmpty()) emptyList() + else { + val customListItems = + customLists.flatMap { customList -> + val expanded = customList.id.expandKey() in expandedkeys + val item = + listOf( + RelayListItem.CustomListItem( + customList, isSelected = selectedItem == customList.id, expanded)) + + if (expanded) { + item + + customList.locations.flatMap { + createCustomListEntry( + parent = customList.id, item = it, 1, expandedkeys) + } + } else { + item } - } - val relayLocations: List = - countries.flatMap { country -> - createGeoLocationEntry(country, selectedItem, expandedkeys = expandedkeys) - } + } + listOf(CustomListHeader) + + customListItems + + RelayListItem.CustomListFooter(customListItems.isNotEmpty()) + } + } - return customListItems + listOf( - RelayListItem.CustomListFooter(customListItems.isNotEmpty()), - RelayListItem.LocationHeader) + relayLocations + fun createLocationRelayListItems( + isSearching: Boolean, + selectedItem: RelayItemId?, + countries: List, + expandedkeys: Set + ): List { + return if (isSearching && countries.isEmpty()) { + emptyList() + } else { + val relayLocations: List = + countries.flatMap { country -> + createGeoLocationEntry(country, selectedItem, expandedkeys = expandedkeys) + } + listOf(RelayListItem.LocationHeader) + relayLocations + } } fun createCustomListEntry( @@ -273,33 +299,18 @@ class SelectLocationViewModel( } fun onToggleExpand(item: RelayItemId, parent: CustomListId? = null, expand: Boolean) { - if (_searchTerm.value.length >= MIN_SEARCH_LENGTH) { + _expandedItems.update { val key = item.expandKey(parent) if (expand) { - _searchExpandedItems.update { it + key } - _searchCollapsedItems.update { it - key } + it + key } else { - _searchExpandedItems.update { it - key } - _searchCollapsedItems.update { it + key } - } - } else { - _expandedItems.update { - val key = item.expandKey(parent) - if (expand) { - it + key - } else { - it - key - } + it - key } } } fun onSearchTermInput(searchTerm: String) { - viewModelScope.launch { - _searchExpandedItems.update { setOf() } - _searchCollapsedItems.update { setOf() } - _searchTerm.emit(searchTerm) - } + viewModelScope.launch { _searchTerm.emit(searchTerm) } } private fun filterSelectedProvidersByOwnership(