Skip to content

Commit

Permalink
Add Server IP Overrides feature
Browse files Browse the repository at this point in the history
Fix remarks

Refactor into new viewmodel

Fix more things

Fix remarks

Fix translations
  • Loading branch information
Rawa committed Mar 19, 2024
1 parent fc7a0c2 commit f33bdfd
Show file tree
Hide file tree
Showing 64 changed files with 1,977 additions and 42 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Line wrap the file at 100 chars. Th
- Add auto connect and lockdown mode guide on platforms that has system vpn settings.
- Add 3D map to Connect screen.
- Add the ability to create and manage custom lists of relays.
- Add Server IP overrides feature.

### Changed
- Change default obfuscation setting to `auto`.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package net.mullvad.mullvadvpn.compose.dialog

import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import io.mockk.MockKAnnotations
import io.mockk.mockk
import io.mockk.verify
import net.mullvad.mullvadvpn.compose.createEdgeToEdgeComposeExtension
import net.mullvad.mullvadvpn.compose.setContentWithTheme
import net.mullvad.mullvadvpn.compose.test.RESET_SERVER_IP_OVERRIDE_CANCEL_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.RESET_SERVER_IP_OVERRIDE_RESET_TEST_TAG
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.RegisterExtension

class ResetServerIPOverridesConfirmationDialogTest {
@OptIn(ExperimentalTestApi::class)
@JvmField
@RegisterExtension
val composeExtension = createEdgeToEdgeComposeExtension()

@BeforeEach
fun setup() {
MockKAnnotations.init(this)
}

@Test
fun testCancelClick() =
composeExtension.use {
val clickHandler: () -> Unit = mockk(relaxed = true)

// Arrange
setContentWithTheme {
ResetServerIpOverridesConfirmationDialog(
onNavigateBack = clickHandler,
onClearAllOverrides = {}
)
}

// Act
onNodeWithTag(RESET_SERVER_IP_OVERRIDE_CANCEL_TEST_TAG).performClick()

// Assert
verify { clickHandler() }
}

@Test
fun testResetClick() =
composeExtension.use {
val clickHandler: () -> Unit = mockk(relaxed = true)

// Arrange
setContentWithTheme {
ResetServerIpOverridesConfirmationDialog(
onNavigateBack = {},
onClearAllOverrides = clickHandler
)
}

// Act
onNodeWithTag(RESET_SERVER_IP_OVERRIDE_RESET_TEST_TAG).performClick()

// Assert
verify { clickHandler() }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package net.mullvad.mullvadvpn.compose.screen

import androidx.compose.runtime.Composable
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.onNodeWithTag
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 net.mullvad.mullvadvpn.compose.createEdgeToEdgeComposeExtension
import net.mullvad.mullvadvpn.compose.setContentWithTheme
import net.mullvad.mullvadvpn.compose.test.SERVER_IP_OVERRIDES_IMPORT_BY_FILE_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.SERVER_IP_OVERRIDES_IMPORT_BY_TEXT_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.SERVER_IP_OVERRIDE_IMPORT_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.SERVER_IP_OVERRIDE_INFO_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.SERVER_IP_OVERRIDE_MORE_VERT_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.SERVER_IP_OVERRIDE_RESET_OVERRIDES_TEST_TAG
import net.mullvad.mullvadvpn.viewmodel.ServerIpOverridesViewState
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.RegisterExtension

@ExperimentalTestApi
class ServerIpOverridesScreenTest {
@JvmField @RegisterExtension val composeExtension = createEdgeToEdgeComposeExtension()

@BeforeEach
fun setup() {
MockKAnnotations.init(this)
}

@Suppress("TestFunctionName")
@Composable
private fun ScreenWithDefault(
state: ServerIpOverridesViewState,
onBackClick: () -> Unit = {},
onInfoClick: () -> Unit = {},
onResetOverridesClick: () -> Unit = {},
onImportByFile: () -> Unit = {},
onImportByText: () -> Unit = {},
) {
ServerIpOverridesScreen(
state = state,
onBackClick = onBackClick,
onInfoClick = onInfoClick,
onResetOverridesClick = onResetOverridesClick,
onImportByFile = onImportByFile,
onImportByText = onImportByText
)
}

@Test
fun testOverridesInactive() =
composeExtension.use {
// Arrange
setContentWithTheme {
ScreenWithDefault(state = ServerIpOverridesViewState.Loaded(false))
}

// Assert
onNodeWithText("Overrides inactive").assertExists()
}

@Test
fun testOverridesActive() =
composeExtension.use {
// Arrange
setContentWithTheme {
ScreenWithDefault(state = ServerIpOverridesViewState.Loaded(true))
}

// Assert
onNodeWithText("Overrides active").assertExists()
}

@Test
fun testOverridesActiveShowsWarningOnImport() =
composeExtension.use {
// Arrange
setContentWithTheme {
ScreenWithDefault(state = ServerIpOverridesViewState.Loaded(true))
}

// Act
onNodeWithTag(testTag = SERVER_IP_OVERRIDE_IMPORT_TEST_TAG).performClick()

// Assert
onNodeWithText(
"Importing new overrides might replace some previously imported overrides."
)
.assertExists()
}

@Test
fun testInfoClick() =
composeExtension.use {
// Arrange
val clickHandler: () -> Unit = mockk(relaxed = true)
setContentWithTheme {
ScreenWithDefault(
state = ServerIpOverridesViewState.Loaded(false),
onInfoClick = clickHandler
)
}

// Act
onNodeWithTag(SERVER_IP_OVERRIDE_INFO_TEST_TAG).performClick()

// Assert
verify { clickHandler() }
}

@Test
fun testResetClick() =
composeExtension.use {
// Arrange
val clickHandler: () -> Unit = mockk(relaxed = true)
setContentWithTheme {
ScreenWithDefault(
state = ServerIpOverridesViewState.Loaded(true),
onResetOverridesClick = clickHandler
)
}

// Act
onNodeWithTag(SERVER_IP_OVERRIDE_MORE_VERT_TEST_TAG).performClick()
onNodeWithTag(SERVER_IP_OVERRIDE_RESET_OVERRIDES_TEST_TAG).performClick()

// Assert
verify { clickHandler() }
}

@Test
fun testImportByFile() =
composeExtension.use {
// Arrange
val clickHandler: () -> Unit = mockk(relaxed = true)
setContentWithTheme {
ScreenWithDefault(
state = ServerIpOverridesViewState.Loaded(false),
onImportByFile = clickHandler
)
}

// Act
onNodeWithTag(SERVER_IP_OVERRIDE_IMPORT_TEST_TAG).performClick()
onNodeWithTag(SERVER_IP_OVERRIDES_IMPORT_BY_FILE_TEST_TAG).performClick()

// Assert
verify { clickHandler() }
}

@Test
fun testImportByText() =
composeExtension.use {
// Arrange
val clickHandler: () -> Unit = mockk(relaxed = true)
setContentWithTheme {
ScreenWithDefault(
state = ServerIpOverridesViewState.Loaded(false),
onImportByText = clickHandler
)
}

// Act
onNodeWithTag(SERVER_IP_OVERRIDE_IMPORT_TEST_TAG).performClick()
onNodeWithTag(SERVER_IP_OVERRIDES_IMPORT_BY_TEXT_TEST_TAG).performClick()

// Assert
verify { clickHandler() }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package net.mullvad.mullvadvpn.compose.button

import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import net.mullvad.mullvadvpn.R

@Composable
fun InfoIconButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
contentDescription: String? = null,
iconTint: Color = MaterialTheme.colorScheme.onPrimary
) {
IconButton(modifier = modifier, onClick = onClick) {
Icon(
painter = painterResource(id = R.drawable.icon_info),
contentDescription = contentDescription,
tint = iconTint
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@ private fun PreviewIconCell() {
@Composable
fun IconCell(
iconId: Int?,
contentDescription: String? = null,
title: String,
modifier: Modifier = Modifier,
contentDescription: String? = null,
titleStyle: TextStyle = MaterialTheme.typography.labelLarge,
titleColor: Color = MaterialTheme.colorScheme.onPrimary,
onClick: () -> Unit = {},
background: Color = MaterialTheme.colorScheme.primary,
enabled: Boolean = true,
enabled: Boolean = true
) {
BaseCell(
headlineContent = {
Expand All @@ -49,6 +50,7 @@ fun IconCell(
},
onCellClicked = onClick,
background = background,
isRowEnabled = enabled
isRowEnabled = enabled,
modifier = modifier
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package net.mullvad.mullvadvpn.compose.cell

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.compose.component.MullvadCircularProgressIndicatorSmall
import net.mullvad.mullvadvpn.lib.theme.AppTheme
import net.mullvad.mullvadvpn.lib.theme.Dimens
import net.mullvad.mullvadvpn.lib.theme.color.AlphaInactive
import net.mullvad.mullvadvpn.lib.theme.color.AlphaVisible
import net.mullvad.mullvadvpn.lib.theme.color.selected

@Preview
@Composable
private fun PreviewServerIpOverridesCell() {
AppTheme { ServerIpOverridesCell(active = true) }
}

@Composable
fun ServerIpOverridesCell(
active: Boolean?,
modifier: Modifier = Modifier,
activeColor: Color = MaterialTheme.colorScheme.selected,
inactiveColor: Color = MaterialTheme.colorScheme.error,
) {
BaseCell(
modifier = modifier,
iconView = {
if (active == null) {
MullvadCircularProgressIndicatorSmall()
} else {
Box(
modifier =
Modifier.size(Dimens.relayCircleSize)
.background(
color =
when {
active -> activeColor
else -> inactiveColor
},
shape = CircleShape
)
)
}
},
headlineContent = {
if (active != null) {
Text(
text =
if (active) stringResource(id = R.string.server_ip_overrides_active)
else stringResource(id = R.string.server_ip_overrides_inactive),
color = MaterialTheme.colorScheme.onPrimary,
modifier =
Modifier.weight(1f)
.alpha(
if (active) {
AlphaVisible
} else {
AlphaInactive
}
)
.padding(
horizontal = Dimens.smallPadding,
vertical = Dimens.mediumPadding
)
)
}
},
isRowEnabled = false
)
}
Loading

0 comments on commit f33bdfd

Please sign in to comment.