summaryrefslogtreecommitdiffhomepage
path: root/android/app
diff options
context:
space:
mode:
authorAlbin <albin@mullvad.net>2023-08-01 12:00:27 +0200
committerAlbin <albin@mullvad.net>2023-08-01 12:00:27 +0200
commitddd2ed9858c7a1aa47884a44b1a653c3f3af8df3 (patch)
tree338416e3b07235dc6116bcf11e1b03326227a172 /android/app
parent9f76fd36507ef03e1c72ec432c05ddaf52ae2634 (diff)
parentfd6464f0568ad087e3669ae444434768c2a9e50f (diff)
downloadmullvadvpn-ddd2ed9858c7a1aa47884a44b1a653c3f3af8df3.tar.xz
mullvadvpn-ddd2ed9858c7a1aa47884a44b1a653c3f3af8df3.zip
Merge branch 'reduce-location-flickering-droid-219'
Diffstat (limited to 'android/app')
-rw-r--r--android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreenTest.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/button/ConnectionButton.kt28
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt20
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt27
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModelTest.kt14
5 files changed, 53 insertions, 38 deletions
diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreenTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreenTest.kt
index 9d64b2d395..083443b259 100644
--- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreenTest.kt
+++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreenTest.kt
@@ -200,7 +200,7 @@ class ConnectScreenTest {
composeTestRule.apply {
onNodeWithText("UNSECURED CONNECTION").assertExists()
onNodeWithText(mockLocationName).assertExists()
- onNodeWithText("Secure my connection").assertExists()
+ onNodeWithText("Disconnect").assertExists()
}
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/button/ConnectionButton.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/button/ConnectionButton.kt
index ad483165f8..e6424a1674 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/button/ConnectionButton.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/button/ConnectionButton.kt
@@ -28,7 +28,6 @@ import net.mullvad.mullvadvpn.compose.theme.AlphaInactive
import net.mullvad.mullvadvpn.compose.theme.AppTheme
import net.mullvad.mullvadvpn.compose.theme.Dimens
import net.mullvad.mullvadvpn.model.TunnelState
-import net.mullvad.talpid.tunnel.ActionAfterDisconnect
@Composable
fun ConnectionButton(
@@ -43,26 +42,13 @@ fun ConnectionButton(
when (state) {
is TunnelState.Disconnected -> ConnectButton(modifier = modifier, onClick = connectClick)
is TunnelState.Disconnecting -> {
- when (state.actionAfterDisconnect) {
- ActionAfterDisconnect.Nothing ->
- ConnectButton(modifier = modifier, onClick = connectClick)
- ActionAfterDisconnect.Block ->
- DisconnectButton(
- modifier = modifier,
- text = stringResource(id = R.string.disconnect),
- mainClick = connectClick,
- reconnectClick = reconnectClick,
- reconnectButtonTestTag = reconnectButtonTestTag
- )
- ActionAfterDisconnect.Reconnect ->
- DisconnectButton(
- modifier = modifier,
- text = stringResource(id = R.string.disconnect),
- mainClick = connectClick,
- reconnectClick = reconnectClick,
- reconnectButtonTestTag = reconnectButtonTestTag
- )
- }
+ DisconnectButton(
+ modifier = modifier,
+ text = stringResource(id = R.string.disconnect),
+ mainClick = connectClick,
+ reconnectClick = reconnectClick,
+ reconnectButtonTestTag = reconnectButtonTestTag
+ )
}
is TunnelState.Connecting ->
DisconnectButton(
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 50c0005a96..384c92d590 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
@@ -15,6 +15,10 @@ import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
@@ -36,6 +40,8 @@ import net.mullvad.mullvadvpn.compose.theme.Dimens
import net.mullvad.mullvadvpn.model.TunnelState
import net.mullvad.talpid.tunnel.ActionAfterDisconnect
+private const val CONNECT_BUTTON_THROTTLE_MILLIS = 1000
+
@Preview
@Composable
fun PreviewConnectScreen() {
@@ -54,6 +60,16 @@ fun ConnectScreen(
onToggleTunnelInfo: () -> Unit = {}
) {
val scrollState = rememberScrollState()
+ var lastConnectionActionTimestamp by remember { mutableStateOf(0L) }
+
+ fun handleThrottledAction(action: () -> Unit) {
+ val currentTime = System.currentTimeMillis()
+ if ((currentTime - lastConnectionActionTimestamp) > CONNECT_BUTTON_THROTTLE_MILLIS) {
+ lastConnectionActionTimestamp = currentTime
+ action.invoke()
+ }
+ }
+
Column(
verticalArrangement = Arrangement.Bottom,
horizontalAlignment = Alignment.Start,
@@ -126,9 +142,9 @@ fun ConnectScreen(
.height(Dimens.connectButtonHeight)
.testTag(CONNECT_BUTTON_TEST_TAG),
disconnectClick = onDisconnectClick,
- reconnectClick = onReconnectClick,
+ reconnectClick = { handleThrottledAction(onReconnectClick) },
cancelClick = onCancelClick,
- connectClick = onConnectClick,
+ connectClick = { handleThrottledAction(onConnectClick) },
reconnectButtonTestTag = RECONNECT_BUTTON_TEST_TAG
)
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt
index 826376ad9f..37aa06a7c9 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt
@@ -11,6 +11,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.shareIn
@@ -64,7 +65,13 @@ class ConnectViewModel(private val serviceConnectionManager: ServiceConnectionMa
tunnelRealState,
isTunnelInfoExpanded ->
ConnectUiState(
- location = location,
+ location =
+ when (tunnelRealState) {
+ is TunnelState.Connected -> tunnelRealState.location
+ is TunnelState.Connecting -> tunnelRealState.location
+ else -> null
+ }
+ ?: location,
relayLocation = relayLocation,
versionInfo = versionInfo,
tunnelUiState = tunnelUiState,
@@ -82,7 +89,7 @@ class ConnectViewModel(private val serviceConnectionManager: ServiceConnectionMa
is TunnelState.Disconnected -> true
is TunnelState.Disconnecting -> {
when (tunnelUiState.actionAfterDisconnect) {
- ActionAfterDisconnect.Nothing -> true
+ ActionAfterDisconnect.Nothing -> false
ActionAfterDisconnect.Block -> true
ActionAfterDisconnect.Reconnect -> false
}
@@ -94,12 +101,16 @@ class ConnectViewModel(private val serviceConnectionManager: ServiceConnectionMa
)
}
}
+ .debounce(UI_STATE_DEBOUNCE_DURATION_MILLIS)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), ConnectUiState.INITIAL)
- private fun LocationInfoCache.locationCallbackFlow() = callbackFlow {
- onNewLocation = { this.trySend(it) }
- awaitClose { onNewLocation = null }
- }
+ private fun LocationInfoCache.locationCallbackFlow() =
+ callbackFlow {
+ onNewLocation = { this.trySend(it) }
+ awaitClose { onNewLocation = null }
+ }
+ // Filter out empty or short-name country representations.
+ .filter { it?.let { location -> location.country.length > 2 } ?: false }
private fun RelayListListener.relayListCallbackFlow() = callbackFlow {
onRelayCountriesChange = { _, item -> this.trySend(item) }
@@ -108,11 +119,9 @@ class ConnectViewModel(private val serviceConnectionManager: ServiceConnectionMa
private fun ConnectionProxy.tunnelUiStateFlow(): Flow<TunnelState> =
callbackFlowFromNotifier(this.onUiStateChange)
- .debounce(TUNNEL_STATE_UPDATE_DEBOUNCE_DURATION_MILLIS)
private fun ConnectionProxy.tunnelRealStateFlow(): Flow<TunnelState> =
callbackFlowFromNotifier(this.onStateChange)
- .debounce(TUNNEL_STATE_UPDATE_DEBOUNCE_DURATION_MILLIS)
fun toggleTunnelInfoExpansion() {
_isTunnelInfoExpanded.value = _isTunnelInfoExpanded.value.not()
@@ -132,6 +141,6 @@ class ConnectViewModel(private val serviceConnectionManager: ServiceConnectionMa
}
companion object {
- const val TUNNEL_STATE_UPDATE_DEBOUNCE_DURATION_MILLIS: Long = 200
+ const val UI_STATE_DEBOUNCE_DURATION_MILLIS: Long = 100
}
}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModelTest.kt
index 5b1ee8e7e1..c08f8682eb 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModelTest.kt
@@ -58,6 +58,7 @@ class ConnectViewModelTest {
private val mockRelayListListener: RelayListListener = mockk(relaxUnitFun = true)
private lateinit var mockAppVersionInfoCache: AppVersionInfoCache
private val mockConnectionProxy: ConnectionProxy = mockk()
+ private val mockLocation: GeoIpLocation = mockk(relaxed = true)
// Captures
private val locationSlot = slot<((GeoIpLocation?) -> Unit)>()
@@ -85,6 +86,9 @@ class ConnectViewModelTest {
every { mockConnectionProxy.onUiStateChange } returns eventNotifierTunnelUiState
every { mockConnectionProxy.onStateChange } returns eventNotifierTunnelRealState
+
+ every { mockLocation.country } returns "dummy country"
+
// Listeners
every { mockLocationInfoCache.onNewLocation = capture(locationSlot) } answers {}
every { mockRelayListListener.onRelayCountriesChange = capture(relaySlot) } answers {}
@@ -113,7 +117,7 @@ class ConnectViewModelTest {
assertEquals(ConnectUiState.INITIAL, awaitItem())
serviceConnectionState.value =
ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
- locationSlot.captured.invoke(mockk(relaxed = true))
+ locationSlot.captured.invoke(mockLocation)
relaySlot.captured.invoke(mockk(), mockk())
viewModel.toggleTunnelInfoExpansion()
val result = awaitItem()
@@ -130,7 +134,7 @@ class ConnectViewModelTest {
assertEquals(ConnectUiState.INITIAL, awaitItem())
serviceConnectionState.value =
ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
- locationSlot.captured.invoke(mockk(relaxed = true))
+ locationSlot.captured.invoke(mockLocation)
relaySlot.captured.invoke(mockk(), mockk())
eventNotifierTunnelRealState.notify(tunnelRealStateTestItem)
val result = awaitItem()
@@ -147,7 +151,7 @@ class ConnectViewModelTest {
assertEquals(ConnectUiState.INITIAL, awaitItem())
serviceConnectionState.value =
ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
- locationSlot.captured.invoke(mockk(relaxed = true))
+ locationSlot.captured.invoke(mockLocation)
relaySlot.captured.invoke(mockk(), mockk())
eventNotifierTunnelUiState.notify(tunnelUiStateTestItem)
val result = awaitItem()
@@ -170,7 +174,7 @@ class ConnectViewModelTest {
assertEquals(ConnectUiState.INITIAL, awaitItem())
serviceConnectionState.value =
ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
- locationSlot.captured.invoke(mockk(relaxed = true))
+ locationSlot.captured.invoke(mockLocation)
relaySlot.captured.invoke(mockk(), mockk())
versionInfo.value = versionInfoTestItem
val result = awaitItem()
@@ -188,7 +192,7 @@ class ConnectViewModelTest {
assertEquals(ConnectUiState.INITIAL, awaitItem())
serviceConnectionState.value =
ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
- locationSlot.captured.invoke(mockk(relaxed = true))
+ locationSlot.captured.invoke(mockLocation)
relaySlot.captured.invoke(mockk(), relayTestItem)
val result = awaitItem()
assertEquals(relayTestItem, result.relayLocation)