diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/FilterCell.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/FilterCell.kt index 1a70c6c001cd..3865c874cb91 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/FilterCell.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/FilterCell.kt @@ -1,6 +1,7 @@ package net.mullvad.mullvadvpn.compose.cell import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -40,9 +41,9 @@ fun FilterRow( horizontal = Dimens.searchFieldHorizontalPadding, ) .fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(Dimens.chipSpace) ) { Text( - modifier = Modifier.padding(end = Dimens.filterTittlePadding), text = stringResource(id = R.string.filtered), color = MaterialTheme.colorScheme.onPrimary, style = MaterialTheme.typography.labelMedium) diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SelectLocationScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SelectLocationScreen.kt index 8083e23162d8..9611aa84b1e8 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SelectLocationScreen.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SelectLocationScreen.kt @@ -1,6 +1,7 @@ package net.mullvad.mullvadvpn.compose.screen import android.content.Context +import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.gestures.animateScrollBy @@ -47,6 +48,8 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.dropUnlessResumed +import arrow.core.toNonEmptyListOrNull +import co.touchlab.kermit.Logger import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.generated.destinations.CreateCustomListDestination @@ -88,6 +91,7 @@ import net.mullvad.mullvadvpn.compose.screen.BottomSheetState.ShowEditCustomList import net.mullvad.mullvadvpn.compose.screen.BottomSheetState.ShowLocationBottomSheet import net.mullvad.mullvadvpn.compose.state.RelayListItem import net.mullvad.mullvadvpn.compose.state.SelectLocationUiState +import net.mullvad.mullvadvpn.compose.state.SelectLocationUiState.Content import net.mullvad.mullvadvpn.compose.test.CIRCULAR_PROGRESS_INDICATOR import net.mullvad.mullvadvpn.compose.test.SELECT_LOCATION_CUSTOM_LIST_BOTTOM_SHEET_TEST_TAG import net.mullvad.mullvadvpn.compose.test.SELECT_LOCATION_CUSTOM_LIST_HEADER_TEST_TAG @@ -95,7 +99,6 @@ import net.mullvad.mullvadvpn.compose.test.SELECT_LOCATION_LOCATION_BOTTOM_SHEET import net.mullvad.mullvadvpn.compose.textfield.SearchTextField import net.mullvad.mullvadvpn.compose.transitions.SelectLocationTransition import net.mullvad.mullvadvpn.compose.util.CollectSideEffectWithLifecycle -import net.mullvad.mullvadvpn.compose.util.RunOnKeyChange import net.mullvad.mullvadvpn.compose.util.showSnackbarImmediately import net.mullvad.mullvadvpn.lib.model.CustomList import net.mullvad.mullvadvpn.lib.model.CustomListId @@ -120,7 +123,7 @@ private fun PreviewSelectLocationScreen() { emptyList(), relayListItems = emptyList(), customLists = emptyList(), - ) + ) AppTheme { SelectLocationScreen( state = state, @@ -147,6 +150,8 @@ fun SelectLocation( val snackbarHostState = remember { SnackbarHostState() } val context = LocalContext.current + val lazyListState = rememberLazyListState() + CollectSideEffectWithLifecycle(vm.uiSideEffect) { when (it) { SelectLocationSideEffect.CloseScreen -> backNavigator.navigateBack(result = true) @@ -166,6 +171,16 @@ fun SelectLocation( message = context.getString(R.string.error_occurred), duration = SnackbarDuration.Short) } + + is SelectLocationSideEffect.CenterOnItem -> { + val index = state.indexOfSelectedRelayItem() + Logger.d("CENTER ON ITEM $index") + + if (index >= 0) { + lazyListState.scrollToItem(index) + lazyListState.animateScrollAndCentralizeItem(index) + } + } } } @@ -182,6 +197,7 @@ fun SelectLocation( SelectLocationScreen( state = state, + lazyListState = lazyListState, snackbarHostState = snackbarHostState, onSelectRelay = vm::selectRelay, onSearchTermInput = vm::onSearchTermInput, @@ -226,6 +242,7 @@ fun SelectLocation( @Composable fun SelectLocationScreen( state: SelectLocationUiState, + lazyListState: LazyListState = rememberLazyListState(), snackbarHostState: SnackbarHostState = remember { SnackbarHostState() }, onSelectRelay: (item: RelayItem) -> Unit = {}, onSearchTermInput: (searchTerm: String) -> Unit = {}, @@ -269,15 +286,11 @@ fun SelectLocationScreen( Column(modifier = Modifier.padding(it).background(backgroundColor).fillMaxSize()) { SelectLocationTopBar(onBackClick = onBackClick, onFilterClick = onFilterClick) - when (state) { - SelectLocationUiState.Loading -> {} - is SelectLocationUiState.Content -> { - if (state.filterChips.isNotEmpty()) { - FilterRow( - filters = state.filterChips, - removeOwnershipFilter, - removeProviderFilter) - } + val filterChips = (state as? Content)?.filterChips?.toNonEmptyListOrNull() + AnimatedContent(filterChips) { + if (it != null) { + FilterRow( + filters = it.toList(), removeOwnershipFilter, removeProviderFilter) } } @@ -292,17 +305,6 @@ fun SelectLocationScreen( onSearchTermInput.invoke(searchString) } Spacer(modifier = Modifier.height(height = Dimens.verticalSpace)) - val lazyListState = rememberLazyListState() - -// val selectedItemCode = (state as? SelectLocationUiState.Content)?.selectedItem ?: "" -// RunOnKeyChange(key = selectedItemCode) { -// val index = state.indexOfSelectedRelayItem() -// -// if (index >= 0) { -// lazyListState.scrollToItem(index) -// lazyListState.animateScrollAndCentralizeItem(index) -// } -// } LazyColumn( modifier = 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 cf6837dc48de..24aa99c12ec8 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 @@ -3,10 +3,17 @@ package net.mullvad.mullvadvpn.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import arrow.core.raise.either +import co.touchlab.kermit.Logger import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flatMap +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.receiveAsFlow @@ -18,6 +25,7 @@ import net.mullvad.mullvadvpn.compose.communication.LocationsChanged import net.mullvad.mullvadvpn.compose.state.RelayListItem import net.mullvad.mullvadvpn.compose.state.RelayListItem.CustomListHeader import net.mullvad.mullvadvpn.compose.state.SelectLocationUiState +import net.mullvad.mullvadvpn.compose.state.SelectLocationUiState.Content import net.mullvad.mullvadvpn.compose.state.toNullableOwnership import net.mullvad.mullvadvpn.compose.state.toSelectedProviders import net.mullvad.mullvadvpn.lib.model.Constraint @@ -144,7 +152,7 @@ class SelectLocationViewModel( relayListItems, filterChips, customLists -> - SelectLocationUiState.Content( + Content( searchTerm = searchTerm, filterChips = filterChips, relayListItems = relayListItems, @@ -159,6 +167,22 @@ class SelectLocationViewModel( private val _uiSideEffect = Channel() val uiSideEffect = _uiSideEffect.receiveAsFlow() + init { + viewModelScope.launch { + uiState + .map { it is Content } + .filter { it } + .distinctUntilChanged() + .flatMapLatest { relayListRepository.selectedLocation.filterNotNull() } + .filterIsInstance>() + .map { it.value } + .collect { + Logger.d("SELECTED LOCATION CHANGED!") + _uiSideEffect.send(SelectLocationSideEffect.CenterOnItem(it)) + } + } + } + fun createRelayListItems( isSearching: Boolean, selectedItem: RelayItemId?, @@ -386,4 +410,6 @@ sealed interface SelectLocationSideEffect { class LocationRemovedFromCustomList(val result: LocationsChanged) : SelectLocationSideEffect data object GenericError : SelectLocationSideEffect + + data class CenterOnItem(val selectedItem: RelayItemId?) : SelectLocationSideEffect } diff --git a/android/lib/theme/src/main/kotlin/net/mullvad/mullvadvpn/lib/theme/dimensions/Dimensions.kt b/android/lib/theme/src/main/kotlin/net/mullvad/mullvadvpn/lib/theme/dimensions/Dimensions.kt index d38813f59674..922e97073b11 100644 --- a/android/lib/theme/src/main/kotlin/net/mullvad/mullvadvpn/lib/theme/dimensions/Dimensions.kt +++ b/android/lib/theme/src/main/kotlin/net/mullvad/mullvadvpn/lib/theme/dimensions/Dimensions.kt @@ -41,7 +41,7 @@ data class Dimensions( val dropdownMenuVerticalPadding: Dp = 8.dp, // Used to remove padding from dropdown menu val dropdownMenuBorder: Dp = 1.dp, val expandableCellChevronSize: Dp = 30.dp, - val filterTittlePadding: Dp = 4.dp, + val filterTitlePadding: Dp = 4.dp, val formTextFieldMinHeight: Dp = 72.dp, val iconFailSuccessTopMargin: Dp = 30.dp, val iconHeight: Dp = 44.dp,