summaryrefslogtreecommitdiffhomepage
path: root/android
diff options
context:
space:
mode:
authorDavid Göransson <david.goransson@mullvad.net>2025-11-04 08:43:08 +0100
committerDavid Göransson <david.goransson@mullvad.net>2025-11-04 10:14:20 +0100
commite40fb9b75328cead06b698994910368ec0629754 (patch)
tree070285d6fc9e6555b522230549c9860d47dadafb /android
parent8dff924ed516675d5838e40aa067056b862e53e7 (diff)
downloadmullvadvpn-e40fb9b75328cead06b698994910368ec0629754.tar.xz
mullvadvpn-e40fb9b75328cead06b698994910368ec0629754.zip
Keep expand state while secured
Diffstat (limited to 'android')
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/connectioninfo/ConnectionDetailPanel.kt29
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/connectioninfo/FeatureIndicatorsPanel.kt39
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt28
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/TunnelState.kt29
4 files changed, 75 insertions, 50 deletions
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/connectioninfo/ConnectionDetailPanel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/connectioninfo/ConnectionDetailPanel.kt
index 92b782895b..cef0ab2cf9 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/connectioninfo/ConnectionDetailPanel.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/connectioninfo/ConnectionDetailPanel.kt
@@ -2,6 +2,7 @@ package net.mullvad.mullvadvpn.compose.component.connectioninfo
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.selection.SelectionContainer
@@ -25,23 +26,25 @@ import net.mullvad.mullvadvpn.lib.ui.tag.LOCATION_INFO_CONNECTION_OUT_TEST_TAG
@Composable
fun ConnectionDetailPanel(
- connectionDetails: ConnectionDetails,
+ connectionDetails: ConnectionDetails?,
enableSelectableText: Boolean = true,
) {
- ConnectionInfoHeader(
- stringResource(R.string.connect_panel_connection_details),
- Modifier.fillMaxWidth().padding(bottom = Dimens.smallPadding),
- )
-
- AnimatedContent(connectionDetails, label = "ConnectionDetails") {
- ConnectionDetails(
- it.inAddress,
- it.outIpv4Address,
- it.outIpv6Address,
- modifier = Modifier.padding(bottom = Dimens.smallPadding),
- enableSelectableText = enableSelectableText,
+ Column {
+ ConnectionInfoHeader(
+ stringResource(R.string.connect_panel_connection_details),
+ Modifier.fillMaxWidth().padding(bottom = Dimens.smallPadding),
)
+
+ AnimatedContent(connectionDetails, label = "ConnectionDetails") {
+ ConnectionDetails(
+ it?.inAddress.orEmpty(),
+ it?.outIpv4Address,
+ it?.outIpv6Address,
+ modifier = Modifier.padding(bottom = Dimens.smallPadding),
+ enableSelectableText = enableSelectableText,
+ )
+ }
}
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/connectioninfo/FeatureIndicatorsPanel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/connectioninfo/FeatureIndicatorsPanel.kt
index 6a039bd5b4..c5051b1f8d 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/connectioninfo/FeatureIndicatorsPanel.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/connectioninfo/FeatureIndicatorsPanel.kt
@@ -90,26 +90,24 @@ fun FeatureIndicators(
text = featureIndicator.text(),
onClick = { onNavigateToFeature(featureIndicator) },
modifier =
- Modifier.let {
- if (this@with != null && animatedVisibilityScope != null) {
- it.sharedBounds(
- rememberSharedContentState(
- key =
- if (featureIndicator == FeatureIndicator.DAITA_MULTIHOP)
- FeatureIndicator.DAITA
- else featureIndicator
- ),
- animatedVisibilityScope = animatedVisibilityScope,
- // This flag should be set to `true` (default), this would allow the
- // element to animate above all other views. However, it causes the
- // expand/collapse animation to become janky.
- renderInOverlayDuringTransition = false,
- enter = fadeIn(tween(easing = EaseInQuart)),
- exit = fadeOut(tween(easing = EaseOutQuad)),
- )
- } else {
- it
- }
+ if (this@with != null && animatedVisibilityScope != null) {
+ Modifier.sharedBounds(
+ rememberSharedContentState(
+ key =
+ if (featureIndicator == FeatureIndicator.DAITA_MULTIHOP)
+ FeatureIndicator.DAITA
+ else featureIndicator
+ ),
+ animatedVisibilityScope = animatedVisibilityScope,
+ // This flag should be set to `true` (default), this would allow the
+ // element to animate above all other views. However, it causes the
+ // expand/collapse animation to become janky.
+ renderInOverlayDuringTransition = false,
+ enter = fadeIn(tween(easing = EaseInQuart)),
+ exit = fadeOut(tween(easing = EaseOutQuad)),
+ )
+ } else {
+ Modifier
},
)
}
@@ -123,6 +121,7 @@ fun FeatureIndicators(
}
}
+@Suppress("CyclomaticComplexMethod")
@Composable
private fun FeatureIndicator.text(): String {
val resource =
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt
index c7074b10a6..d680da7cf4 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt
@@ -3,6 +3,7 @@ package net.mullvad.mullvadvpn.compose.screen
import android.content.Context
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateFloatAsState
@@ -528,7 +529,8 @@ private fun ConnectionCard(
onConnectClick: () -> Unit,
onNavigateToFeature: (FeatureIndicator) -> Unit,
) {
- var expanded by rememberSaveable(state.tunnelState::class) { mutableStateOf(false) }
+ var expanded by
+ rememberSaveable(state.tunnelState is TunnelState.Disconnected) { mutableStateOf(false) }
val containerColor =
animateColorAsState(
if (expanded) MaterialTheme.colorScheme.surfaceContainer
@@ -546,14 +548,14 @@ private fun ConnectionCard(
ConnectionCardHeader(state, state.location, expanded) { expanded = !expanded }
AnimatedContent(
- (state.tunnelState as? TunnelState.Connected)?.featureIndicators to expanded,
+ state.tunnelState.featureIndicators() to expanded,
modifier = Modifier.weight(1f, fill = false),
label = "connection_card_connection_details",
) { (featureIndicators, exp) ->
if (featureIndicators != null) {
ConnectionInfo(
featureIndicators,
- (state.tunnelState as? TunnelState.Connected)?.toConnectionsDetails(),
+ state.tunnelState.toConnectionsDetails(),
exp,
onToggleExpand = { expanded = !exp },
onNavigateToFeature = onNavigateToFeature,
@@ -589,14 +591,14 @@ private fun ConnectionCardHeader(
modifier =
Modifier.fillMaxWidth()
.clickable(
- enabled = state.tunnelState is TunnelState.Connected,
+ enabled = state.tunnelState.isConnectingOrConnected(),
onClick = onToggleExpand,
)
.testTag(CONNECT_CARD_HEADER_TEST_TAG)
) {
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
ConnectionStatusText(state = state.tunnelState)
- if (state.tunnelState is TunnelState.Connected) {
+ if (state.tunnelState.isConnectingOrConnected()) {
ExpandChevron(isExpanded = !expanded)
}
}
@@ -676,7 +678,7 @@ private fun ConnectionInfo(
) {
FeatureIndicatorsPanel(featureIndicators, expanded, onToggleExpand, onNavigateToFeature)
- if (expanded && connectionDetails != null) {
+ AnimatedVisibility(expanded && connectionDetails != null) {
ConnectionDetailPanel(connectionDetails, enableSelectableText = !isTv())
}
}
@@ -690,12 +692,22 @@ data class ConnectionDetails(
)
@Composable
-fun TunnelState.Connected.toConnectionsDetails(): ConnectionDetails =
- ConnectionDetails(
+fun TunnelState.toConnectionsDetails(): ConnectionDetails? {
+ val endpoint =
+ when (this) {
+ is TunnelState.Connected -> endpoint
+ is TunnelState.Connecting -> endpoint
+ else -> null
+ }
+
+ if (endpoint == null) return null
+
+ return ConnectionDetails(
endpoint.toInAddress(),
location()?.ipv4?.hostAddress,
location()?.ipv6?.hostAddress,
)
+}
@Composable
private fun ButtonPanel(
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/TunnelState.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/TunnelState.kt
index 61d8ec89e3..b8759ab76f 100644
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/TunnelState.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/TunnelState.kt
@@ -19,33 +19,44 @@ sealed class TunnelState {
data class Error(val errorState: ErrorState) : TunnelState()
- fun location(): GeoIpLocation? {
- return when (this) {
+ fun featureIndicators(): List<FeatureIndicator>? =
+ when (this) {
+ is Connected -> featureIndicators
+ is Connecting -> featureIndicators
+ else -> null
+ }
+
+ fun location(): GeoIpLocation? =
+ when (this) {
is Connected -> location
is Connecting -> location
is Disconnecting -> null
is Disconnected -> location
is Error -> null
}
- }
- fun isSecured(): Boolean {
- return when (this) {
+ fun isConnectingOrConnected(): Boolean =
+ when (this) {
+ is Connected,
+ is Connecting -> true
+ else -> false
+ }
+
+ fun isSecured(): Boolean =
+ when (this) {
is Connected,
is Connecting,
is Disconnecting -> true
is Disconnected -> false
is Error -> this.errorState.isBlocking
}
- }
- fun isBlocked(): Boolean {
- return when (this) {
+ fun isBlocked(): Boolean =
+ when (this) {
is Connected,
is Disconnected -> false
is Connecting,
is Disconnecting -> true
is Error -> this.errorState.isBlocking
}
- }
}