Skip to content

Commit 54879de

Browse files
committed
Add Server IP Overrides feature
1 parent 113d3af commit 54879de

File tree

57 files changed

+1441
-16
lines changed

Some content is hidden

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

57 files changed

+1441
-16
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,20 @@
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.res.painterResource
9+
import net.mullvad.mullvadvpn.R
10+
11+
@Composable
12+
fun InfoIconButton(onClick: () -> Unit, modifier: Modifier = Modifier) {
13+
IconButton(modifier = modifier, onClick = onClick) {
14+
Icon(
15+
painter = painterResource(id = R.drawable.icon_info),
16+
contentDescription = null,
17+
tint = MaterialTheme.colorScheme.onPrimary
18+
)
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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.IntrinsicSize
6+
import androidx.compose.foundation.layout.Row
7+
import androidx.compose.foundation.layout.fillMaxWidth
8+
import androidx.compose.foundation.layout.height
9+
import androidx.compose.foundation.layout.padding
10+
import androidx.compose.foundation.layout.size
11+
import androidx.compose.foundation.layout.wrapContentHeight
12+
import androidx.compose.foundation.shape.CircleShape
13+
import androidx.compose.material3.MaterialTheme
14+
import androidx.compose.material3.Text
15+
import androidx.compose.runtime.Composable
16+
import androidx.compose.ui.Alignment
17+
import androidx.compose.ui.Modifier
18+
import androidx.compose.ui.draw.alpha
19+
import androidx.compose.ui.graphics.Color
20+
import androidx.compose.ui.res.stringResource
21+
import androidx.compose.ui.tooling.preview.Preview
22+
import net.mullvad.mullvadvpn.R
23+
import net.mullvad.mullvadvpn.lib.theme.AppTheme
24+
import net.mullvad.mullvadvpn.lib.theme.Dimens
25+
import net.mullvad.mullvadvpn.lib.theme.color.AlphaInactive
26+
import net.mullvad.mullvadvpn.lib.theme.color.AlphaVisible
27+
import net.mullvad.mullvadvpn.lib.theme.color.selected
28+
29+
@Preview
30+
@Composable
31+
private fun PreviewServerIpOverridesCell() {
32+
AppTheme { ServerIpOverridesCell(active = true) }
33+
}
34+
35+
@Composable
36+
fun ServerIpOverridesCell(
37+
active: Boolean,
38+
modifier: Modifier = Modifier,
39+
activeColor: Color = MaterialTheme.colorScheme.selected,
40+
inactiveColor: Color = MaterialTheme.colorScheme.error,
41+
) {
42+
Row(
43+
modifier =
44+
modifier
45+
.wrapContentHeight()
46+
.height(IntrinsicSize.Min)
47+
.background(MaterialTheme.colorScheme.primary)
48+
.padding(horizontal = Dimens.sideMargin)
49+
.fillMaxWidth(),
50+
verticalAlignment = Alignment.CenterVertically
51+
) {
52+
Box(
53+
modifier =
54+
Modifier.size(Dimens.relayCircleSize)
55+
.background(
56+
color =
57+
when {
58+
active -> activeColor
59+
else -> inactiveColor
60+
},
61+
shape = CircleShape
62+
)
63+
)
64+
Text(
65+
text =
66+
if (active) stringResource(id = R.string.server_ip_overrides_active)
67+
else stringResource(id = R.string.server_ip_overrides_inactive),
68+
color = MaterialTheme.colorScheme.onPrimary,
69+
modifier =
70+
Modifier.weight(1f)
71+
.alpha(
72+
if (active) {
73+
AlphaVisible
74+
} else {
75+
AlphaInactive
76+
}
77+
)
78+
.padding(horizontal = Dimens.smallPadding, vertical = Dimens.mediumPadding)
79+
)
80+
}
81+
}

android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/MullvadModalBottomSheet.kt

+3-3
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ private fun PreviewMullvadModalBottomSheet() {
3737
title = "Select",
3838
)
3939
},
40-
closeBottomSheet = {}
40+
onDismissRequest = {}
4141
)
4242
}
4343
}
@@ -49,13 +49,13 @@ fun MullvadModalBottomSheet(
4949
sheetState: SheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true),
5050
backgroundColor: Color = MaterialTheme.colorScheme.surfaceContainer,
5151
onBackgroundColor: Color = MaterialTheme.colorScheme.onSurface,
52-
closeBottomSheet: () -> Unit,
52+
onDismissRequest: () -> Unit,
5353
sheetContent: @Composable ColumnScope.() -> Unit
5454
) {
5555
// This is to avoid weird colors in the status bar and the navigation bar
5656
val paddingValues = BottomSheetDefaults.windowInsets.asPaddingValues()
5757
ModalBottomSheet(
58-
onDismissRequest = closeBottomSheet,
58+
onDismissRequest = onDismissRequest,
5959
sheetState = sheetState,
6060
containerColor = backgroundColor,
6161
modifier = modifier,

android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/Scaffolding.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,9 @@ fun ScaffoldWithTopBarAndDeviceName(
107107
}
108108

109109
@Composable
110-
fun MullvadSnackbar(snackbarData: SnackbarData) {
110+
fun MullvadSnackbar(modifier: Modifier = Modifier, snackbarData: SnackbarData) {
111111
Snackbar(
112+
modifier = modifier,
112113
snackbarData = snackbarData,
113114
containerColor = MaterialTheme.colorScheme.surfaceContainer,
114115
contentColor = MaterialTheme.colorScheme.onSurface,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package net.mullvad.mullvadvpn.compose.dialog
2+
3+
import androidx.compose.foundation.layout.Arrangement
4+
import androidx.compose.foundation.layout.Column
5+
import androidx.compose.foundation.layout.fillMaxWidth
6+
import androidx.compose.material3.AlertDialog
7+
import androidx.compose.material3.MaterialTheme
8+
import androidx.compose.material3.Text
9+
import androidx.compose.runtime.Composable
10+
import androidx.compose.ui.Modifier
11+
import androidx.compose.ui.res.stringResource
12+
import androidx.compose.ui.tooling.preview.Preview
13+
import com.ramcosta.composedestinations.annotation.Destination
14+
import com.ramcosta.composedestinations.result.EmptyResultBackNavigator
15+
import com.ramcosta.composedestinations.result.ResultBackNavigator
16+
import com.ramcosta.composedestinations.spec.DestinationStyle
17+
import net.mullvad.mullvadvpn.R
18+
import net.mullvad.mullvadvpn.compose.button.NegativeButton
19+
import net.mullvad.mullvadvpn.compose.button.PrimaryButton
20+
import net.mullvad.mullvadvpn.lib.theme.AppTheme
21+
import net.mullvad.mullvadvpn.lib.theme.Dimens
22+
23+
@Preview
24+
@Composable
25+
private fun PreviewResetServerIpOverridesConfirmationDialog() {
26+
AppTheme { ResetServerIpOverridesConfirmationDialog(EmptyResultBackNavigator()) }
27+
}
28+
29+
@Destination(style = DestinationStyle.Dialog::class)
30+
@Composable
31+
fun ResetServerIpOverridesConfirmationDialog(
32+
resultNavigator: ResultBackNavigator<Boolean>,
33+
) {
34+
AlertDialog(
35+
containerColor = MaterialTheme.colorScheme.background,
36+
confirmButton = {
37+
Column(verticalArrangement = Arrangement.spacedBy(Dimens.buttonSpacing)) {
38+
NegativeButton(
39+
modifier = Modifier.fillMaxWidth(),
40+
text = stringResource(id = R.string.server_ip_overrides_reset_reset_button),
41+
onClick = { resultNavigator.navigateBack(result = true) }
42+
)
43+
44+
PrimaryButton(
45+
modifier = Modifier.fillMaxWidth(),
46+
text = stringResource(R.string.cancel),
47+
onClick = resultNavigator::navigateBack
48+
)
49+
}
50+
},
51+
title = {
52+
Text(
53+
text = stringResource(id = R.string.server_ip_overrides_reset_title),
54+
color = MaterialTheme.colorScheme.onPrimary
55+
)
56+
},
57+
text = {
58+
Text(
59+
text = stringResource(id = R.string.server_ip_overrides_reset_body),
60+
color = MaterialTheme.colorScheme.onPrimary,
61+
style = MaterialTheme.typography.bodySmall,
62+
)
63+
},
64+
onDismissRequest = resultNavigator::navigateBack
65+
)
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package net.mullvad.mullvadvpn.compose.dialog
2+
3+
import androidx.compose.runtime.Composable
4+
import androidx.compose.ui.res.stringResource
5+
import androidx.compose.ui.tooling.preview.Preview
6+
import com.ramcosta.composedestinations.annotation.Destination
7+
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
8+
import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
9+
import com.ramcosta.composedestinations.spec.DestinationStyle
10+
import net.mullvad.mullvadvpn.R
11+
12+
@Preview
13+
@Composable
14+
private fun PreviewServerIpOverridesInfoDialog() {
15+
ServerIpOverridesInfoDialog(EmptyDestinationsNavigator)
16+
}
17+
18+
@Destination(style = DestinationStyle.Dialog::class)
19+
@Composable
20+
fun ServerIpOverridesInfoDialog(navigator: DestinationsNavigator) {
21+
InfoDialog(
22+
message =
23+
buildString {
24+
appendLine(stringResource(id = R.string.server_ip_overrides_info_first_paragraph))
25+
appendLine()
26+
appendLine(stringResource(id = R.string.server_ip_overrides_info_second_paragraph))
27+
appendLine()
28+
append(stringResource(id = R.string.server_ip_overrides_info_third_paragraph))
29+
},
30+
onDismiss = navigator::navigateUp
31+
)
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package net.mullvad.mullvadvpn.compose.screen
2+
3+
import androidx.compose.foundation.layout.Column
4+
import androidx.compose.foundation.layout.fillMaxSize
5+
import androidx.compose.foundation.layout.padding
6+
import androidx.compose.material.icons.Icons
7+
import androidx.compose.material.icons.filled.Close
8+
import androidx.compose.material3.ButtonDefaults
9+
import androidx.compose.material3.Icon
10+
import androidx.compose.material3.IconButton
11+
import androidx.compose.material3.MaterialTheme
12+
import androidx.compose.material3.Scaffold
13+
import androidx.compose.material3.Text
14+
import androidx.compose.material3.TextButton
15+
import androidx.compose.material3.TextField
16+
import androidx.compose.runtime.Composable
17+
import androidx.compose.runtime.getValue
18+
import androidx.compose.runtime.mutableStateOf
19+
import androidx.compose.runtime.remember
20+
import androidx.compose.runtime.setValue
21+
import androidx.compose.ui.Modifier
22+
import androidx.compose.ui.res.stringResource
23+
import androidx.compose.ui.tooling.preview.Preview
24+
import com.ramcosta.composedestinations.annotation.Destination
25+
import com.ramcosta.composedestinations.result.ResultBackNavigator
26+
import net.mullvad.mullvadvpn.R
27+
import net.mullvad.mullvadvpn.compose.component.MullvadSmallTopBar
28+
import net.mullvad.mullvadvpn.compose.textfield.mullvadWhiteTextFieldColors
29+
import net.mullvad.mullvadvpn.compose.transitions.DefaultTransition
30+
31+
@Preview
32+
@Composable
33+
private fun PreviewImportOverridesByText() {
34+
ImportOverridesByTextScreen({}, {})
35+
}
36+
37+
@Destination(style = DefaultTransition::class)
38+
@Composable
39+
fun ImportOverridesByText(
40+
resultNavigator: ResultBackNavigator<String>,
41+
) {
42+
ImportOverridesByTextScreen(
43+
onNavigateBack = resultNavigator::navigateBack,
44+
onImportClicked = { resultNavigator.navigateBack(result = it) }
45+
)
46+
}
47+
48+
@Composable
49+
fun ImportOverridesByTextScreen(
50+
onNavigateBack: () -> Unit,
51+
onImportClicked: (String) -> Unit,
52+
) {
53+
var text by remember { mutableStateOf("") }
54+
55+
Scaffold(
56+
topBar = {
57+
MullvadSmallTopBar(
58+
title = stringResource(R.string.import_overrides_text_title),
59+
navigationIcon = {
60+
IconButton(onClick = onNavigateBack) {
61+
Icon(imageVector = Icons.Default.Close, contentDescription = null)
62+
}
63+
},
64+
actions = {
65+
TextButton(
66+
enabled = text.isNotEmpty(),
67+
colors =
68+
ButtonDefaults.textButtonColors()
69+
.copy(contentColor = MaterialTheme.colorScheme.onPrimary),
70+
onClick = { onImportClicked(text) }
71+
) {
72+
Text(
73+
text = stringResource(R.string.import_overrides_import),
74+
)
75+
}
76+
}
77+
)
78+
},
79+
) {
80+
Column(modifier = Modifier.padding(it)) {
81+
TextField(
82+
modifier = Modifier.fillMaxSize(),
83+
value = text,
84+
onValueChange = { text = it },
85+
placeholder = {
86+
Text(text = stringResource(R.string.import_override_textfield_placeholder))
87+
},
88+
colors = mullvadWhiteTextFieldColors()
89+
)
90+
}
91+
}
92+
}

android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SelectLocationScreen.kt

+3-3
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,7 @@ private fun CustomListsBottomSheet(
545545
) {
546546
MullvadModalBottomSheet(
547547
sheetState = sheetState,
548-
closeBottomSheet = { closeBottomSheet(false) },
548+
onDismissRequest = { closeBottomSheet(false) },
549549
modifier = Modifier.testTag(SELECT_LOCATION_CUSTOM_LIST_BOTTOM_SHEET_TEST_TAG)
550550
) { ->
551551
HeaderCell(
@@ -598,7 +598,7 @@ private fun LocationBottomSheet(
598598
) {
599599
MullvadModalBottomSheet(
600600
sheetState = sheetState,
601-
closeBottomSheet = { closeBottomSheet(false) },
601+
onDismissRequest = { closeBottomSheet(false) },
602602
modifier = Modifier.testTag(SELECT_LOCATION_LOCATION_BOTTOM_SHEET_TEST_TAG)
603603
) { ->
604604
HeaderCell(
@@ -656,7 +656,7 @@ private fun EditCustomListBottomSheet(
656656
) {
657657
MullvadModalBottomSheet(
658658
sheetState = sheetState,
659-
closeBottomSheet = { closeBottomSheet(false) }
659+
onDismissRequest = { closeBottomSheet(false) }
660660
) {
661661
HeaderCell(text = customList.name, background = Color.Unspecified)
662662
IconCell(

0 commit comments

Comments
 (0)