diff options
6 files changed, 69 insertions, 20 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 42f9dce3ff..1305f94c84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ Line wrap the file at 100 chars. Th #### Android - Migrate welcome view to compose. - Migrate in app notifications to compose. +- Move out of time evaluation to connect view model. #### Linux - Don't block forwarding of traffic when the split tunnel mark (ct mark) is set. 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 67e53da747..40f9b278b8 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 @@ -820,4 +820,20 @@ class ConnectScreenTest { // Assert composeTestRule.apply { onNodeWithTag(SCROLLABLE_COLUMN_TEST_TAG).assertDoesNotExist() } } + + @Test + fun testOpenOutOfTimeScreen() { + // Arrange + val mockedOpenScreenHandler: () -> Unit = mockk(relaxed = true) + composeTestRule.setContent { + ConnectScreen( + uiState = ConnectUiState.INITIAL, + viewActions = MutableStateFlow(ConnectViewModel.ViewAction.OpenOutOfTimeView), + onOpenOutOfTimeScreen = mockedOpenScreenHandler + ) + } + + // Assert + verify { mockedOpenScreenHandler.invoke() } + } } 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 0a50e339a6..ac9ea5cf1c 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 @@ -74,13 +74,19 @@ fun ConnectScreen( onSwitchLocationClick: () -> Unit = {}, onToggleTunnelInfo: () -> Unit = {}, onUpdateVersionClick: () -> Unit = {}, - onManageAccountClick: () -> Unit = {} + onManageAccountClick: () -> Unit = {}, + onOpenOutOfTimeScreen: () -> Unit = {} ) { val context = LocalContext.current LaunchedEffect(key1 = Unit) { viewActions.collect { viewAction -> - if (viewAction is ConnectViewModel.ViewAction.OpenAccountManagementPageInBrowser) { - context.openAccountPageInBrowser(viewAction.token) + when (viewAction) { + is ConnectViewModel.ViewAction.OpenAccountManagementPageInBrowser -> { + context.openAccountPageInBrowser(viewAction.token) + } + is ConnectViewModel.ViewAction.OpenOutOfTimeView -> { + onOpenOutOfTimeScreen() + } } } } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/ConnectFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/ConnectFragment.kt index 7bd3c56edd..d83941e4de 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/ConnectFragment.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/ConnectFragment.kt @@ -24,7 +24,6 @@ import net.mullvad.mullvadvpn.ui.NavigationBarPainter import net.mullvad.mullvadvpn.ui.paintNavigationBar import net.mullvad.mullvadvpn.ui.widget.HeaderBar import net.mullvad.mullvadvpn.viewmodel.ConnectViewModel -import net.mullvad.talpid.tunnel.ErrorStateCause import org.koin.androidx.viewmodel.ext.android.viewModel class ConnectFragment : BaseFragment(), NavigationBarPainter { @@ -66,7 +65,8 @@ class ConnectFragment : BaseFragment(), NavigationBarPainter { onSwitchLocationClick = { openSwitchLocationScreen() }, onToggleTunnelInfo = connectViewModel::toggleTunnelInfoExpansion, onUpdateVersionClick = { openDownloadUrl() }, - onManageAccountClick = connectViewModel::onManageAccountClick + onManageAccountClick = connectViewModel::onManageAccountClick, + onOpenOutOfTimeScreen = ::openOutOfTimeScreen ) } } @@ -103,10 +103,6 @@ class ConnectFragment : BaseFragment(), NavigationBarPainter { private fun updateTunnelState(realState: TunnelState) { headerBar.tunnelState = realState - - if (realState.isTunnelErrorStateDueToExpiredAccount()) { - openOutOfTimeScreen() - } } private fun openSwitchLocationScreen() { @@ -124,17 +120,9 @@ class ConnectFragment : BaseFragment(), NavigationBarPainter { } private fun openOutOfTimeScreen() { - jobTracker.newUiJob("openOutOfTimeScreen") { - parentFragmentManager.beginTransaction().apply { - replace(R.id.main_fragment, OutOfTimeFragment()) - commitAllowingStateLoss() - } + parentFragmentManager.beginTransaction().apply { + replace(R.id.main_fragment, OutOfTimeFragment()) + commitAllowingStateLoss() } } - - private fun TunnelState.isTunnelErrorStateDueToExpiredAccount(): Boolean { - return ((this as? TunnelState.Error)?.errorState?.cause as? ErrorStateCause.AuthFailed) - ?.isCausedByExpiredAccount() - ?: false - } } 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 3df2ea3a07..e6256e2be6 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 @@ -40,6 +40,7 @@ import net.mullvad.mullvadvpn.util.combine import net.mullvad.mullvadvpn.util.toInAddress import net.mullvad.mullvadvpn.util.toOutAddress import net.mullvad.talpid.tunnel.ActionAfterDisconnect +import net.mullvad.talpid.tunnel.ErrorStateCause import org.joda.time.DateTime @OptIn(FlowPreview::class) @@ -83,6 +84,9 @@ class ConnectViewModel( tunnelRealState, accountExpiry, isTunnelInfoExpanded -> + if (tunnelRealState.isTunnelErrorStateDueToExpiredAccount()) { + _viewActions.tryEmit(ViewAction.OpenOutOfTimeView) + } ConnectUiState( location = when (tunnelRealState) { @@ -178,6 +182,12 @@ class ConnectViewModel( return this.date()?.isBefore(threeDaysFromNow) == true } + private fun TunnelState.isTunnelErrorStateDueToExpiredAccount(): Boolean { + return ((this as? TunnelState.Error)?.errorState?.cause as? ErrorStateCause.AuthFailed) + ?.isCausedByExpiredAccount() + ?: false + } + fun toggleTunnelInfoExpansion() { _isTunnelInfoExpanded.value = _isTunnelInfoExpanded.value.not() } @@ -210,6 +220,8 @@ class ConnectViewModel( sealed interface ViewAction { data class OpenAccountManagementPageInBrowser(val token: String) : ViewAction + + data object OpenOutOfTimeView : ViewAction } companion object { 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 776136d1c5..517281e6c2 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 @@ -11,8 +11,10 @@ import io.mockk.unmockkAll import io.mockk.verify import kotlin.test.assertEquals import kotlin.test.assertIs +import kotlinx.coroutines.async import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import net.mullvad.mullvadvpn.compose.state.ConnectNotificationState import net.mullvad.mullvadvpn.compose.state.ConnectUiState @@ -36,6 +38,7 @@ import net.mullvad.mullvadvpn.ui.serviceconnection.authTokenCache import net.mullvad.mullvadvpn.ui.serviceconnection.connectionProxy import net.mullvad.mullvadvpn.util.appVersionCallbackFlow import net.mullvad.talpid.tunnel.ErrorState +import net.mullvad.talpid.tunnel.ErrorStateCause import net.mullvad.talpid.util.EventNotifier import org.joda.time.DateTime import org.joda.time.ReadableInstant @@ -404,6 +407,29 @@ class ConnectViewModelTest { } } + @Test + fun testOutOfTimeViewAction() = + runTest(testCoroutineRule.testDispatcher) { + // Arrange + val errorStateCause = ErrorStateCause.AuthFailed("[EXPIRED_ACCOUNT]") + val tunnelRealStateTestItem = TunnelState.Error(ErrorState(errorStateCause, true)) + val deferred = async { viewModel.viewActions.first() } + + // Act + viewModel.uiState.test { + awaitItem() + serviceConnectionState.value = + ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) + locationSlot.captured.invoke(mockLocation) + relaySlot.captured.invoke(mockk(), mockk()) + eventNotifierTunnelRealState.notify(tunnelRealStateTestItem) + awaitItem() + } + + // Assert + assertIs<ConnectViewModel.ViewAction.OpenOutOfTimeView>(deferred.await()) + } + companion object { private const val CACHE_EXTENSION_CLASS = "net.mullvad.mullvadvpn.util.CacheExtensionsKt" private const val SERVICE_CONNECTION_MANAGER_EXTENSIONS = |
