diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/ExpandableComposeCell.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/ExpandableComposeCell.kt index b19f53445699..d47cfeabfcf9 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/ExpandableComposeCell.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/ExpandableComposeCell.kt @@ -46,6 +46,7 @@ private fun PreviewExpandedEnabledExpandableComposeCell() { fun ExpandableComposeCell( title: String, isExpanded: Boolean, + modifier: Modifier = Modifier, isEnabled: Boolean = true, testTag: String = "", onCellClicked: (Boolean) -> Unit = {}, @@ -55,7 +56,7 @@ fun ExpandableComposeCell( val bodyViewModifier = Modifier BaseCell( - modifier = Modifier.testTag(testTag).focusProperties { canFocus = false }, + modifier = modifier.testTag(testTag).focusProperties { canFocus = false }, headlineContent = { BaseCellTitle( title = title, diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/SelectableCell.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/SelectableCell.kt index a4cd9b954809..dffc15bf1221 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/SelectableCell.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/SelectableCell.kt @@ -37,6 +37,7 @@ private fun PreviewSelectableCell() { fun SelectableCell( title: String, isSelected: Boolean, + modifier: Modifier = Modifier, isEnabled: Boolean = true, iconContentDescription: String? = null, selectedIcon: @Composable RowScope.() -> Unit = { @@ -60,6 +61,7 @@ fun SelectableCell( testTag: String = "" ) { BaseCell( + modifier = modifier, onCellClicked = onCellClicked, isRowEnabled = isEnabled, headlineContent = { diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/FilterScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/FilterScreen.kt index 3c3fe9e05bc0..48a5796aa04b 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/FilterScreen.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/FilterScreen.kt @@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyItemScope import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -36,6 +37,7 @@ import net.mullvad.mullvadvpn.compose.button.ApplyButton import net.mullvad.mullvadvpn.compose.cell.CheckboxCell import net.mullvad.mullvadvpn.compose.cell.ExpandableComposeCell import net.mullvad.mullvadvpn.compose.cell.SelectableCell +import net.mullvad.mullvadvpn.compose.constant.ContentType import net.mullvad.mullvadvpn.compose.extensions.itemWithDivider import net.mullvad.mullvadvpn.compose.extensions.itemsWithDivider import net.mullvad.mullvadvpn.compose.state.RelayFilterState @@ -127,9 +129,9 @@ fun FilterScreen( Box( modifier = Modifier.fillMaxWidth() - .padding(top = Dimens.screenVerticalMargin) .clickable(enabled = false, onClick = onApplyClick) - .background(color = backgroundColor), + .background(color = backgroundColor) + .padding(top = Dimens.screenVerticalMargin), contentAlignment = Alignment.BottomCenter ) { ApplyButton( @@ -146,17 +148,33 @@ fun FilterScreen( }, ) { contentPadding -> LazyColumn(modifier = Modifier.padding(contentPadding).fillMaxSize()) { - itemWithDivider { OwnershipHeader(ownershipExpanded) { ownershipExpanded = it } } + itemWithDivider(key = Keys.OWNERSHIP_TITLE, contentType = ContentType.HEADER) { + OwnershipHeader(ownershipExpanded) { ownershipExpanded = it } + } if (ownershipExpanded) { - item { AnyOwnership(state, onSelectedOwnership) } - itemsWithDivider(state.filteredOwnershipByProviders) { ownership -> + item(key = Keys.OWNERSHIP_ALL, contentType = ContentType.ITEM) { + AnyOwnership(state, onSelectedOwnership) + } + itemsWithDivider( + key = { it.name }, + contentType = { ContentType.ITEM }, + items = state.filteredOwnershipByProviders + ) { ownership -> Ownership(ownership, state, onSelectedOwnership) } } - itemWithDivider { ProvidersHeader(providerExpanded) { providerExpanded = it } } + itemWithDivider(key = Keys.PROVIDERS_TITLE, contentType = ContentType.HEADER) { + ProvidersHeader(providerExpanded) { providerExpanded = it } + } if (providerExpanded) { - itemWithDivider { AllProviders(state, onAllProviderCheckChange) } - itemsWithDivider(state.filteredProvidersByOwnership) { provider -> + itemWithDivider(key = Keys.PROVIDERS_ALL, contentType = ContentType.ITEM) { + AllProviders(state, onAllProviderCheckChange) + } + itemsWithDivider( + key = { it.providerId.value }, + contentType = { ContentType.ITEM }, + items = state.filteredProvidersByOwnership + ) { provider -> Provider(provider, state, onSelectedProvider) } } @@ -165,30 +183,32 @@ fun FilterScreen( } @Composable -private fun OwnershipHeader(expanded: Boolean, onToggleExpanded: (Boolean) -> Unit) { +private fun LazyItemScope.OwnershipHeader(expanded: Boolean, onToggleExpanded: (Boolean) -> Unit) { ExpandableComposeCell( title = stringResource(R.string.ownership), isExpanded = expanded, isEnabled = true, onInfoClicked = null, - onCellClicked = { onToggleExpanded(!expanded) } + onCellClicked = { onToggleExpanded(!expanded) }, + modifier = Modifier.animateItem() ) } @Composable -private fun AnyOwnership( +private fun LazyItemScope.AnyOwnership( state: RelayFilterState, onSelectedOwnership: (ownership: Ownership?) -> Unit ) { SelectableCell( title = stringResource(id = R.string.any), isSelected = state.selectedOwnership == null, - onCellClicked = { onSelectedOwnership(null) } + onCellClicked = { onSelectedOwnership(null) }, + modifier = Modifier.animateItem() ) } @Composable -private fun Ownership( +private fun LazyItemScope.Ownership( ownership: Ownership, state: RelayFilterState, onSelectedOwnership: (ownership: Ownership?) -> Unit @@ -196,35 +216,38 @@ private fun Ownership( SelectableCell( title = stringResource(id = ownership.stringResource()), isSelected = ownership == state.selectedOwnership, - onCellClicked = { onSelectedOwnership(ownership) } + onCellClicked = { onSelectedOwnership(ownership) }, + modifier = Modifier.animateItem() ) } @Composable -private fun ProvidersHeader(expanded: Boolean, onToggleExpanded: (Boolean) -> Unit) { +private fun LazyItemScope.ProvidersHeader(expanded: Boolean, onToggleExpanded: (Boolean) -> Unit) { ExpandableComposeCell( title = stringResource(R.string.providers), isExpanded = expanded, isEnabled = true, onInfoClicked = null, - onCellClicked = { onToggleExpanded(!expanded) } + onCellClicked = { onToggleExpanded(!expanded) }, + modifier = Modifier.animateItem() ) } @Composable -private fun AllProviders( +private fun LazyItemScope.AllProviders( state: RelayFilterState, onAllProviderCheckChange: (isChecked: Boolean) -> Unit ) { CheckboxCell( title = stringResource(R.string.all_providers), checked = state.isAllProvidersChecked, - onCheckedChange = { isChecked -> onAllProviderCheckChange(isChecked) } + onCheckedChange = { isChecked -> onAllProviderCheckChange(isChecked) }, + modifier = Modifier.animateItem() ) } @Composable -private fun Provider( +private fun LazyItemScope.Provider( provider: Provider, state: RelayFilterState, onSelectedProvider: (checked: Boolean, provider: Provider) -> Unit @@ -232,7 +255,8 @@ private fun Provider( CheckboxCell( title = provider.providerId.value, checked = provider in state.selectedProviders, - onCheckedChange = { checked -> onSelectedProvider(checked, provider) } + onCheckedChange = { checked -> onSelectedProvider(checked, provider) }, + modifier = Modifier.animateItem() ) } @@ -241,3 +265,10 @@ private fun Ownership.stringResource(): Int = Ownership.MullvadOwned -> R.string.mullvad_owned_only Ownership.Rented -> R.string.rented_only } + +private object Keys { + const val OWNERSHIP_TITLE = "ownership-title" + const val OWNERSHIP_ALL = "ownership-all" + const val PROVIDERS_TITLE = "providers-title" + const val PROVIDERS_ALL = "providers_all" +}