diff options
| author | David Göransson <david.goransson@mullvad.net> | 2025-03-01 13:00:25 +0100 |
|---|---|---|
| committer | David Göransson <david.goransson@mullvad.net> | 2025-03-07 09:07:43 +0100 |
| commit | 0d733c7eac966ecd198e5f604dac2035739bda5a (patch) | |
| tree | c2b294d6bfbbe535c5742aae7d0afd51654a7857 /android | |
| parent | 7de489bb2aebaca7226c570a7c772c91ad0fa252 (diff) | |
| download | mullvadvpn-0d733c7eac966ecd198e5f604dac2035739bda5a.tar.xz mullvadvpn-0d733c7eac966ecd198e5f604dac2035739bda5a.zip | |
Offer to store accout number in password manager
Diffstat (limited to 'android')
5 files changed, 40 insertions, 3 deletions
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/WelcomeScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/WelcomeScreen.kt index 5146d65bb0..20396c4849 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/WelcomeScreen.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/WelcomeScreen.kt @@ -29,9 +29,13 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.credentials.CreatePasswordRequest +import androidx.credentials.CredentialManager +import androidx.credentials.exceptions.CreateCredentialException import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.dropUnlessResumed +import co.touchlab.kermit.Logger import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.generated.NavGraphs @@ -140,6 +144,17 @@ fun Welcome( snackbarHostState.showSnackbarImmediately( message = context.getString(R.string.error_occurred) ) + is WelcomeViewModel.UiSideEffect.StoreCredentialsRequest -> { + // UserId is not allowed to be empty + val createPasswordRequest = + CreatePasswordRequest(id = "-", password = uiSideEffect.accountNumber.value) + val credentialsManager = CredentialManager.create(context) + try { + credentialsManager.createCredential(context, createPasswordRequest) + } catch (e: CreateCredentialException) { + Logger.w("Unable to create Credentials") + } + } } } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModel.kt index 0e91390262..e22055cba7 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModel.kt @@ -16,6 +16,7 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import net.mullvad.mullvadvpn.compose.state.WelcomeUiState import net.mullvad.mullvadvpn.lib.common.util.isAfterNowInstant +import net.mullvad.mullvadvpn.lib.model.AccountNumber import net.mullvad.mullvadvpn.lib.model.WebsiteAuthToken import net.mullvad.mullvadvpn.lib.shared.AccountRepository import net.mullvad.mullvadvpn.lib.shared.ConnectionProxy @@ -39,12 +40,18 @@ class WelcomeViewModel( val uiState = combine( connectionProxy.tunnelState, - deviceRepository.deviceState.filterNotNull(), + deviceRepository.deviceState.filterNotNull().onEach { + viewModelScope.launch { + it.accountNumber()?.let { accountNumber -> + _uiSideEffect.send(UiSideEffect.StoreCredentialsRequest(accountNumber)) + } + } + }, paymentUseCase.paymentAvailability, ) { tunnelState, accountState, paymentAvailability -> WelcomeUiState( tunnelState = tunnelState, - accountNumber = accountState.token(), + accountNumber = accountState.accountNumber(), deviceName = accountState.displayName(), showSitePayment = !isPlayBuild, billingPaymentState = paymentAvailability?.toPaymentState(), @@ -122,6 +129,8 @@ class WelcomeViewModel( data object OpenConnectScreen : UiSideEffect + data class StoreCredentialsRequest(val accountNumber: AccountNumber) : UiSideEffect + data object GenericError : UiSideEffect } diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DeviceState.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DeviceState.kt index ccec166a47..12131832ed 100644 --- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DeviceState.kt +++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DeviceState.kt @@ -15,7 +15,7 @@ sealed class DeviceState : Parcelable { return (this as? LoggedIn)?.device?.displayName() } - fun token(): AccountNumber? { + fun accountNumber(): AccountNumber? { return (this as? LoggedIn)?.accountNumber } } diff --git a/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/interactor/AppInteractor.kt b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/interactor/AppInteractor.kt index 2b9b008ad0..c940f9bfba 100644 --- a/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/interactor/AppInteractor.kt +++ b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/interactor/AppInteractor.kt @@ -152,4 +152,15 @@ class AppInteractor( device.findObjectWithTimeout(By.desc("Remove")).click() clickActionButtonByText("Yes, log out device") } + + fun dismissStorePasswordPromptIfShown() { + try { + device.waitForIdle() + val selector = By.textContains("password") + device.wait(Until.hasObject(selector), DEFAULT_TIMEOUT) + device.pressBack() + } catch (e: IllegalArgumentException) { + // This is OK since it means the password prompt wasn't shown. + } + } } diff --git a/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/CreateAccountMockApiTest.kt b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/CreateAccountMockApiTest.kt index 05418cb34b..9b5cf85b95 100644 --- a/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/CreateAccountMockApiTest.kt +++ b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/CreateAccountMockApiTest.kt @@ -24,6 +24,8 @@ class CreateAccountMockApiTest : MockApiTest() { app.waitForLoginPrompt() app.attemptCreateAccount() + app.dismissStorePasswordPromptIfShown() + // Assert val expectedResult = "1234 1234 1234 1234" app.ensureAccountCreated(expectedResult) |
