Skip to content

Commit 27e26d1

Browse files
committed
Offer to store accout number in password manager
1 parent b7e3887 commit 27e26d1

File tree

5 files changed

+41
-3
lines changed

5 files changed

+41
-3
lines changed

android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/WelcomeScreen.kt

+15
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,13 @@ import androidx.compose.ui.res.stringResource
2929
import androidx.compose.ui.text.style.TextOverflow
3030
import androidx.compose.ui.tooling.preview.Preview
3131
import androidx.compose.ui.tooling.preview.PreviewParameter
32+
import androidx.credentials.CreatePasswordRequest
33+
import androidx.credentials.CredentialManager
34+
import androidx.credentials.exceptions.CreateCredentialException
3235
import androidx.lifecycle.Lifecycle
3336
import androidx.lifecycle.compose.collectAsStateWithLifecycle
3437
import androidx.lifecycle.compose.dropUnlessResumed
38+
import co.touchlab.kermit.Logger
3539
import com.ramcosta.composedestinations.annotation.Destination
3640
import com.ramcosta.composedestinations.annotation.RootGraph
3741
import com.ramcosta.composedestinations.generated.NavGraphs
@@ -140,6 +144,17 @@ fun Welcome(
140144
snackbarHostState.showSnackbarImmediately(
141145
message = context.getString(R.string.error_occurred)
142146
)
147+
is WelcomeViewModel.UiSideEffect.StoreCredentialsRequest -> {
148+
// UserId is not allowed to be empty
149+
val createPasswordRequest =
150+
CreatePasswordRequest(id = "-", password = uiSideEffect.accountNumber.value)
151+
val credentialsManager = CredentialManager.create(context)
152+
try {
153+
credentialsManager.createCredential(context, createPasswordRequest)
154+
} catch (e: CreateCredentialException) {
155+
Logger.w("Unable to create Credentials")
156+
}
157+
}
143158
}
144159
}
145160

android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModel.kt

+11-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import kotlinx.coroutines.flow.stateIn
1616
import kotlinx.coroutines.launch
1717
import net.mullvad.mullvadvpn.compose.state.WelcomeUiState
1818
import net.mullvad.mullvadvpn.lib.common.util.isAfterNowInstant
19+
import net.mullvad.mullvadvpn.lib.model.AccountNumber
1920
import net.mullvad.mullvadvpn.lib.model.WebsiteAuthToken
2021
import net.mullvad.mullvadvpn.lib.shared.AccountRepository
2122
import net.mullvad.mullvadvpn.lib.shared.ConnectionProxy
@@ -39,12 +40,18 @@ class WelcomeViewModel(
3940
val uiState =
4041
combine(
4142
connectionProxy.tunnelState,
42-
deviceRepository.deviceState.filterNotNull(),
43+
deviceRepository.deviceState.filterNotNull().onEach {
44+
viewModelScope.launch {
45+
it.accountNumber()?.let { accountNumber ->
46+
_uiSideEffect.send(UiSideEffect.StoreCredentialsRequest(accountNumber))
47+
}
48+
}
49+
},
4350
paymentUseCase.paymentAvailability,
4451
) { tunnelState, accountState, paymentAvailability ->
4552
WelcomeUiState(
4653
tunnelState = tunnelState,
47-
accountNumber = accountState.token(),
54+
accountNumber = accountState.accountNumber(),
4855
deviceName = accountState.displayName(),
4956
showSitePayment = !isPlayBuild,
5057
billingPaymentState = paymentAvailability?.toPaymentState(),
@@ -122,6 +129,8 @@ class WelcomeViewModel(
122129

123130
data object OpenConnectScreen : UiSideEffect
124131

132+
data class StoreCredentialsRequest(val accountNumber: AccountNumber) : UiSideEffect
133+
125134
data object GenericError : UiSideEffect
126135
}
127136

android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/DeviceState.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ sealed class DeviceState : Parcelable {
1515
return (this as? LoggedIn)?.device?.displayName()
1616
}
1717

18-
fun token(): AccountNumber? {
18+
fun accountNumber(): AccountNumber? {
1919
return (this as? LoggedIn)?.accountNumber
2020
}
2121
}

android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/interactor/AppInteractor.kt

+12
Original file line numberDiff line numberDiff line change
@@ -152,4 +152,16 @@ class AppInteractor(
152152
device.findObjectWithTimeout(By.desc("Remove")).click()
153153
clickActionButtonByText("Yes, log out device")
154154
}
155+
156+
fun dismissStorePasswordPromptIfShown() {
157+
try {
158+
device.waitForIdle()
159+
val selector = By.text("password")
160+
device.wait(Until.hasObject(selector), DEFAULT_TIMEOUT)
161+
device.findObjectWithTimeout(selector).click()
162+
device.pressBack()
163+
} catch (e: IllegalArgumentException) {
164+
// This is OK since it means the password prompt wasn't shown.
165+
}
166+
}
155167
}

android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/CreateAccountMockApiTest.kt

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ class CreateAccountMockApiTest : MockApiTest() {
2424
app.waitForLoginPrompt()
2525
app.attemptCreateAccount()
2626

27+
app.dismissStorePasswordPromptIfShown()
28+
2729
// Assert
2830
val expectedResult = "1234 1234 1234 1234"
2931
app.ensureAccountCreated(expectedResult)

0 commit comments

Comments
 (0)