-
Notifications
You must be signed in to change notification settings - Fork 336
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'migrate-account-view-to-compose-droid-63'
- Loading branch information
Showing
28 changed files
with
732 additions
and
1,223 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
128 changes: 128 additions & 0 deletions
128
...oid/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreenTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
package net.mullvad.mullvadvpn.compose.screen | ||
|
||
import androidx.compose.material3.ExperimentalMaterial3Api | ||
import androidx.compose.ui.test.junit4.createComposeRule | ||
import androidx.compose.ui.test.onNodeWithText | ||
import androidx.compose.ui.test.performClick | ||
import io.mockk.MockKAnnotations | ||
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.AccountViewModel | ||
import org.junit.Before | ||
import org.junit.Rule | ||
import org.junit.Test | ||
|
||
class AccountScreenTest { | ||
@get:Rule val composeTestRule = createComposeRule() | ||
|
||
@Before | ||
fun setup() { | ||
MockKAnnotations.init(this) | ||
} | ||
|
||
@OptIn(ExperimentalMaterial3Api::class) | ||
@Test | ||
fun testDefaultState() { | ||
// Arrange | ||
composeTestRule.setContent { | ||
AccountScreen( | ||
uiState = | ||
AccountUiState( | ||
deviceName = DUMMY_DEVICE_NAME, | ||
accountNumber = DUMMY_ACCOUNT_NUMBER, | ||
accountExpiry = null | ||
), | ||
viewActions = MutableSharedFlow<AccountViewModel.ViewAction>().asSharedFlow() | ||
) | ||
} | ||
|
||
// Assert | ||
composeTestRule.apply { | ||
onNodeWithText("Redeem voucher").assertExists() | ||
onNodeWithText("Log out").assertExists() | ||
} | ||
} | ||
|
||
@OptIn(ExperimentalMaterial3Api::class) | ||
@Test | ||
fun testManageAccountClick() { | ||
// Arrange | ||
val mockedClickHandler: () -> Unit = mockk(relaxed = true) | ||
composeTestRule.setContent { | ||
AccountScreen( | ||
uiState = | ||
AccountUiState( | ||
deviceName = DUMMY_DEVICE_NAME, | ||
accountNumber = DUMMY_ACCOUNT_NUMBER, | ||
accountExpiry = null | ||
), | ||
viewActions = MutableSharedFlow<AccountViewModel.ViewAction>().asSharedFlow(), | ||
onManageAccountClick = mockedClickHandler | ||
) | ||
} | ||
|
||
// Act | ||
composeTestRule.onNodeWithText("Manage account").performClick() | ||
|
||
// Assert | ||
verify { mockedClickHandler.invoke() } | ||
} | ||
|
||
@OptIn(ExperimentalMaterial3Api::class) | ||
@Test | ||
fun testRedeemVoucherClick() { | ||
// Arrange | ||
val mockedClickHandler: () -> Unit = mockk(relaxed = true) | ||
composeTestRule.setContent { | ||
AccountScreen( | ||
uiState = | ||
AccountUiState( | ||
deviceName = DUMMY_DEVICE_NAME, | ||
accountNumber = DUMMY_ACCOUNT_NUMBER, | ||
accountExpiry = null | ||
), | ||
viewActions = MutableSharedFlow<AccountViewModel.ViewAction>().asSharedFlow(), | ||
onRedeemVoucherClick = mockedClickHandler | ||
) | ||
} | ||
|
||
// Act | ||
composeTestRule.onNodeWithText("Redeem voucher").performClick() | ||
|
||
// Assert | ||
verify { mockedClickHandler.invoke() } | ||
} | ||
|
||
@OptIn(ExperimentalMaterial3Api::class) | ||
@Test | ||
fun testLogoutClick() { | ||
// Arrange | ||
val mockedClickHandler: () -> Unit = mockk(relaxed = true) | ||
composeTestRule.setContent { | ||
AccountScreen( | ||
uiState = | ||
AccountUiState( | ||
deviceName = DUMMY_DEVICE_NAME, | ||
accountNumber = DUMMY_ACCOUNT_NUMBER, | ||
accountExpiry = null | ||
), | ||
viewActions = MutableSharedFlow<AccountViewModel.ViewAction>().asSharedFlow(), | ||
onLogoutClick = mockedClickHandler | ||
) | ||
} | ||
|
||
// Act | ||
composeTestRule.onNodeWithText("Log out").performClick() | ||
|
||
// Assert | ||
verify { mockedClickHandler.invoke() } | ||
} | ||
|
||
companion object { | ||
private const val DUMMY_DEVICE_NAME = "fake_name" | ||
private const val DUMMY_ACCOUNT_NUMBER = "fake_number" | ||
} | ||
} |
15 changes: 15 additions & 0 deletions
15
android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/AccountNumberView.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package net.mullvad.mullvadvpn.compose.component | ||
|
||
import androidx.compose.runtime.Composable | ||
import net.mullvad.mullvadvpn.lib.common.util.groupPasswordModeWithSpaces | ||
import net.mullvad.mullvadvpn.lib.common.util.groupWithSpaces | ||
|
||
@Composable | ||
fun AccountNumberView(accountNumber: String, doObfuscateWithPasswordDots: Boolean) { | ||
InformationView( | ||
content = | ||
if (doObfuscateWithPasswordDots) accountNumber.groupPasswordModeWithSpaces() | ||
else accountNumber.groupWithSpaces(), | ||
whenMissing = MissingPolicy.SHOW_SPINNER | ||
) | ||
} |
66 changes: 66 additions & 0 deletions
66
...d/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/CopyableObfuscationView.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package net.mullvad.mullvadvpn.compose.component | ||
|
||
import androidx.compose.foundation.Image | ||
import androidx.compose.foundation.clickable | ||
import androidx.compose.foundation.layout.Row | ||
import androidx.compose.foundation.layout.Spacer | ||
import androidx.compose.foundation.layout.padding | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.mutableStateOf | ||
import androidx.compose.runtime.remember | ||
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 net.mullvad.mullvadvpn.R | ||
import net.mullvad.mullvadvpn.compose.theme.Dimens | ||
import net.mullvad.mullvadvpn.lib.common.util.SdkUtils | ||
import net.mullvad.mullvadvpn.ui.extension.copyToClipboard | ||
|
||
@Preview | ||
@Composable | ||
private fun PreviewCopyableObfuscationView() { | ||
CopyableObfuscationView("1111222233334444") | ||
} | ||
|
||
@Composable | ||
fun CopyableObfuscationView(content: String) { | ||
val context = LocalContext.current | ||
val shouldObfuscated = remember { mutableStateOf(true) } | ||
|
||
Row(verticalAlignment = Alignment.CenterVertically) { | ||
AccountNumberView( | ||
accountNumber = content, | ||
doObfuscateWithPasswordDots = shouldObfuscated.value | ||
) | ||
Spacer(modifier = Modifier.weight(1f)) | ||
Image( | ||
painter = | ||
painterResource( | ||
id = if (shouldObfuscated.value) R.drawable.icon_hide else R.drawable.icon_show | ||
), | ||
modifier = | ||
Modifier.clickable { shouldObfuscated.value = shouldObfuscated.value.not() } | ||
.padding(start = Dimens.sideMargin), | ||
contentDescription = stringResource(id = R.string.copy_account_number) | ||
) | ||
Image( | ||
painter = painterResource(id = R.drawable.icon_copy), | ||
modifier = | ||
Modifier.clickable { | ||
context.copyToClipboard( | ||
content = content, | ||
clipboardLabel = context.getString(R.string.mullvad_account_number) | ||
) | ||
SdkUtils.showCopyToastIfNeeded( | ||
context, | ||
context.getString(R.string.copied_mullvad_account_number) | ||
) | ||
} | ||
.padding(start = Dimens.sideMargin, end = Dimens.sideMargin), | ||
contentDescription = stringResource(id = R.string.copy_account_number) | ||
) | ||
} | ||
} |
78 changes: 78 additions & 0 deletions
78
android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/InformationView.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
package net.mullvad.mullvadvpn.compose.component | ||
|
||
import androidx.compose.foundation.layout.height | ||
import androidx.compose.foundation.layout.padding | ||
import androidx.compose.foundation.layout.width | ||
import androidx.compose.material3.CircularProgressIndicator | ||
import androidx.compose.material3.MaterialTheme | ||
import androidx.compose.material3.Text | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.tooling.preview.Preview | ||
import net.mullvad.mullvadvpn.compose.theme.Dimens | ||
|
||
@Preview | ||
@Composable | ||
private fun PreviewInformationView() { | ||
InformationView(content = "test content") | ||
} | ||
|
||
@Preview | ||
@Composable | ||
private fun PreviewEmptyInformationView() { | ||
InformationView(content = "", whenMissing = MissingPolicy.SHOW_SPINNER) | ||
} | ||
|
||
@Composable | ||
fun InformationView(content: String, whenMissing: MissingPolicy = MissingPolicy.SHOW_VIEW) { | ||
return if (content.isNotEmpty()) { | ||
Text( | ||
style = MaterialTheme.typography.titleSmall, | ||
text = content, | ||
modifier = | ||
Modifier.padding( | ||
start = Dimens.sideMargin, | ||
end = Dimens.sideMargin, | ||
top = Dimens.smallPadding, | ||
bottom = Dimens.mediumPadding | ||
) | ||
) | ||
} else { | ||
when (whenMissing) { | ||
MissingPolicy.SHOW_VIEW -> { | ||
Text( | ||
style = MaterialTheme.typography.titleMedium, | ||
text = content, | ||
modifier = | ||
Modifier.padding( | ||
start = Dimens.sideMargin, | ||
end = Dimens.sideMargin, | ||
top = Dimens.smallPadding, | ||
bottom = Dimens.mediumPadding | ||
) | ||
) | ||
} | ||
MissingPolicy.HIDE_VIEW -> {} | ||
MissingPolicy.SHOW_SPINNER -> { | ||
CircularProgressIndicator( | ||
modifier = | ||
Modifier.padding( | ||
start = Dimens.sideMargin, | ||
end = Dimens.sideMargin, | ||
top = Dimens.smallPadding, | ||
bottom = Dimens.mediumPadding | ||
) | ||
.height(Dimens.loadingSpinnerSizeMedium) | ||
.width(Dimens.loadingSpinnerSizeMedium), | ||
color = MaterialTheme.colorScheme.onSecondary | ||
) | ||
} | ||
} | ||
} | ||
} | ||
|
||
enum class MissingPolicy { | ||
SHOW_VIEW, | ||
HIDE_VIEW, | ||
SHOW_SPINNER | ||
} |
Oops, something went wrong.