Skip to content

Commit

Permalink
Add info dialog for device name
Browse files Browse the repository at this point in the history
  • Loading branch information
sabercodic committed Sep 11, 2023
1 parent 4d28de5 commit d7dee1d
Show file tree
Hide file tree
Showing 9 changed files with 118 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import net.mullvad.mullvadvpn.compose.state.AccountUiState
import net.mullvad.mullvadvpn.viewmodel.AccountUiState
import net.mullvad.mullvadvpn.viewmodel.AccountViewModel
import org.junit.Before
import org.junit.Rule
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package net.mullvad.mullvadvpn.compose.dialog

import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import net.mullvad.mullvadvpn.R

@Composable
fun DeviceNameInfoDialog(onDismiss: () -> Unit) {
InfoDialog(
message = stringResource(id = R.string.local_network_sharing_info),
additionalInfo =
buildString {
appendLine(stringResource(id = R.string.device_name_info_part1))
appendLine(stringResource(id = R.string.device_name_info_part2))
appendLine(stringResource(id = R.string.device_name_info_part3))
},
onDismiss = onDismiss
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,26 @@ package net.mullvad.mullvadvpn.compose.screen

import androidx.compose.animation.animateContentSize
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.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
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.tooling.preview.Preview
import kotlinx.coroutines.flow.MutableSharedFlow
Expand All @@ -34,12 +38,14 @@ import net.mullvad.mullvadvpn.compose.component.CopyableObfuscationView
import net.mullvad.mullvadvpn.compose.component.InformationView
import net.mullvad.mullvadvpn.compose.component.MissingPolicy
import net.mullvad.mullvadvpn.compose.component.drawVerticalScrollbar
import net.mullvad.mullvadvpn.compose.state.AccountUiState
import net.mullvad.mullvadvpn.compose.dialog.DeviceNameInfoDialog
import net.mullvad.mullvadvpn.lib.common.constant.BuildTypes
import net.mullvad.mullvadvpn.lib.common.util.capitalizeFirstCharOfEachWord
import net.mullvad.mullvadvpn.lib.common.util.openAccountPageInBrowser
import net.mullvad.mullvadvpn.lib.theme.Dimens
import net.mullvad.mullvadvpn.util.toExpiryDateString
import net.mullvad.mullvadvpn.viewmodel.AccountScreenDialogState
import net.mullvad.mullvadvpn.viewmodel.AccountUiState
import net.mullvad.mullvadvpn.viewmodel.AccountViewModel

@OptIn(ExperimentalMaterial3Api::class)
Expand All @@ -51,7 +57,8 @@ private fun PreviewAccountScreen() {
AccountUiState(
deviceName = "Test Name",
accountNumber = "1234123412341234",
accountExpiry = null
accountExpiry = null,
dialogState = AccountScreenDialogState.NoDialog
),
viewActions = MutableSharedFlow<AccountViewModel.ViewAction>().asSharedFlow(),
)
Expand All @@ -62,6 +69,8 @@ private fun PreviewAccountScreen() {
fun AccountScreen(
uiState: AccountUiState,
viewActions: SharedFlow<AccountViewModel.ViewAction>,
onDeviceNameInfoClick: () -> Unit = {},
onDismissInfoClick: () -> Unit = {},
onRedeemVoucherClick: () -> Unit = {},
onManageAccountClick: () -> Unit = {},
onLogoutClick: () -> Unit = {},
Expand All @@ -71,6 +80,10 @@ fun AccountScreen(
val state = rememberCollapsingToolbarScaffoldState()
val progress = state.toolbarState.progress

if (uiState.dialogState == AccountScreenDialogState.DeviceNameInfoDialog) {
DeviceNameInfoDialog(onDismissInfoClick)
}

CollapsingToolbarScaffold(
backgroundColor = MaterialTheme.colorScheme.background,
modifier = Modifier.fillMaxSize(),
Expand Down Expand Up @@ -118,12 +131,23 @@ fun AccountScreen(
text = stringResource(id = R.string.device_name),
modifier = Modifier.padding(start = Dimens.sideMargin, end = Dimens.sideMargin)
)

InformationView(
content = uiState.deviceName.capitalizeFirstCharOfEachWord(),
whenMissing = MissingPolicy.SHOW_SPINNER
)

uiState.deviceName?.let {
Row {
InformationView(
content = it.capitalizeFirstCharOfEachWord(),
whenMissing = MissingPolicy.SHOW_SPINNER
)
Icon(
modifier =
Modifier.clickable { onDeviceNameInfoClick() }
.padding(start = Dimens.mediumPadding, end = Dimens.mediumPadding)
.align(Alignment.CenterVertically),
painter = painterResource(id = R.drawable.icon_info),
contentDescription = null,
tint = MaterialTheme.colorScheme.inverseSurface
)
}
}
Text(
style = MaterialTheme.typography.labelMedium,
text = stringResource(id = R.string.account_number),
Expand All @@ -134,9 +158,9 @@ fun AccountScreen(
top = Dimens.smallPadding
)
)

CopyableObfuscationView(content = uiState.accountNumber)

if (uiState.accountNumber != null) {
CopyableObfuscationView(content = uiState.accountNumber)
}
Text(
style = MaterialTheme.typography.labelMedium,
text = stringResource(id = R.string.paid_until),
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,11 @@ class AccountFragment : BaseFragment(), StatusBarPainter, NavigationBarPainter {
viewActions = vm.viewActions,
onRedeemVoucherClick = { openRedeemVoucherFragment() },
onManageAccountClick = vm::onManageAccountClick,
onLogoutClick = vm::onLogoutClick
) {
activity?.onBackPressed()
}
onLogoutClick = vm::onLogoutClick,
onDeviceNameInfoClick = vm::onDeviceNameInfoClick,
onDismissInfoClick = vm::onDismissInfoClick,
onBackClick = { activity?.onBackPressedDispatcher?.onBackPressed() }
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package net.mullvad.mullvadvpn.viewmodel

import net.mullvad.mullvadvpn.model.AccountExpiry
import net.mullvad.mullvadvpn.model.DeviceState
import org.joda.time.DateTime

data class AccountUiState(
val deviceName: String?,
val accountNumber: String?,
val accountExpiry: DateTime?,
val dialogState: AccountScreenDialogState = AccountScreenDialogState.NoDialog
) {
companion object {
fun default() =
AccountUiState(
deviceName = DeviceState.Unknown.deviceName(),
accountNumber = DeviceState.Unknown.token(),
accountExpiry = AccountExpiry.Missing.date(),
dialogState = AccountScreenDialogState.NoDialog
)
}
}

sealed class AccountScreenDialogState {
data object NoDialog : AccountScreenDialogState()

data object DeviceNameInfoDialog : AccountScreenDialogState()
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ package net.mullvad.mullvadvpn.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import net.mullvad.mullvadvpn.compose.state.AccountUiState
import net.mullvad.mullvadvpn.repository.AccountRepository
import net.mullvad.mullvadvpn.repository.DeviceRepository
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
Expand All @@ -24,27 +25,24 @@ class AccountViewModel(
private val _viewActions = MutableSharedFlow<ViewAction>(extraBufferCapacity = 1)
val viewActions = _viewActions.asSharedFlow()

private val dialogState =
MutableStateFlow<AccountScreenDialogState>(AccountScreenDialogState.NoDialog)

private val vmState: StateFlow<AccountUiState> =
combine(deviceRepository.deviceState, accountRepository.accountExpiryState) {
combine(deviceRepository.deviceState, accountRepository.accountExpiryState, dialogState) {
deviceState,
accountExpiry ->
accountExpiry,
dialogState ->
AccountUiState(
deviceName = deviceState.deviceName() ?: "",
accountNumber = deviceState.token() ?: "",
accountExpiry = accountExpiry.date()
deviceName = deviceState.deviceName(),
accountNumber = deviceState.token(),
accountExpiry = accountExpiry.date(),
dialogState = dialogState
)
}
.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(),
AccountUiState(deviceName = "", accountNumber = "", accountExpiry = null)
)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), AccountUiState.default())
val uiState =
vmState.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(),
AccountUiState(deviceName = "", accountNumber = "", accountExpiry = null)
)
vmState.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), AccountUiState.default())

fun onManageAccountClick() {
viewModelScope.launch {
Expand All @@ -60,6 +58,18 @@ class AccountViewModel(
accountRepository.logout()
}

fun onDeviceNameInfoClick() {
dialogState.update { AccountScreenDialogState.DeviceNameInfoDialog }
}

fun onDismissInfoClick() {
hideDialog()
}

private fun hideDialog() {
dialogState.update { AccountScreenDialogState.NoDialog }
}

sealed class ViewAction {
data class OpenAccountManagementPageInBrowser(val token: String) : ViewAction()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ class AccountViewModelTest {
// Act, Assert
viewModel.uiState.test {
var result = awaitItem()
assertEquals("", result.deviceName)
assertEquals(null, result.deviceName)
deviceState.value = DeviceState.LoggedIn(accountAndDevice = dummyAccountAndDevice)
result = awaitItem()
assertEquals(DUMMY_DEVICE_NAME, result.accountNumber)
Expand Down
3 changes: 3 additions & 0 deletions android/lib/resource/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -216,4 +216,7 @@
<string name="custom_port_dialog_remove">Remove custom port</string>
<string name="custom_port_dialog_valid_ranges">Valid ranges: %s</string>
<string name="custom_port_dialog_placeholder">Enter port</string>
<string name="device_name_info_part1">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.</string>
<string name="device_name_info_part2">You can have up to 5 devices logged in on one Mullvad account.</string>
<string name="device_name_info_part3">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.</string>
</resources>

0 comments on commit d7dee1d

Please sign in to comment.