Skip to content

Commit

Permalink
Merge branch 'missing-relay-list-in-location-selection-view-droid-574'
Browse files Browse the repository at this point in the history
  • Loading branch information
Pururun committed Jan 19, 2024
2 parents c5ad442 + 7efda2d commit e0d794c
Show file tree
Hide file tree
Showing 9 changed files with 207 additions and 143 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import io.mockk.MockKAnnotations
import io.mockk.mockk
import io.mockk.verify
import net.mullvad.mullvadvpn.compose.setContentWithTheme
import net.mullvad.mullvadvpn.compose.state.RelayListState
import net.mullvad.mullvadvpn.compose.state.SelectLocationUiState
import net.mullvad.mullvadvpn.compose.test.CIRCULAR_PROGRESS_INDICATOR
import net.mullvad.mullvadvpn.model.Constraint
Expand Down Expand Up @@ -54,9 +55,12 @@ class SelectLocationScreenTest {
setContentWithTheme {
SelectLocationScreen(
uiState =
SelectLocationUiState.ShowData(
countries = DUMMY_RELAY_COUNTRIES,
selectedRelay = null,
SelectLocationUiState.Data(
relayListState =
RelayListState.RelayList(
countries = DUMMY_RELAY_COUNTRIES,
selectedRelay = null
),
selectedOwnership = null,
selectedProvidersCount = 0,
searchTerm = ""
Expand Down Expand Up @@ -91,9 +95,12 @@ class SelectLocationScreenTest {
setContentWithTheme {
SelectLocationScreen(
uiState =
SelectLocationUiState.ShowData(
countries = updatedDummyList,
selectedRelay = updatedDummyList[0].cities[0].relays[0],
SelectLocationUiState.Data(
relayListState =
RelayListState.RelayList(
countries = updatedDummyList,
selectedRelay = updatedDummyList[0].cities[0].relays[0]
),
selectedOwnership = null,
selectedProvidersCount = 0,
searchTerm = ""
Expand All @@ -118,9 +125,12 @@ class SelectLocationScreenTest {
setContentWithTheme {
SelectLocationScreen(
uiState =
SelectLocationUiState.ShowData(
countries = emptyList(),
selectedRelay = null,
SelectLocationUiState.Data(
relayListState =
RelayListState.RelayList(
countries = emptyList(),
selectedRelay = null
),
selectedOwnership = null,
selectedProvidersCount = 0,
searchTerm = ""
Expand All @@ -146,9 +156,8 @@ class SelectLocationScreenTest {
setContentWithTheme {
SelectLocationScreen(
uiState =
SelectLocationUiState.ShowData(
countries = emptyList(),
selectedRelay = null,
SelectLocationUiState.Data(
relayListState = RelayListState.Empty,
selectedOwnership = null,
selectedProvidersCount = 0,
searchTerm = mockSearchString
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.Icon
Expand Down Expand Up @@ -45,6 +45,7 @@ import net.mullvad.mullvadvpn.compose.component.textResource
import net.mullvad.mullvadvpn.compose.constant.ContentType
import net.mullvad.mullvadvpn.compose.destinations.FilterScreenDestination
import net.mullvad.mullvadvpn.compose.extensions.toAnnotatedString
import net.mullvad.mullvadvpn.compose.state.RelayListState
import net.mullvad.mullvadvpn.compose.state.SelectLocationUiState
import net.mullvad.mullvadvpn.compose.test.CIRCULAR_PROGRESS_INDICATOR
import net.mullvad.mullvadvpn.compose.textfield.SearchTextField
Expand All @@ -62,12 +63,15 @@ import org.koin.androidx.compose.koinViewModel
@Composable
private fun PreviewSelectLocationScreen() {
val state =
SelectLocationUiState.ShowData(
SelectLocationUiState.Data(
searchTerm = "",
countries = listOf(RelayCountry("Country 1", "Code 1", false, emptyList())),
selectedRelay = null,
selectedOwnership = null,
selectedProvidersCount = 0
selectedProvidersCount = 0,
relayListState =
RelayListState.RelayList(
countries = listOf(RelayCountry("Country 1", "Code 1", false, emptyList())),
selectedRelay = null,
)
)
AppTheme {
SelectLocationScreen(
Expand Down Expand Up @@ -141,7 +145,7 @@ fun SelectLocationScreen(

when (uiState) {
SelectLocationUiState.Loading -> {}
is SelectLocationUiState.ShowData -> {
is SelectLocationUiState.Data -> {
if (uiState.hasFilter) {
FilterCell(
ownershipFilter = uiState.selectedOwnership,
Expand All @@ -163,12 +167,16 @@ fun SelectLocationScreen(
}
Spacer(modifier = Modifier.height(height = Dimens.verticalSpace))
val lazyListState = rememberLazyListState()
if (uiState is SelectLocationUiState.ShowData && uiState.selectedRelay != null) {
LaunchedEffect(uiState.selectedRelay) {
if (
uiState is SelectLocationUiState.Data &&
uiState.relayListState is RelayListState.RelayList &&
uiState.relayListState.selectedRelay != null
) {
LaunchedEffect(uiState.relayListState.selectedRelay) {
val index =
uiState.countries.indexOfFirst {
it.location.location.country ==
uiState.selectedRelay.location.location.country
uiState.relayListState.countries.indexOfFirst { relayCountry ->
relayCountry.location.location.country ==
uiState.relayListState.selectedRelay.location.location.country
}

lazyListState.scrollToItem(index)
Expand All @@ -187,73 +195,86 @@ fun SelectLocationScreen(
) {
when (uiState) {
SelectLocationUiState.Loading -> {
item(contentType = ContentType.PROGRESS) {
MullvadCircularProgressIndicatorLarge(
Modifier.testTag(CIRCULAR_PROGRESS_INDICATOR)
)
}
loading()
}
is SelectLocationUiState.ShowData -> {
if (uiState.countries.isEmpty()) {
item(contentType = ContentType.EMPTY_TEXT) {
val firstRow =
HtmlCompat.fromHtml(
textResource(
id = R.string.select_location_empty_text_first_row,
uiState.searchTerm
),
HtmlCompat.FROM_HTML_MODE_COMPACT
)
.toAnnotatedString(boldFontWeight = FontWeight.ExtraBold)
val secondRow =
textResource(
id = R.string.select_location_empty_text_second_row
)
Column(
modifier =
Modifier.padding(
horizontal = Dimens.selectLocationTitlePadding
),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = firstRow,
style = MaterialTheme.typography.labelMedium,
textAlign = TextAlign.Center,
color = MaterialTheme.colorScheme.onSecondary,
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
Text(
text = secondRow,
style = MaterialTheme.typography.labelMedium,
textAlign = TextAlign.Center,
color = MaterialTheme.colorScheme.onSecondary
)
}
}
} else {
items(
count = uiState.countries.size,
key = { index -> uiState.countries[index].hashCode() },
contentType = { ContentType.ITEM }
) { index ->
val country = uiState.countries[index]
RelayLocationCell(
relay = country,
selectedItem = uiState.selectedRelay,
onSelectRelay = onSelectRelay,
modifier = Modifier.animateContentSize()
)
}
}
is SelectLocationUiState.Data -> {
relayList(
relayListState = uiState.relayListState,
searchTerm = uiState.searchTerm,
onSelectRelay = onSelectRelay
)
}
}
}
}
}
}

private fun LazyListScope.loading() {
item(contentType = ContentType.PROGRESS) {
MullvadCircularProgressIndicatorLarge(Modifier.testTag(CIRCULAR_PROGRESS_INDICATOR))
}
}

private fun LazyListScope.relayList(
relayListState: RelayListState,
searchTerm: String,
onSelectRelay: (item: RelayItem) -> Unit
) {
when (relayListState) {
is RelayListState.RelayList -> {
items(
count = relayListState.countries.size,
key = { index -> relayListState.countries[index].hashCode() },
contentType = { ContentType.ITEM }
) { index ->
val country = relayListState.countries[index]
RelayLocationCell(
relay = country,
selectedItem = relayListState.selectedRelay,
onSelectRelay = onSelectRelay,
modifier = Modifier.animateContentSize()
)
}
}
RelayListState.Empty -> {
if (searchTerm.isNotEmpty())
item(contentType = ContentType.EMPTY_TEXT) {
val firstRow =
HtmlCompat.fromHtml(
textResource(
id = R.string.select_location_empty_text_first_row,
searchTerm
),
HtmlCompat.FROM_HTML_MODE_COMPACT
)
.toAnnotatedString(boldFontWeight = FontWeight.ExtraBold)
val secondRow =
textResource(id = R.string.select_location_empty_text_second_row)
Column(
modifier = Modifier.padding(horizontal = Dimens.selectLocationTitlePadding),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = firstRow,
style = MaterialTheme.typography.labelMedium,
textAlign = TextAlign.Center,
color = MaterialTheme.colorScheme.onSecondary,
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
Text(
text = secondRow,
style = MaterialTheme.typography.labelMedium,
textAlign = TextAlign.Center,
color = MaterialTheme.colorScheme.onSecondary
)
}
}
}
}
}

suspend fun LazyListState.animateScrollAndCentralizeItem(index: Int) {
val itemInfo = this.layoutInfo.visibleItemsInfo.firstOrNull { it.index == index }
if (itemInfo != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ fun Ownership?.toOwnershipConstraint(): Constraint<Ownership> =
else -> Constraint.Only(this)
}

fun Constraint<Providers>.toSelectedProviders(allProviders: List<Provider>): List<Provider> =
fun Constraint<Providers>.toSelectedProviders(allProviders: List<Provider>): List<Provider>? =
when (this) {
is Constraint.Any -> allProviders
is Constraint.Any -> null
is Constraint.Only ->
this.value.providers.toList().mapNotNull { providerName ->
allProviders.firstOrNull { it.name == providerName }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,19 @@ sealed interface SelectLocationUiState {

data object Loading : SelectLocationUiState

data class ShowData(
data class Data(
val searchTerm: String,
val countries: List<RelayCountry>,
val selectedRelay: RelayItem?,
val selectedOwnership: Ownership?,
val selectedProvidersCount: Int?
val selectedProvidersCount: Int?,
val relayListState: RelayListState
) : SelectLocationUiState {
val hasFilter: Boolean = (selectedProvidersCount != null || selectedOwnership != null)
}
}

sealed interface RelayListState {
data object Empty : RelayListState

data class RelayList(val countries: List<RelayCountry>, val selectedRelay: RelayItem?) :
RelayListState
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ class RelayListListener(
// not be a relay list since the fetching of a relay list would be done before the
// event stream is available.
.onStart { messageHandler.trySendRequest(Request.FetchRelayList) }
.stateIn(CoroutineScope(dispatcher), SharingStarted.Eagerly, defaultRelayList())
.stateIn(
CoroutineScope(dispatcher),
SharingStarted.WhileSubscribed(),
defaultRelayList()
)

fun updateSelectedRelayLocation(value: GeographicLocationConstraint) {
messageHandler.trySendRequest(Request.SetRelayLocation(value))
Expand All @@ -49,5 +53,9 @@ class RelayListListener(
messageHandler.trySendRequest(Request.SetOwnershipAndProviders(ownership, providers))
}

fun fetchRelayList() {
messageHandler.trySendRequest(Request.FetchRelayList)
}

private fun defaultRelayList() = RelayList(ArrayList(), WireguardEndpointData(ArrayList()))
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ class RelayListUseCase(

fun selectedRelayItem(): Flow<RelayItem?> = relayListWithSelection().map { it.selectedItem }

fun fetchRelayList() {
relayListListener.fetchRelayList()
}

private fun List<RelayCountry>.findSelectedRelayItem(
relaySettings: RelaySettings?,
): RelayItem? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class FilterViewModel(
selectedConstraintProviders.toSelectedProviders(allProviders)
}
.first()
?: emptyList()

val ownershipConstraint = relayListFilterUseCase.selectedOwnership().first()
selectedOwnership.value = ownershipConstraint.toNullableOwnership()
Expand Down
Loading

0 comments on commit e0d794c

Please sign in to comment.