diff options
| author | Albin <albin@mullvad.net> | 2023-08-01 12:00:27 +0200 |
|---|---|---|
| committer | Albin <albin@mullvad.net> | 2023-08-01 12:00:27 +0200 |
| commit | ddd2ed9858c7a1aa47884a44b1a653c3f3af8df3 (patch) | |
| tree | 338416e3b07235dc6116bcf11e1b03326227a172 /android/app/src | |
| parent | 9f76fd36507ef03e1c72ec432c05ddaf52ae2634 (diff) | |
| parent | fd6464f0568ad087e3669ae444434768c2a9e50f (diff) | |
| download | mullvadvpn-ddd2ed9858c7a1aa47884a44b1a653c3f3af8df3.tar.xz mullvadvpn-ddd2ed9858c7a1aa47884a44b1a653c3f3af8df3.zip | |
Merge branch 'reduce-location-flickering-droid-219'
Diffstat (limited to 'android/app/src')
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) |
