From 77df1ad51259d665e875c5d28c42af4f06ed7cac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20G=C3=B6ransson?= Date: Thu, 21 Sep 2023 08:20:25 +0200 Subject: [PATCH] Add device name to Welcome Screen Fix TitleCase --- .../compose/screen/WelcomeScreen.kt | 65 ++++++++++++++++++- .../compose/state/WelcomeUiState.kt | 3 +- .../mullvadvpn/viewmodel/WelcomeViewModel.kt | 7 +- .../viewmodel/WelcomeViewModelTest.kt | 8 +-- .../resource/src/main/res/values/strings.xml | 3 + 5 files changed, 77 insertions(+), 9 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 b7dc83ac48a4..ca0fe3c49fe1 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 @@ -4,6 +4,7 @@ import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -11,14 +12,22 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +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.LocalContext +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow @@ -28,6 +37,7 @@ import net.mullvad.mullvadvpn.compose.button.RedeemVoucherButton import net.mullvad.mullvadvpn.compose.button.SitePaymentButton import net.mullvad.mullvadvpn.compose.component.ScaffoldWithTopBar import net.mullvad.mullvadvpn.compose.component.drawVerticalScrollbar +import net.mullvad.mullvadvpn.compose.dialog.InfoDialog import net.mullvad.mullvadvpn.compose.state.WelcomeUiState import net.mullvad.mullvadvpn.lib.common.util.SdkUtils import net.mullvad.mullvadvpn.lib.common.util.groupWithSpaces @@ -35,6 +45,7 @@ import net.mullvad.mullvadvpn.lib.common.util.openAccountPageInBrowser import net.mullvad.mullvadvpn.lib.theme.AlphaTopBar import net.mullvad.mullvadvpn.lib.theme.AppTheme import net.mullvad.mullvadvpn.lib.theme.Dimens +import net.mullvad.mullvadvpn.lib.theme.MullvadWhite import net.mullvad.mullvadvpn.ui.extension.copyToClipboard import net.mullvad.mullvadvpn.viewmodel.WelcomeViewModel @@ -44,7 +55,7 @@ private fun PreviewWelcomeScreen() { AppTheme { WelcomeScreen( showSitePayment = true, - uiState = WelcomeUiState(accountNumber = "4444555566667777"), + uiState = WelcomeUiState(accountNumber = "4444555566667777", deviceName = "Happy Mole"), viewActions = MutableSharedFlow().asSharedFlow(), onSitePaymentClick = {}, onRedeemVoucherClick = {}, @@ -150,13 +161,61 @@ fun WelcomeScreen( context.getString(R.string.copied_mullvad_account_number) ) } - } - ?: Modifier + } ?: Modifier ) .padding(vertical = Dimens.smallPadding, horizontal = Dimens.sideMargin), style = MaterialTheme.typography.headlineSmall, color = MaterialTheme.colorScheme.onPrimary ) + Row( + modifier = Modifier.padding(horizontal = Dimens.sideMargin), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + modifier = Modifier.weight(1f, fill = false), + text = + buildString { + append(stringResource(id = R.string.device_name)) + append(": ") + append(uiState.deviceName) + }, + style = MaterialTheme.typography.bodySmall, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + color = MaterialTheme.colorScheme.onPrimary + ) + + var showDeviceNameDialog by remember { mutableStateOf(false) } + IconButton( + modifier = Modifier.align(Alignment.CenterVertically), + onClick = { showDeviceNameDialog = true } + ) { + Icon( + painter = painterResource(id = R.drawable.icon_info), + contentDescription = null, + tint = MullvadWhite + ) + } + if (showDeviceNameDialog) { + InfoDialog( + message = + buildString { + appendLine( + stringResource(id = R.string.device_name_info_first_paragraph) + ) + appendLine() + appendLine( + stringResource(id = R.string.device_name_info_second_paragraph) + ) + appendLine() + appendLine( + stringResource(id = R.string.device_name_info_third_paragraph) + ) + }, + onDismiss = { showDeviceNameDialog = false } + ) + } + } Text( text = buildString { diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/WelcomeUiState.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/WelcomeUiState.kt index b8a12ce4ae53..c6959f23e016 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/WelcomeUiState.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/WelcomeUiState.kt @@ -4,5 +4,6 @@ import net.mullvad.mullvadvpn.model.TunnelState data class WelcomeUiState( val tunnelState: TunnelState = TunnelState.Disconnected, - val accountNumber: String? = null + val accountNumber: String? = null, + val deviceName: String? = null ) 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 eaba6ad78417..eb25ad1268ed 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 @@ -18,6 +18,7 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import net.mullvad.mullvadvpn.compose.state.WelcomeUiState import net.mullvad.mullvadvpn.constant.ACCOUNT_EXPIRY_POLL_INTERVAL +import net.mullvad.mullvadvpn.lib.common.util.capitalizeFirstCharOfEachWord import net.mullvad.mullvadvpn.model.TunnelState import net.mullvad.mullvadvpn.repository.AccountRepository import net.mullvad.mullvadvpn.repository.DeviceRepository @@ -57,7 +58,11 @@ class WelcomeViewModel( it.addDebounceForUnknownState(UNKNOWN_STATE_DEBOUNCE_DELAY_MILLISECONDS) } ) { tunnelState, deviceState -> - WelcomeUiState(tunnelState = tunnelState, accountNumber = deviceState.token()) + WelcomeUiState( + tunnelState = tunnelState, + accountNumber = deviceState.token(), + deviceName = deviceState.deviceName()?.capitalizeFirstCharOfEachWord() + ) } } .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), WelcomeUiState()) diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModelTest.kt index 88f6f0c9cb51..1c61b115057b 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModelTest.kt @@ -16,6 +16,7 @@ import net.mullvad.mullvadvpn.compose.state.WelcomeUiState import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule import net.mullvad.mullvadvpn.model.AccountAndDevice import net.mullvad.mullvadvpn.model.AccountExpiry +import net.mullvad.mullvadvpn.model.Device import net.mullvad.mullvadvpn.model.DeviceState import net.mullvad.mullvadvpn.model.TunnelState import net.mullvad.mullvadvpn.repository.AccountRepository @@ -124,6 +125,8 @@ class WelcomeViewModelTest { runTest(testCoroutineRule.testDispatcher) { // Arrange val expectedAccountNumber = "4444555566667777" + val device: Device = mockk() + every { device.name } returns "" // Act, Assert viewModel.uiState.test { @@ -133,10 +136,7 @@ class WelcomeViewModelTest { deviceState.value = DeviceState.LoggedIn( accountAndDevice = - AccountAndDevice( - account_token = expectedAccountNumber, - device = mockk() - ) + AccountAndDevice(account_token = expectedAccountNumber, device = device) ) val result = awaitItem() assertEquals(expectedAccountNumber, result.accountNumber) diff --git a/android/lib/resource/src/main/res/values/strings.xml b/android/lib/resource/src/main/res/values/strings.xml index ac979ed2106c..6b1486fe5623 100644 --- a/android/lib/resource/src/main/res/values/strings.xml +++ b/android/lib/resource/src/main/res/values/strings.xml @@ -51,6 +51,9 @@ Privacy policy Account number Device name + This is the name assigned to the device. Each device logged in on a Mullvad account gets a unique name that helps you identify it when you manage your devices in the app or on the website. + You can have up to 5 devices logged in on one Mullvad account. + If you log out, the device and the device name is removed. When you log back in again, the device will get a new name. Mullvad account number Copied Mullvad account number to clipboard Paid until