summaryrefslogtreecommitdiffhomepage
path: root/android/lib/ui/component/src
diff options
context:
space:
mode:
authorKalle Lindström <karl.lindstrom@mullvad.net>2025-07-22 14:26:22 +0200
committerKalle Lindström <karl.lindstrom@mullvad.net>2025-07-22 14:26:22 +0200
commitb2fc803af349205bc40d7cd00e0a480536c3d09e (patch)
treed603241a7e9ed6284f89704140f02c1a828518cb /android/lib/ui/component/src
parent75501a665b1bb7257cacd79f1eca84c839929725 (diff)
parent526ecbf7d85c8abe7af08daf04dc4bc0c6df109c (diff)
downloadmullvadvpn-b2fc803af349205bc40d7cd00e0a480536c3d09e.tar.xz
mullvadvpn-b2fc803af349205bc40d7cd00e0a480536c3d09e.zip
Merge branch 'implement-recents-support-ui'
Diffstat (limited to 'android/lib/ui/component/src')
-rw-r--r--android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/relaylist/RelayListItem.kt66
-rw-r--r--android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/relaylist/RelayListItemPreviewData.kt49
-rw-r--r--android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/relaylist/SelectableRelayListItem.kt13
-rw-r--r--android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/relaylist/SelectableRelayListItemPreviewParameterProvider.kt65
4 files changed, 128 insertions, 65 deletions
diff --git a/android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/relaylist/RelayListItem.kt b/android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/relaylist/RelayListItem.kt
index 8132a9ece7..f88b7b92b4 100644
--- a/android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/relaylist/RelayListItem.kt
+++ b/android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/relaylist/RelayListItem.kt
@@ -1,8 +1,11 @@
package net.mullvad.mullvadvpn.lib.ui.component.relaylist
+import android.content.Context
import net.mullvad.mullvadvpn.lib.model.CustomListId
import net.mullvad.mullvadvpn.lib.model.CustomListName
+import net.mullvad.mullvadvpn.lib.model.Hop
import net.mullvad.mullvadvpn.lib.model.RelayItem
+import net.mullvad.mullvadvpn.lib.resource.R
enum class RelayListItemContentType {
CUSTOM_LIST_HEADER,
@@ -13,6 +16,10 @@ enum class RelayListItemContentType {
LOCATION_ITEM,
LOCATIONS_EMPTY_TEXT,
EMPTY_RELAY_LIST,
+ RECENT_LIST_ITEM,
+ RECENT_LIST_HEADER,
+ RECENT_LIST_FOOTER,
+ SECTION_DIVIDER,
}
enum class RelayListItemState {
@@ -24,46 +31,51 @@ sealed interface RelayListItem {
val key: Any
val contentType: RelayListItemContentType
- data object CustomListHeader : RelayListItem {
- override val key = "custom_list_header"
- override val contentType = RelayListItemContentType.CUSTOM_LIST_HEADER
- }
-
sealed interface SelectableItem : RelayListItem {
- val item: RelayItem
+ val hop: Hop
val depth: Int
val isSelected: Boolean
val expanded: Boolean
+ val canExpand: Boolean
val state: RelayListItemState?
val itemPosition: ItemPosition
}
+ data object CustomListHeader : RelayListItem {
+ override val key = "custom_list_header"
+ override val contentType = RelayListItemContentType.CUSTOM_LIST_HEADER
+ }
+
data class CustomListItem(
- override val item: RelayItem.CustomList,
+ override val hop: Hop.Single<RelayItem.CustomList>,
override val isSelected: Boolean = false,
override val expanded: Boolean = false,
override val state: RelayListItemState? = null,
override val itemPosition: ItemPosition = ItemPosition.Single,
) : SelectableItem {
+ val item = hop.relay
override val key = item.id
override val depth: Int = 0
override val contentType = RelayListItemContentType.CUSTOM_LIST_ITEM
+ override val canExpand: Boolean = item.hasChildren
}
data class CustomListEntryItem(
val parentId: CustomListId,
val parentName: CustomListName,
- override val item: RelayItem.Location,
+ override val hop: Hop.Single<RelayItem.Location>,
override val expanded: Boolean,
override val depth: Int = 0,
override val state: RelayListItemState? = null,
override val itemPosition: ItemPosition,
) : SelectableItem {
+ val item = hop.relay
override val key = parentId to item.id
// Can't be displayed as selected
override val isSelected: Boolean = false
override val contentType = RelayListItemContentType.CUSTOM_LIST_ENTRY_ITEM
+ override val canExpand: Boolean = item.hasChildren
}
data class CustomListFooter(val hasCustomList: Boolean) : RelayListItem {
@@ -77,15 +89,40 @@ sealed interface RelayListItem {
}
data class GeoLocationItem(
- override val item: RelayItem.Location,
+ override val hop: Hop.Single<RelayItem.Location>,
override val isSelected: Boolean = false,
override val depth: Int = 0,
override val expanded: Boolean = false,
override val state: RelayListItemState? = null,
override val itemPosition: ItemPosition,
) : SelectableItem {
+ val item = hop.relay
override val key = item.id
override val contentType = RelayListItemContentType.LOCATION_ITEM
+ override val canExpand: Boolean = item.hasChildren
+ }
+
+ data object RecentsListHeader : RelayListItem {
+ override val key = "recents_list_header"
+ override val contentType = RelayListItemContentType.RECENT_LIST_HEADER
+ }
+
+ data class RecentListItem(
+ override val hop: Hop,
+ override val isSelected: Boolean = false,
+ override val expanded: Boolean = false,
+ override val state: RelayListItemState? = null,
+ override val itemPosition: ItemPosition = ItemPosition.Single,
+ ) : SelectableItem {
+ override val key = "recents$hop"
+ override val depth: Int = 0
+ override val contentType = RelayListItemContentType.RECENT_LIST_ITEM
+ override val canExpand: Boolean = false
+ }
+
+ data object RecentsListFooter : RelayListItem {
+ override val key = "recents_list_footer"
+ override val contentType = RelayListItemContentType.RECENT_LIST_FOOTER
}
data class LocationsEmptyText(val searchTerm: String) : RelayListItem {
@@ -97,6 +134,11 @@ sealed interface RelayListItem {
override val key = "empty_relay_list"
override val contentType = RelayListItemContentType.EMPTY_RELAY_LIST
}
+
+ class SectionDivider : RelayListItem {
+ override val key: String = "section_divider_${this.hashCode()}"
+ override val contentType = RelayListItemContentType.SECTION_DIVIDER
+ }
}
data class CheckableRelayListItem(
@@ -130,3 +172,9 @@ sealed interface ItemPosition {
else -> false
}
}
+
+fun Hop.displayName(context: Context): String =
+ when (this) {
+ is Hop.Multi -> context.getString(R.string.x_via_x, exit.name, entry.name)
+ is Hop.Single<*> -> relay.name
+ }
diff --git a/android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/relaylist/RelayListItemPreviewData.kt b/android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/relaylist/RelayListItemPreviewData.kt
index 5776601168..58ae2f2e82 100644
--- a/android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/relaylist/RelayListItemPreviewData.kt
+++ b/android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/relaylist/RelayListItemPreviewData.kt
@@ -3,6 +3,7 @@ package net.mullvad.mullvadvpn.lib.ui.component.relaylist
import net.mullvad.mullvadvpn.lib.model.CustomList
import net.mullvad.mullvadvpn.lib.model.CustomListId
import net.mullvad.mullvadvpn.lib.model.CustomListName
+import net.mullvad.mullvadvpn.lib.model.Hop
import net.mullvad.mullvadvpn.lib.model.RelayItem
object RelayListItemPreviewData {
@@ -16,23 +17,25 @@ object RelayListItemPreviewData {
// Add custom list items
if (includeCustomLists) {
RelayListItem.CustomListItem(
- item =
- RelayItem.CustomList(
- customList =
- CustomList(
- id = CustomListId("custom_list_id"),
- name = CustomListName.fromString("Custom List"),
- locations = emptyList(),
- ),
- locations =
- listOf(
- generateRelayItemCountry(
- name = "Country",
- cityNames = listOf("City"),
- relaysPerCity = 2,
- active = true,
- )
- ),
+ hop =
+ Hop.Single(
+ RelayItem.CustomList(
+ customList =
+ CustomList(
+ id = CustomListId("custom_list_id"),
+ name = CustomListName.fromString("Custom List"),
+ locations = emptyList(),
+ ),
+ locations =
+ listOf(
+ generateRelayItemCountry(
+ name = "Country",
+ cityNames = listOf("City"),
+ relaysPerCity = 2,
+ active = true,
+ )
+ ),
+ )
),
isSelected = false,
state = null,
@@ -63,7 +66,7 @@ object RelayListItemPreviewData {
addAll(
listOf(
RelayListItem.GeoLocationItem(
- item = locations[0],
+ hop = Hop.Single(locations[0]),
isSelected = false,
depth = 0,
expanded = true,
@@ -71,7 +74,7 @@ object RelayListItemPreviewData {
itemPosition = ItemPosition.Middle,
),
RelayListItem.GeoLocationItem(
- item = locations[0].cities[0],
+ hop = Hop.Single(locations[0].cities[0]),
isSelected = true,
depth = 1,
expanded = false,
@@ -79,7 +82,7 @@ object RelayListItemPreviewData {
itemPosition = ItemPosition.Middle,
),
RelayListItem.GeoLocationItem(
- item = locations[0].cities[1],
+ hop = Hop.Single(locations[0].cities[1]),
isSelected = false,
depth = 1,
expanded = true,
@@ -87,7 +90,7 @@ object RelayListItemPreviewData {
itemPosition = ItemPosition.Middle,
),
RelayListItem.GeoLocationItem(
- item = locations[0].cities[1].relays[0],
+ hop = Hop.Single(locations[0].cities[1].relays[0]),
isSelected = false,
depth = 2,
expanded = false,
@@ -95,7 +98,7 @@ object RelayListItemPreviewData {
itemPosition = ItemPosition.Middle,
),
RelayListItem.GeoLocationItem(
- item = locations[0].cities[1].relays[1],
+ hop = Hop.Single(locations[0].cities[1].relays[1]),
isSelected = false,
depth = 2,
expanded = false,
@@ -103,7 +106,7 @@ object RelayListItemPreviewData {
itemPosition = ItemPosition.Middle,
),
RelayListItem.GeoLocationItem(
- item = locations[1],
+ hop = Hop.Single(locations[1]),
isSelected = false,
depth = 0,
expanded = false,
diff --git a/android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/relaylist/SelectableRelayListItem.kt b/android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/relaylist/SelectableRelayListItem.kt
index 83b24ff137..289eb5aa9f 100644
--- a/android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/relaylist/SelectableRelayListItem.kt
+++ b/android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/relaylist/SelectableRelayListItem.kt
@@ -26,6 +26,7 @@ import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
@@ -72,7 +73,7 @@ fun SelectableRelayListItem(
modifier = modifier,
shape = relayListItem.itemPosition.toShape(),
selected = relayListItem.isSelected,
- enabled = relayListItem.item.active,
+ enabled = relayListItem.hop.isActive,
content = {
Row(
modifier =
@@ -84,7 +85,7 @@ fun SelectableRelayListItem(
) {
val iconTint =
when {
- !relayListItem.item.active -> MaterialTheme.colorScheme.error
+ !relayListItem.hop.isActive -> MaterialTheme.colorScheme.error
relayListItem.isSelected -> MaterialTheme.colorScheme.tertiary
else -> Color.Transparent
}
@@ -94,14 +95,14 @@ fun SelectableRelayListItem(
contentDescription = null,
tint = iconTint,
)
- } else if (!relayListItem.item.active) {
+ } else if (!relayListItem.hop.isActive) {
InactiveRelayIndicator(iconTint)
}
Name(
- name = relayListItem.item.name,
+ name = relayListItem.hop.displayName(LocalContext.current),
state = relayListItem.state,
- active = relayListItem.item.active,
+ active = relayListItem.hop.isActive,
)
}
},
@@ -111,7 +112,7 @@ fun SelectableRelayListItem(
else ({}),
onLongClick = onLongClick,
trailingContent =
- if (relayListItem.item.hasChildren) {
+ if (relayListItem.canExpand) {
{
ExpandChevron(
isExpanded = relayListItem.expanded,
diff --git a/android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/relaylist/SelectableRelayListItemPreviewParameterProvider.kt b/android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/relaylist/SelectableRelayListItemPreviewParameterProvider.kt
index 732c03bbc4..812f4de60e 100644
--- a/android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/relaylist/SelectableRelayListItemPreviewParameterProvider.kt
+++ b/android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/relaylist/SelectableRelayListItemPreviewParameterProvider.kt
@@ -1,6 +1,7 @@
package net.mullvad.mullvadvpn.lib.ui.component.relaylist
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
+import net.mullvad.mullvadvpn.lib.model.Hop
class SelectableRelayListItemPreviewParameterProvider :
PreviewParameterProvider<List<RelayListItem.SelectableItem>> {
@@ -8,55 +9,65 @@ class SelectableRelayListItemPreviewParameterProvider :
sequenceOf(
listOf(
RelayListItem.GeoLocationItem(
- item =
- generateRelayItemCountry(
- name = "Relay country Active",
- cityNames = listOf("Relay city 1", "Relay city 2"),
- relaysPerCity = 2,
+ hop =
+ Hop.Single(
+ generateRelayItemCountry(
+ name = "Relay country Active",
+ cityNames = listOf("Relay city 1", "Relay city 2"),
+ relaysPerCity = 2,
+ )
),
isSelected = true,
expanded = false,
itemPosition = ItemPosition.Single,
),
RelayListItem.GeoLocationItem(
- item =
- generateRelayItemCountry(
- name = "Not Enabled Relay country",
- cityNames = listOf("Not Enabled city"),
- relaysPerCity = 1,
- active = false,
+ hop =
+ Hop.Single(
+ generateRelayItemCountry(
+ name = "Not Enabled Relay country",
+ cityNames = listOf("Not Enabled city"),
+ relaysPerCity = 1,
+ active = false,
+ )
),
isSelected = false,
itemPosition = ItemPosition.Single,
),
RelayListItem.GeoLocationItem(
- item =
- generateRelayItemCountry(
- name = "Relay country Expanded",
- cityNames = listOf("Normal city"),
- relaysPerCity = 2,
+ hop =
+ Hop.Single(
+ generateRelayItemCountry(
+ name = "Relay country Expanded",
+ cityNames = listOf("Normal city"),
+ relaysPerCity = 2,
+ )
),
isSelected = true,
expanded = true,
itemPosition = ItemPosition.Single,
),
RelayListItem.GeoLocationItem(
- item =
- generateRelayItemCountry(
- name = "Country and city Expanded",
- cityNames = listOf("Expanded city A", "Expanded city B"),
- relaysPerCity = 2,
+ hop =
+ Hop.Single(
+ generateRelayItemCountry(
+ name = "Country and city Expanded",
+ cityNames = listOf("Expanded city A", "Expanded city B"),
+ relaysPerCity = 2,
+ )
),
isSelected = false,
itemPosition = ItemPosition.Single,
),
RelayListItem.GeoLocationItem(
- item =
- generateRelayItemCountry(
- name = "Country selected but inactive",
- cityNames = listOf("Expanded city A", "Expanded city B"),
- relaysPerCity = 2,
- active = false,
+ hop =
+ Hop.Single(
+ generateRelayItemCountry(
+ name = "Country selected but inactive",
+ cityNames = listOf("Expanded city A", "Expanded city B"),
+ relaysPerCity = 2,
+ active = false,
+ )
),
isSelected = true,
itemPosition = ItemPosition.Single,