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 28, 2023
1 parent 6541297 commit f59dea5
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 41 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,20 @@
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 =
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 = onDismiss
)
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
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.Surface
import androidx.compose.material3.Text
Expand All @@ -19,6 +21,7 @@ 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 com.google.accompanist.systemuicontroller.rememberSystemUiController
Expand All @@ -35,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.constant.IS_PLAY_BUILD
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 @@ -52,7 +57,8 @@ private fun PreviewAccountScreen() {
AccountUiState(
deviceName = "Test Name",
accountNumber = "1234123412341234",
accountExpiry = null
accountExpiry = null,
dialogState = AccountScreenDialogState.NoDialog
),
viewActions = MutableSharedFlow<AccountViewModel.ViewAction>().asSharedFlow(),
enterTransitionEndAction = MutableSharedFlow()
Expand All @@ -64,6 +70,8 @@ private fun PreviewAccountScreen() {
fun AccountScreen(
uiState: AccountUiState,
viewActions: SharedFlow<AccountViewModel.ViewAction>,
onDeviceNameInfoClick: () -> Unit = {},
onDismissInfoClick: () -> Unit = {},
enterTransitionEndAction: SharedFlow<Unit>,
onRedeemVoucherClick: () -> Unit = {},
onManageAccountClick: () -> Unit = {},
Expand All @@ -79,6 +87,10 @@ fun AccountScreen(
LaunchedEffect(Unit) {
enterTransitionEndAction.collect { systemUiController.setStatusBarColor(backgroundColor) }
}
if (uiState.dialogState == AccountScreenDialogState.DeviceNameInfoDialog) {
DeviceNameInfoDialog(onDismissInfoClick)
}

CollapsingToolbarScaffold(
backgroundColor = MaterialTheme.colorScheme.background,
modifier = Modifier.fillMaxSize(),
Expand Down Expand Up @@ -127,10 +139,21 @@ fun AccountScreen(
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
)
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,
Expand All @@ -142,9 +165,7 @@ fun AccountScreen(
top = Dimens.smallPadding
)
)

CopyableObfuscationView(content = uiState.accountNumber)

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 @@ -36,10 +36,11 @@ class AccountFragment : BaseFragment(), StatusBarPainter, NavigationBarPainter {
enterTransitionEndAction = vm.enterTransitionEndAction,
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 @@ -25,27 +26,24 @@ class AccountViewModel(
private val _enterTransitionEndAction = MutableSharedFlow<Unit>()
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())

val enterTransitionEndAction = _enterTransitionEndAction.asSharedFlow()

Expand All @@ -63,6 +61,18 @@ class AccountViewModel(
accountRepository.logout()
}

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

fun onDismissInfoClick() {
hideDialog()
}

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

fun onTransitionAnimationEnd() {
viewModelScope.launch { _enterTransitionEndAction.emit(Unit) }
}
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

0 comments on commit f59dea5

Please sign in to comment.