Skip to content

Commit 2b40cfa

Browse files
committed
Merge branch 'create-server-ip-overrides-composable-droid-709'
2 parents fc7a0c2 + cb19d35 commit 2b40cfa

File tree

64 files changed

+1969
-42
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+1969
-42
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Line wrap the file at 100 chars. Th
3333
- Add auto connect and lockdown mode guide on platforms that has system vpn settings.
3434
- Add 3D map to Connect screen.
3535
- Add the ability to create and manage custom lists of relays.
36+
- Add Server IP overrides feature.
3637

3738
### Changed
3839
- Change default obfuscation setting to `auto`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package net.mullvad.mullvadvpn.compose.dialog
2+
3+
import androidx.compose.ui.test.ExperimentalTestApi
4+
import androidx.compose.ui.test.onNodeWithTag
5+
import androidx.compose.ui.test.performClick
6+
import io.mockk.MockKAnnotations
7+
import io.mockk.mockk
8+
import io.mockk.verify
9+
import net.mullvad.mullvadvpn.compose.createEdgeToEdgeComposeExtension
10+
import net.mullvad.mullvadvpn.compose.setContentWithTheme
11+
import net.mullvad.mullvadvpn.compose.test.RESET_SERVER_IP_OVERRIDE_CANCEL_TEST_TAG
12+
import net.mullvad.mullvadvpn.compose.test.RESET_SERVER_IP_OVERRIDE_RESET_TEST_TAG
13+
import org.junit.jupiter.api.BeforeEach
14+
import org.junit.jupiter.api.Test
15+
import org.junit.jupiter.api.extension.RegisterExtension
16+
17+
class ResetServerIPOverridesConfirmationDialogTest {
18+
@OptIn(ExperimentalTestApi::class)
19+
@JvmField
20+
@RegisterExtension
21+
val composeExtension = createEdgeToEdgeComposeExtension()
22+
23+
@BeforeEach
24+
fun setup() {
25+
MockKAnnotations.init(this)
26+
}
27+
28+
@Test
29+
fun ensure_cancel_click_works() =
30+
composeExtension.use {
31+
val clickHandler: () -> Unit = mockk(relaxed = true)
32+
33+
// Arrange
34+
setContentWithTheme {
35+
ResetServerIpOverridesConfirmationDialog(
36+
onNavigateBack = clickHandler,
37+
onClearAllOverrides = {}
38+
)
39+
}
40+
41+
// Act
42+
onNodeWithTag(RESET_SERVER_IP_OVERRIDE_CANCEL_TEST_TAG).performClick()
43+
44+
// Assert
45+
verify { clickHandler() }
46+
}
47+
48+
@Test
49+
fun ensure_reset_click_works() =
50+
composeExtension.use {
51+
val clickHandler: () -> Unit = mockk(relaxed = true)
52+
53+
// Arrange
54+
setContentWithTheme {
55+
ResetServerIpOverridesConfirmationDialog(
56+
onNavigateBack = {},
57+
onClearAllOverrides = clickHandler
58+
)
59+
}
60+
61+
// Act
62+
onNodeWithTag(RESET_SERVER_IP_OVERRIDE_RESET_TEST_TAG).performClick()
63+
64+
// Assert
65+
verify { clickHandler() }
66+
}
67+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
package net.mullvad.mullvadvpn.compose.screen
2+
3+
import androidx.compose.runtime.Composable
4+
import androidx.compose.ui.test.ExperimentalTestApi
5+
import androidx.compose.ui.test.onNodeWithTag
6+
import androidx.compose.ui.test.onNodeWithText
7+
import androidx.compose.ui.test.performClick
8+
import io.mockk.MockKAnnotations
9+
import io.mockk.mockk
10+
import io.mockk.verify
11+
import net.mullvad.mullvadvpn.compose.createEdgeToEdgeComposeExtension
12+
import net.mullvad.mullvadvpn.compose.setContentWithTheme
13+
import net.mullvad.mullvadvpn.compose.test.SERVER_IP_OVERRIDES_IMPORT_BY_FILE_TEST_TAG
14+
import net.mullvad.mullvadvpn.compose.test.SERVER_IP_OVERRIDES_IMPORT_BY_TEXT_TEST_TAG
15+
import net.mullvad.mullvadvpn.compose.test.SERVER_IP_OVERRIDE_IMPORT_TEST_TAG
16+
import net.mullvad.mullvadvpn.compose.test.SERVER_IP_OVERRIDE_INFO_TEST_TAG
17+
import net.mullvad.mullvadvpn.compose.test.SERVER_IP_OVERRIDE_MORE_VERT_TEST_TAG
18+
import net.mullvad.mullvadvpn.compose.test.SERVER_IP_OVERRIDE_RESET_OVERRIDES_TEST_TAG
19+
import net.mullvad.mullvadvpn.viewmodel.ServerIpOverridesViewState
20+
import org.junit.jupiter.api.BeforeEach
21+
import org.junit.jupiter.api.Test
22+
import org.junit.jupiter.api.extension.RegisterExtension
23+
24+
@ExperimentalTestApi
25+
class ServerIpOverridesScreenTest {
26+
@JvmField @RegisterExtension val composeExtension = createEdgeToEdgeComposeExtension()
27+
28+
@BeforeEach
29+
fun setup() {
30+
MockKAnnotations.init(this)
31+
}
32+
33+
@Suppress("TestFunctionName")
34+
@Composable
35+
private fun ScreenWithDefault(
36+
state: ServerIpOverridesViewState,
37+
onBackClick: () -> Unit = {},
38+
onInfoClick: () -> Unit = {},
39+
onResetOverridesClick: () -> Unit = {},
40+
onImportByFile: () -> Unit = {},
41+
onImportByText: () -> Unit = {},
42+
) {
43+
ServerIpOverridesScreen(
44+
state = state,
45+
onBackClick = onBackClick,
46+
onInfoClick = onInfoClick,
47+
onResetOverridesClick = onResetOverridesClick,
48+
onImportByFile = onImportByFile,
49+
onImportByText = onImportByText
50+
)
51+
}
52+
53+
@Test
54+
fun ensure_overrides_inactive_is_displayed() =
55+
composeExtension.use {
56+
// Arrange
57+
setContentWithTheme {
58+
ScreenWithDefault(state = ServerIpOverridesViewState.Loaded(false))
59+
}
60+
61+
// Assert
62+
onNodeWithText("Overrides inactive").assertExists()
63+
}
64+
65+
@Test
66+
fun ensure_overrides_active_is_displayed() =
67+
composeExtension.use {
68+
// Arrange
69+
setContentWithTheme {
70+
ScreenWithDefault(state = ServerIpOverridesViewState.Loaded(true))
71+
}
72+
73+
// Assert
74+
onNodeWithText("Overrides active").assertExists()
75+
}
76+
77+
@Test
78+
fun ensure_overrides_active_shows_warning_on_import() =
79+
composeExtension.use {
80+
// Arrange
81+
setContentWithTheme {
82+
ScreenWithDefault(state = ServerIpOverridesViewState.Loaded(true))
83+
}
84+
85+
// Act
86+
onNodeWithTag(testTag = SERVER_IP_OVERRIDE_IMPORT_TEST_TAG).performClick()
87+
88+
// Assert
89+
onNodeWithText(
90+
"Importing new overrides might replace some previously imported overrides."
91+
)
92+
.assertExists()
93+
}
94+
95+
@Test
96+
fun ensure_info_click_works() =
97+
composeExtension.use {
98+
// Arrange
99+
val clickHandler: () -> Unit = mockk(relaxed = true)
100+
setContentWithTheme {
101+
ScreenWithDefault(
102+
state = ServerIpOverridesViewState.Loaded(false),
103+
onInfoClick = clickHandler
104+
)
105+
}
106+
107+
// Act
108+
onNodeWithTag(SERVER_IP_OVERRIDE_INFO_TEST_TAG).performClick()
109+
110+
// Assert
111+
verify { clickHandler() }
112+
}
113+
114+
@Test
115+
fun ensure_reset_click_works() =
116+
composeExtension.use {
117+
// Arrange
118+
val clickHandler: () -> Unit = mockk(relaxed = true)
119+
setContentWithTheme {
120+
ScreenWithDefault(
121+
state = ServerIpOverridesViewState.Loaded(true),
122+
onResetOverridesClick = clickHandler
123+
)
124+
}
125+
126+
// Act
127+
onNodeWithTag(SERVER_IP_OVERRIDE_MORE_VERT_TEST_TAG).performClick()
128+
onNodeWithTag(SERVER_IP_OVERRIDE_RESET_OVERRIDES_TEST_TAG).performClick()
129+
130+
// Assert
131+
verify { clickHandler() }
132+
}
133+
134+
@Test
135+
fun ensure_import_by_file_works() =
136+
composeExtension.use {
137+
// Arrange
138+
val clickHandler: () -> Unit = mockk(relaxed = true)
139+
setContentWithTheme {
140+
ScreenWithDefault(
141+
state = ServerIpOverridesViewState.Loaded(false),
142+
onImportByFile = clickHandler
143+
)
144+
}
145+
146+
// Act
147+
onNodeWithTag(SERVER_IP_OVERRIDE_IMPORT_TEST_TAG).performClick()
148+
onNodeWithTag(SERVER_IP_OVERRIDES_IMPORT_BY_FILE_TEST_TAG).performClick()
149+
150+
// Assert
151+
verify { clickHandler() }
152+
}
153+
154+
@Test
155+
fun ensure_import_by_text() =
156+
composeExtension.use {
157+
// Arrange
158+
val clickHandler: () -> Unit = mockk(relaxed = true)
159+
setContentWithTheme {
160+
ScreenWithDefault(
161+
state = ServerIpOverridesViewState.Loaded(false),
162+
onImportByText = clickHandler
163+
)
164+
}
165+
166+
// Act
167+
onNodeWithTag(SERVER_IP_OVERRIDE_IMPORT_TEST_TAG).performClick()
168+
onNodeWithTag(SERVER_IP_OVERRIDES_IMPORT_BY_TEXT_TEST_TAG).performClick()
169+
170+
// Assert
171+
verify { clickHandler() }
172+
}
173+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package net.mullvad.mullvadvpn.compose.button
2+
3+
import androidx.compose.material3.Icon
4+
import androidx.compose.material3.IconButton
5+
import androidx.compose.material3.MaterialTheme
6+
import androidx.compose.runtime.Composable
7+
import androidx.compose.ui.Modifier
8+
import androidx.compose.ui.graphics.Color
9+
import androidx.compose.ui.res.painterResource
10+
import net.mullvad.mullvadvpn.R
11+
12+
@Composable
13+
fun InfoIconButton(
14+
onClick: () -> Unit,
15+
modifier: Modifier = Modifier,
16+
contentDescription: String? = null,
17+
iconTint: Color = MaterialTheme.colorScheme.onPrimary
18+
) {
19+
IconButton(modifier = modifier, onClick = onClick) {
20+
Icon(
21+
painter = painterResource(id = R.drawable.icon_info),
22+
contentDescription = contentDescription,
23+
tint = iconTint
24+
)
25+
}
26+
}

android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/IconCell.kt

+5-3
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,14 @@ private fun PreviewIconCell() {
2525
@Composable
2626
fun IconCell(
2727
iconId: Int?,
28-
contentDescription: String? = null,
2928
title: String,
29+
modifier: Modifier = Modifier,
30+
contentDescription: String? = null,
3031
titleStyle: TextStyle = MaterialTheme.typography.labelLarge,
3132
titleColor: Color = MaterialTheme.colorScheme.onPrimary,
3233
onClick: () -> Unit = {},
3334
background: Color = MaterialTheme.colorScheme.primary,
34-
enabled: Boolean = true,
35+
enabled: Boolean = true
3536
) {
3637
BaseCell(
3738
headlineContent = {
@@ -49,6 +50,7 @@ fun IconCell(
4950
},
5051
onCellClicked = onClick,
5152
background = background,
52-
isRowEnabled = enabled
53+
isRowEnabled = enabled,
54+
modifier = modifier
5355
)
5456
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package net.mullvad.mullvadvpn.compose.cell
2+
3+
import androidx.compose.foundation.background
4+
import androidx.compose.foundation.layout.Box
5+
import androidx.compose.foundation.layout.padding
6+
import androidx.compose.foundation.layout.size
7+
import androidx.compose.foundation.shape.CircleShape
8+
import androidx.compose.material3.MaterialTheme
9+
import androidx.compose.material3.Text
10+
import androidx.compose.runtime.Composable
11+
import androidx.compose.ui.Modifier
12+
import androidx.compose.ui.draw.alpha
13+
import androidx.compose.ui.graphics.Color
14+
import androidx.compose.ui.res.stringResource
15+
import androidx.compose.ui.tooling.preview.Preview
16+
import net.mullvad.mullvadvpn.R
17+
import net.mullvad.mullvadvpn.compose.component.MullvadCircularProgressIndicatorSmall
18+
import net.mullvad.mullvadvpn.lib.theme.AppTheme
19+
import net.mullvad.mullvadvpn.lib.theme.Dimens
20+
import net.mullvad.mullvadvpn.lib.theme.color.AlphaInactive
21+
import net.mullvad.mullvadvpn.lib.theme.color.AlphaVisible
22+
import net.mullvad.mullvadvpn.lib.theme.color.selected
23+
24+
@Preview
25+
@Composable
26+
private fun PreviewServerIpOverridesCell() {
27+
AppTheme { ServerIpOverridesCell(active = true) }
28+
}
29+
30+
@Composable
31+
fun ServerIpOverridesCell(
32+
active: Boolean?,
33+
modifier: Modifier = Modifier,
34+
activeColor: Color = MaterialTheme.colorScheme.selected,
35+
inactiveColor: Color = MaterialTheme.colorScheme.error,
36+
) {
37+
BaseCell(
38+
modifier = modifier,
39+
iconView = {
40+
if (active == null) {
41+
MullvadCircularProgressIndicatorSmall()
42+
} else {
43+
Box(
44+
modifier =
45+
Modifier.size(Dimens.relayCircleSize)
46+
.background(
47+
color =
48+
when {
49+
active -> activeColor
50+
else -> inactiveColor
51+
},
52+
shape = CircleShape
53+
)
54+
)
55+
}
56+
},
57+
headlineContent = {
58+
if (active != null) {
59+
Text(
60+
text =
61+
if (active) stringResource(id = R.string.server_ip_overrides_active)
62+
else stringResource(id = R.string.server_ip_overrides_inactive),
63+
color = MaterialTheme.colorScheme.onPrimary,
64+
modifier =
65+
Modifier.weight(1f)
66+
.alpha(
67+
if (active) {
68+
AlphaVisible
69+
} else {
70+
AlphaInactive
71+
}
72+
)
73+
.padding(
74+
horizontal = Dimens.smallPadding,
75+
vertical = Dimens.mediumPadding
76+
)
77+
)
78+
}
79+
},
80+
isRowEnabled = false
81+
)
82+
}

0 commit comments

Comments
 (0)