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 Aug 22, 2023
1 parent 3413af0 commit 4edf1fa
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ fun InformationView(
maxLines: Int = 1
) {
return if (content.isNotEmpty()) {

AutoResizeText(
style = MaterialTheme.typography.titleSmall,
text = content,
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 @@ -33,8 +37,10 @@ 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.dialog.DeviceNameInfoDialog
import net.mullvad.mullvadvpn.compose.state.AccountUiState
import net.mullvad.mullvadvpn.compose.theme.Dimens
import net.mullvad.mullvadvpn.compose.theme.MullvadWhite
import net.mullvad.mullvadvpn.lib.common.util.capitalizeFirstCharOfEachWord
import net.mullvad.mullvadvpn.lib.common.util.openAccountPageInBrowser
import net.mullvad.mullvadvpn.util.toExpiryDateString
Expand All @@ -46,7 +52,7 @@ import net.mullvad.mullvadvpn.viewmodel.AccountViewModel
private fun PreviewAccountScreen() {
AccountScreen(
uiState =
AccountUiState(
AccountUiState.DefaultUiState(
deviceName = "Test Name",
accountNumber = "1234123412341234",
accountExpiry = null
Expand All @@ -60,6 +66,8 @@ private fun PreviewAccountScreen() {
fun AccountScreen(
uiState: AccountUiState,
viewActions: SharedFlow<AccountViewModel.ViewAction>,
onDeviceNameInfoClick: () -> Unit = {},
onDismissInfoClick: () -> Unit = {},
onRedeemVoucherClick: () -> Unit = {},
onManageAccountClick: () -> Unit = {},
onLogoutClick: () -> Unit = {},
Expand All @@ -69,6 +77,15 @@ fun AccountScreen(
val state = rememberCollapsingToolbarScaffoldState()
val progress = state.toolbarState.progress

when (uiState) {
is AccountUiState.DeviceNameDialogUiState -> {
DeviceNameInfoDialog(onDismissInfoClick)
}
else -> {
// NOOP
}
}

CollapsingToolbarScaffold(
backgroundColor = MaterialTheme.colorScheme.background,
modifier = Modifier.fillMaxSize(),
Expand Down Expand Up @@ -117,12 +134,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
)

Row {
InformationView(
content = uiState.deviceName.capitalizeFirstCharOfEachWord(),
whenMissing = MissingPolicy.SHOW_SPINNER
)
val horizontalPadding = Dimens.mediumPadding
val verticalPadding = Dimens.infoButtonVerticalPadding
Icon(
modifier =
Modifier.clickable { onDeviceNameInfoClick() }
.padding(start = horizontalPadding, end = horizontalPadding)
.align(Alignment.CenterVertically),
painter = painterResource(id = R.drawable.icon_info),
contentDescription = null,
tint = MullvadWhite
)
}
Text(
style = MaterialTheme.typography.labelMedium,
text = stringResource(id = R.string.account_number),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,20 @@ package net.mullvad.mullvadvpn.compose.state

import org.joda.time.DateTime

data class AccountUiState(
val deviceName: String,
val accountNumber: String,
sealed interface AccountUiState {
val deviceName: String
val accountNumber: String
val accountExpiry: DateTime?
)

data class DefaultUiState(
override val deviceName: String = "",
override val accountNumber: String = "",
override val accountExpiry: DateTime? = null
) : AccountUiState

data class DeviceNameDialogUiState(
override val deviceName: String,
override val accountNumber: String,
override val accountExpiry: DateTime?
) : AccountUiState
}
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
Expand Up @@ -3,11 +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.map
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
Expand All @@ -24,27 +27,33 @@ class AccountViewModel(
private val _viewActions = MutableSharedFlow<ViewAction>(extraBufferCapacity = 1)
val viewActions = _viewActions.asSharedFlow()

private val vmState: StateFlow<AccountUiState> =
combine(deviceRepository.deviceState, accountRepository.accountExpiryState) {
private val dialogState =
MutableStateFlow<AccountScreenDialogState>(AccountScreenDialogState.NoDialog)

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

fun onManageAccountClick() {
viewModelScope.launch {
Expand All @@ -60,6 +69,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 OpenAccountView(val token: String) : ViewAction()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package net.mullvad.mullvadvpn.viewmodel

import net.mullvad.mullvadvpn.compose.state.AccountUiState
import net.mullvad.mullvadvpn.model.AccountExpiry
import net.mullvad.mullvadvpn.model.DeviceState

data class AccountViewModelState(
val deviceState: DeviceState,
val accountExpiry: AccountExpiry,
val dialogState: AccountScreenDialogState
) {
fun toUiState(): AccountUiState {
return when (dialogState) {
is AccountScreenDialogState.NoDialog ->
AccountUiState.DefaultUiState(
deviceName = deviceState.deviceName() ?: "",
accountNumber = deviceState.token() ?: "",
accountExpiry = accountExpiry.date()
)
is AccountScreenDialogState.DeviceNameInfoDialog ->
AccountUiState.DeviceNameDialogUiState(
deviceName = deviceState.deviceName() ?: "",
accountNumber = deviceState.token() ?: "",
accountExpiry = accountExpiry.date()
)
}
}

companion object {
fun default() =
AccountViewModelState(
deviceState = DeviceState.Unknown,
accountExpiry = AccountExpiry.Missing,
dialogState = AccountScreenDialogState.NoDialog
)
}
}

sealed class AccountScreenDialogState {
data object NoDialog : AccountScreenDialogState()

data object DeviceNameInfoDialog : AccountScreenDialogState()
}
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 @@ -217,4 +217,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 4edf1fa

Please sign in to comment.