diff options
| author | saber safavi <saber.safavi@codic.se> | 2023-03-17 15:15:49 +0100 |
|---|---|---|
| committer | Albin <albin@mullvad.net> | 2023-03-20 16:21:29 +0100 |
| commit | d69c346accf92301e5c6f8db30854f2cd28113e3 (patch) | |
| tree | 721ae7ea730eebb962ad74c10a69c65251c249d8 /android | |
| parent | 6e15ab43e1620a5fa291322860127818b8de0cbe (diff) | |
| download | mullvadvpn-d69c346accf92301e5c6f8db30854f2cd28113e3.tar.xz mullvadvpn-d69c346accf92301e5c6f8db30854f2cd28113e3.zip | |
Add composified advanced settings view
Diffstat (limited to 'android')
10 files changed, 1014 insertions, 0 deletions
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/CustomDnsComposeCell.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/CustomDnsComposeCell.kt new file mode 100644 index 0000000000..d5fa79fe09 --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/CustomDnsComposeCell.kt @@ -0,0 +1,99 @@ +package net.mullvad.mullvadvpn.compose.cell + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.sp +import net.mullvad.mullvadvpn.R +import net.mullvad.mullvadvpn.compose.component.CellSwitch +import net.mullvad.mullvadvpn.compose.theme.MullvadWhite +import net.mullvad.mullvadvpn.compose.theme.MullvadWhite60 + +@Preview +@Composable +private fun PreviewDnsComposeCell() { + CustomDnsComposeCell( + checkboxDefaultState = true, + onToggle = {} + ) +} + +@Composable +fun CustomDnsComposeCell( + checkboxDefaultState: Boolean, + onToggle: (Boolean) -> Unit +) { + val titleModifier = Modifier + val bodyViewModifier = Modifier + val subtitleModifier = Modifier + + BaseCell( + title = { CustomDnsCellTitle(modifier = titleModifier) }, + bodyView = { + CustomDnsCellView( + switchTriggered = { + onToggle(it) + }, + isToggled = checkboxDefaultState, + modifier = bodyViewModifier + ) + }, + onCellClicked = { onToggle(!checkboxDefaultState) }, + subtitleModifier = subtitleModifier + ) +} + +@Composable +fun CustomDnsCellTitle( + modifier: Modifier +) { + val textSize = dimensionResource(id = R.dimen.text_medium_plus).value.sp + Text( + text = stringResource(R.string.enable_custom_dns), + textAlign = TextAlign.Center, + fontWeight = FontWeight.Bold, + fontSize = textSize, + color = MullvadWhite, + modifier = modifier + .wrapContentWidth(align = Alignment.End) + .wrapContentHeight() + ) +} + +@Composable +fun CustomDnsCellView( + switchTriggered: (Boolean) -> Unit, + isToggled: Boolean, + modifier: Modifier +) { + Row( + modifier = modifier + .wrapContentWidth() + .wrapContentHeight() + ) { + CellSwitch( + isChecked = isToggled, + onCheckedChange = null + ) + } +} + +@Composable +fun CustomDnsCellSubtitle(modifier: Modifier) { + val textSize = dimensionResource(id = R.dimen.text_small).value.sp + Text( + text = stringResource(R.string.custom_dns_footer), + fontSize = textSize, + color = MullvadWhite60, + modifier = modifier + ) +} diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/DnsCell.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/DnsCell.kt new file mode 100644 index 0000000000..238ecb8d8e --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/DnsCell.kt @@ -0,0 +1,82 @@ +package net.mullvad.mullvadvpn.compose.cell + +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.material.Icon +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import net.mullvad.mullvadvpn.R +import net.mullvad.mullvadvpn.compose.theme.MullvadHelmetYellow + +@Preview +@Composable +private fun PreviewDnsCell() { + DnsCell( + address = "0.0.0.0", + isUnreachableLocalDnsWarningVisible = true, + onClick = {} + ) +} + +@Composable +fun DnsCell( + address: String, + isUnreachableLocalDnsWarningVisible: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + val titleModifier = Modifier + val startPadding = 54.dp + + BaseCell( + title = { + DnsTitle( + address = address, + modifier = titleModifier + ) + }, + bodyView = { + if (isUnreachableLocalDnsWarningVisible) { + Icon( + painter = painterResource(id = R.drawable.icon_alert), + contentDescription = stringResource(id = R.string.confirm_local_dns), + tint = MullvadHelmetYellow + ) + } + }, + onCellClicked = { onClick.invoke() }, + background = colorResource(id = R.color.blue20), + startPadding = startPadding, + modifier = modifier + ) +} + +@Composable +private fun DnsTitle( + address: String, + modifier: Modifier = Modifier +) { + val textSize = dimensionResource(id = R.dimen.text_medium).value.sp + Text( + text = address, + color = Color.White, + fontSize = textSize, + fontStyle = FontStyle.Normal, + textAlign = TextAlign.Start, + modifier = modifier + .wrapContentWidth(align = Alignment.End) + .wrapContentHeight() + ) +} diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/MtuComposeCell.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/MtuComposeCell.kt new file mode 100644 index 0000000000..5be7f04d3b --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/MtuComposeCell.kt @@ -0,0 +1,98 @@ +package net.mullvad.mullvadvpn.compose.cell + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentWidth as wrapContentWidth1 +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.sp +import net.mullvad.mullvadvpn.R +import net.mullvad.mullvadvpn.compose.theme.MullvadWhite60 +import net.mullvad.mullvadvpn.constant.MTU_MAX_VALUE +import net.mullvad.mullvadvpn.constant.MTU_MIN_VALUE + +@Preview +@Composable +fun MtuComposeCellPreview() { + MtuComposeCell( + mtuValue = "1300", + onEditMtu = {} + ) +} + +@Composable +fun MtuComposeCell( + mtuValue: String, + onEditMtu: () -> Unit, +) { + val titleModifier = Modifier + val subtitleModifier = Modifier + + BaseCell( + title = { MtuTitle(modifier = titleModifier) }, + bodyView = { + MtuBodyView( + mtuValue = mtuValue, + modifier = titleModifier + ) + }, + subtitle = { MtuSubtitle(subtitleModifier) }, + subtitleModifier = subtitleModifier, + onCellClicked = { + onEditMtu.invoke() + } + ) +} + +@Composable +private fun MtuTitle( + modifier: Modifier +) { + val textSize = dimensionResource(id = R.dimen.text_medium_plus).value.sp + Text( + text = stringResource(R.string.wireguard_mtu), + textAlign = TextAlign.Center, + fontWeight = FontWeight.Bold, + fontSize = textSize, + color = Color.White, + modifier = modifier + .wrapContentWidth1(align = Alignment.End) + .wrapContentHeight() + ) +} + +@Composable +private fun MtuBodyView( + mtuValue: String, + modifier: Modifier +) { + Row( + modifier = modifier + .wrapContentWidth1() + .wrapContentHeight() + ) { + Text( + text = mtuValue.ifEmpty { stringResource(id = R.string.hint_default) }, + color = Color.White + ) + } +} + +@Composable +private fun MtuSubtitle(modifier: Modifier) { + val textSize = dimensionResource(id = R.dimen.text_small).value.sp + Text( + text = stringResource(R.string.wireguard_mtu_footer, MTU_MIN_VALUE, MTU_MAX_VALUE), + fontSize = textSize, + color = MullvadWhite60, + modifier = modifier + ) +} diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/NavigationComposeCell.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/NavigationComposeCell.kt new file mode 100644 index 0000000000..b4e05ebbd3 --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/NavigationComposeCell.kt @@ -0,0 +1,71 @@ +package net.mullvad.mullvadvpn.compose.cell + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.sp +import net.mullvad.mullvadvpn.R + +@Preview +@Composable +private fun PreviewNavigationCell() { + NavigationComposeCell( + title = "Navigation sample", + onClick = {} + ) +} + +@Composable +fun NavigationComposeCell( + title: String, + modifier: Modifier = Modifier, + bodyView: @Composable () -> Unit = { + DefaultNavigationView(chevronContentDescription = title) + }, + onClick: () -> Unit +) { + BaseCell( + onCellClicked = onClick, + title = { NavigationTitleView(title = title, modifier = modifier) }, + bodyView = { + bodyView() + }, + subtitle = null, + ) +} + +@Composable +private fun NavigationTitleView( + title: String, + modifier: Modifier = Modifier +) { + val textMediumSize = dimensionResource(id = R.dimen.text_medium_plus).value.sp + Text( + text = title, + textAlign = TextAlign.Center, + fontWeight = FontWeight.Bold, + fontSize = textMediumSize, + color = Color.White, + modifier = modifier + .wrapContentWidth(align = Alignment.End) + .wrapContentHeight() + ) +} + +@Composable +private fun DefaultNavigationView(chevronContentDescription: String) { + Image( + painter = painterResource(id = R.drawable.icon_chevron), + contentDescription = chevronContentDescription + ) +} diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DnsDialog.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DnsDialog.kt new file mode 100644 index 0000000000..b70aa2fddc --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DnsDialog.kt @@ -0,0 +1,187 @@ +package net.mullvad.mullvadvpn.compose.dialog + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import net.mullvad.mullvadvpn.R +import net.mullvad.mullvadvpn.compose.textfield.DnsTextField +import net.mullvad.mullvadvpn.compose.theme.MullvadBlue +import net.mullvad.mullvadvpn.compose.theme.MullvadDarkBlue +import net.mullvad.mullvadvpn.compose.theme.MullvadRed +import net.mullvad.mullvadvpn.compose.theme.MullvadWhite +import net.mullvad.mullvadvpn.compose.theme.MullvadWhite20 +import net.mullvad.mullvadvpn.compose.theme.MullvadWhite60 +import net.mullvad.mullvadvpn.viewmodel.StagedDns + +@OptIn(ExperimentalComposeUiApi::class) +@Composable +fun DnsDialog( + stagedDns: StagedDns, + isAllowLanEnabled: Boolean, + onIpAddressChanged: (String) -> Unit, + onAttemptToSave: () -> Unit, + onRemove: () -> Unit, + onDismiss: () -> Unit +) { + val buttonSize = dimensionResource(id = R.dimen.button_height) + val mediumPadding = dimensionResource(id = R.dimen.medium_padding) + val textMediumSize = dimensionResource(id = R.dimen.text_medium_plus).value.sp + val textFieldFocusRequester = FocusRequester() + + val textSmallSize = dimensionResource(id = R.dimen.text_small).value.sp + val textBigSize = dimensionResource(id = R.dimen.text_big).value.sp + val dialogPadding = 20.dp + val midPadding = 10.dp + val smallPadding = 5.dp + + Dialog( + // Fix for https://issuetracker.google.com/issues/221643630 + properties = DialogProperties(usePlatformDefaultWidth = false), + onDismissRequest = { + onDismiss() + }, + content = { + Column( + Modifier + // Related to the fix for https://issuetracker.google.com/issues/221643630 + .fillMaxWidth(0.8f) + .background(color = MullvadDarkBlue) + .padding(dialogPadding) + ) { + Text( + text = if (stagedDns is StagedDns.NewDns) { + stringResource(R.string.add_dns_server_dialog_title) + } else { + stringResource(R.string.update_dns_server_dialog_title) + }, + color = Color.White, + fontSize = textBigSize + ) + + Box( + Modifier + .wrapContentSize() + .clickable { textFieldFocusRequester.requestFocus() } + ) { + DnsTextField( + value = stagedDns.item.address, + isValidValue = stagedDns.isValid(), + onValueChanged = { newMtuValue -> + onIpAddressChanged(newMtuValue) + }, + onFocusChanges = {}, + onSubmit = { onAttemptToSave() }, + isEnabled = true, + placeholderText = stringResource(R.string.enter_value_placeholder), + modifier = Modifier + .padding(top = midPadding) + .focusRequester(textFieldFocusRequester) + ) + } + + val errorMessage = when { + stagedDns.validationResult is StagedDns.ValidationResult.DuplicateAddress -> { + stringResource(R.string.duplicate_address_warning) + } + stagedDns.item.isLocal && isAllowLanEnabled.not() -> { + stringResource(id = R.string.confirm_local_dns) + } + else -> { + null + } + } + + if (errorMessage != null) { + Text( + text = errorMessage, + fontSize = textSmallSize, + color = MullvadRed, + modifier = Modifier.padding(top = smallPadding) + ) + } + + Button( + modifier = Modifier + .padding(top = mediumPadding) + .height(buttonSize) + .defaultMinSize(minHeight = buttonSize) + .fillMaxWidth(), + colors = ButtonDefaults.buttonColors( + backgroundColor = MullvadBlue, + contentColor = MullvadWhite, + disabledContentColor = MullvadWhite60, + disabledBackgroundColor = MullvadWhite20 + ), + onClick = { onAttemptToSave() }, + enabled = stagedDns.isValid() + ) { + Text( + text = stringResource(id = R.string.submit_button), + fontSize = textMediumSize + ) + } + + if (stagedDns is StagedDns.EditDns) { + Button( + modifier = Modifier + .padding(top = mediumPadding) + .height(buttonSize) + .defaultMinSize(minHeight = buttonSize) + .fillMaxWidth(), + colors = ButtonDefaults.buttonColors( + backgroundColor = MullvadBlue, + contentColor = MullvadWhite + ), + onClick = { onRemove() } + ) { + Text( + text = stringResource(id = R.string.remove_button), + fontSize = textMediumSize + ) + } + } + + Button( + modifier = Modifier + .padding(top = mediumPadding) + .height(buttonSize) + .defaultMinSize(minHeight = buttonSize) + .fillMaxWidth(), + colors = ButtonDefaults.buttonColors( + backgroundColor = MullvadBlue, + contentColor = Color.White + ), + onClick = { + onDismiss() + } + ) { + Text( + text = stringResource(id = R.string.cancel), + fontSize = textMediumSize + ) + } + } + } + ) +} diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/MtuDialog.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/MtuDialog.kt new file mode 100644 index 0000000000..e4df8af467 --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/MtuDialog.kt @@ -0,0 +1,175 @@ +package net.mullvad.mullvadvpn.compose.dialog + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import net.mullvad.mullvadvpn.R +import net.mullvad.mullvadvpn.compose.textfield.MtuTextField +import net.mullvad.mullvadvpn.compose.theme.MullvadBlue +import net.mullvad.mullvadvpn.compose.theme.MullvadDarkBlue +import net.mullvad.mullvadvpn.compose.theme.MullvadWhite +import net.mullvad.mullvadvpn.compose.theme.MullvadWhite20 +import net.mullvad.mullvadvpn.compose.theme.MullvadWhite60 +import net.mullvad.mullvadvpn.constant.MTU_MAX_VALUE +import net.mullvad.mullvadvpn.constant.MTU_MIN_VALUE +import net.mullvad.mullvadvpn.util.isValidMtu + +@Composable +fun MtuDialog( + mtuValue: String, + onMtuValueChanged: (String) -> Unit, + onSave: () -> Unit, + onRestoreDefaultValue: () -> Unit, + onDismiss: () -> Unit, +) { + val buttonSize = dimensionResource(id = R.dimen.button_height) + val mediumPadding = dimensionResource(id = R.dimen.medium_padding) + val textMediumSize = dimensionResource(id = R.dimen.text_medium_plus).value.sp + val isValidMtu = mtuValue.toIntOrNull()?.isValidMtu() == true + val textFieldFocusRequester = FocusRequester() + + val textSmallSize = dimensionResource(id = R.dimen.text_small).value.sp + val dialogPadding = 10.dp + val smallPadding = 5.dp + + Dialog( + onDismissRequest = { + onDismiss() + }, + content = { + Column( + Modifier + .background(color = MullvadDarkBlue) + .padding(dialogPadding) + ) { + Text( + text = stringResource(id = R.string.wireguard_mtu), + color = Color.White, + fontSize = textMediumSize + ) + + Box( + Modifier + .wrapContentSize() + .clickable { textFieldFocusRequester.requestFocus() } + .padding(top = dialogPadding) + ) { + MtuTextField( + value = mtuValue, + onValueChanged = { newMtuValue -> + onMtuValueChanged(newMtuValue) + }, + onFocusChange = {}, + onSubmit = { newMtuValue -> + if (newMtuValue.toIntOrNull()?.isValidMtu() == true) { + onSave() + } + }, + isEnabled = true, + placeholderText = stringResource(R.string.enter_value_placeholder), + maxCharLength = 4, + isValidValue = isValidMtu, + modifier = Modifier + .focusRequester(textFieldFocusRequester) + ) + } + + Text( + text = stringResource( + id = R.string.wireguard_mtu_footer, + MTU_MIN_VALUE, + MTU_MAX_VALUE + ), + fontSize = textSmallSize, + color = MullvadWhite60, + modifier = Modifier.padding(top = smallPadding) + ) + + Button( + modifier = Modifier + .padding(top = mediumPadding) + .height(buttonSize) + .fillMaxWidth(), + colors = ButtonDefaults.buttonColors( + backgroundColor = MullvadBlue, + contentColor = MullvadWhite, + disabledContentColor = MullvadWhite60, + disabledBackgroundColor = MullvadWhite20 + ), + enabled = isValidMtu, + onClick = { + onSave() + } + ) { + Text( + text = stringResource(R.string.submit_button), + fontSize = textMediumSize + ) + } + + Button( + modifier = Modifier + .padding(top = mediumPadding) + .height(buttonSize) + .defaultMinSize( + minHeight = buttonSize + ) + .fillMaxWidth(), + colors = ButtonDefaults.buttonColors( + backgroundColor = MullvadBlue, + contentColor = MullvadWhite + ), + onClick = { + onRestoreDefaultValue() + } + ) { + Text( + text = stringResource(R.string.reset_to_default_button), + fontSize = textMediumSize + ) + } + + Button( + modifier = Modifier + .padding(top = mediumPadding) + .height(buttonSize) + .defaultMinSize( + minHeight = buttonSize + ) + .fillMaxWidth(), + colors = ButtonDefaults.buttonColors( + backgroundColor = MullvadBlue, + contentColor = Color.White + ), + onClick = { + onDismiss() + } + ) { + Text( + text = stringResource(R.string.cancel), + fontSize = textMediumSize + ) + } + } + } + ) +} diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AdvancedSettingScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AdvancedSettingScreen.kt new file mode 100644 index 0000000000..6d67718f1a --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AdvancedSettingScreen.kt @@ -0,0 +1,234 @@ +package net.mullvad.mullvadvpn.compose.screen + +import androidx.compose.animation.animateContentSize +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.Divider +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import me.onebone.toolbar.ScrollStrategy +import me.onebone.toolbar.rememberCollapsingToolbarScaffoldState +import net.mullvad.mullvadvpn.R +import net.mullvad.mullvadvpn.compose.cell.BaseCell +import net.mullvad.mullvadvpn.compose.cell.CustomDnsCellSubtitle +import net.mullvad.mullvadvpn.compose.cell.CustomDnsComposeCell +import net.mullvad.mullvadvpn.compose.cell.DnsCell +import net.mullvad.mullvadvpn.compose.cell.MtuComposeCell +import net.mullvad.mullvadvpn.compose.cell.NavigationComposeCell +import net.mullvad.mullvadvpn.compose.component.CollapsableAwareToolbarScaffold +import net.mullvad.mullvadvpn.compose.component.CollapsingTopBar +import net.mullvad.mullvadvpn.compose.component.drawVerticalScrollbar +import net.mullvad.mullvadvpn.compose.dialog.DnsDialog +import net.mullvad.mullvadvpn.compose.dialog.MtuDialog +import net.mullvad.mullvadvpn.compose.state.AdvancedSettingsUiState +import net.mullvad.mullvadvpn.compose.theme.CollapsingToolbarTheme +import net.mullvad.mullvadvpn.compose.theme.MullvadBlue20 +import net.mullvad.mullvadvpn.compose.theme.MullvadDarkBlue +import net.mullvad.mullvadvpn.viewmodel.CustomDnsItem + +@OptIn(ExperimentalMaterialApi::class) +@Preview +@Composable +private fun PreviewAdvancedSettings() { + AdvancedSettingScreen( + uiState = AdvancedSettingsUiState.DefaultUiState( + mtu = "1337", + isCustomDnsEnabled = true, + customDnsItems = listOf( + CustomDnsItem("0.0.0.0", false) + ) + ), + onMtuCellClick = {}, + onMtuInputChange = {}, + onSaveMtuClick = {}, + onRestoreMtuClick = {}, + onCancelMtuDialogClicked = {}, + onSplitTunnelingNavigationClick = {}, + onToggleDnsClick = {}, + onDnsClick = {}, + onDnsInputChange = {}, + onSaveDnsClick = {}, + onRemoveDnsClick = {}, + onCancelDnsDialogClick = {}, + onBackClick = {}, + ) +} + +@OptIn(ExperimentalFoundationApi::class) +@ExperimentalMaterialApi +@Composable +fun AdvancedSettingScreen( + uiState: AdvancedSettingsUiState, + onMtuCellClick: () -> Unit, + onMtuInputChange: (String) -> Unit, + onSaveMtuClick: () -> Unit, + onRestoreMtuClick: () -> Unit, + onCancelMtuDialogClicked: () -> Unit, + onSplitTunnelingNavigationClick: () -> Unit, + onToggleDnsClick: (Boolean) -> Unit, + onDnsClick: (index: Int?) -> Unit, + onDnsInputChange: (String) -> Unit, + onSaveDnsClick: () -> Unit, + onRemoveDnsClick: () -> Unit, + onCancelDnsDialogClick: () -> Unit, + onBackClick: () -> Unit +) { + val cellVerticalSpacing = dimensionResource(id = R.dimen.cell_label_vertical_padding) + val cellHorizontalSpacing = dimensionResource(id = R.dimen.cell_left_padding) + + when (uiState) { + is AdvancedSettingsUiState.MtuDialogUiState -> { + MtuDialog( + mtuValue = uiState.mtuEditValue, + onMtuValueChanged = { onMtuInputChange(it) }, + onSave = { onSaveMtuClick() }, + onRestoreDefaultValue = { onRestoreMtuClick() }, + onDismiss = { onCancelMtuDialogClicked() } + ) + } + is AdvancedSettingsUiState.DnsDialogUiState -> { + DnsDialog( + stagedDns = uiState.stagedDns, + isAllowLanEnabled = uiState.isAllowLanEnabled, + onIpAddressChanged = { onDnsInputChange(it) }, + onAttemptToSave = { onSaveDnsClick() }, + onRemove = { onRemoveDnsClick() }, + onDismiss = { onCancelDnsDialogClick() }, + ) + } + else -> { + // NOOP + } + } + + val lazyListState = rememberLazyListState() + val biggerPadding = 54.dp + val topPadding = 6.dp + + CollapsingToolbarTheme { + + val state = rememberCollapsingToolbarScaffoldState() + val progress = state.toolbarState.progress + + CollapsableAwareToolbarScaffold( + modifier = Modifier + .background(MullvadDarkBlue) + .fillMaxSize(), + state = state, + scrollStrategy = ScrollStrategy.ExitUntilCollapsed, + isEnabledWhenCollapsable = true, + toolbar = { + val scaffoldModifier = Modifier + .road( + whenCollapsed = Alignment.TopCenter, + whenExpanded = Alignment.BottomStart + ) + CollapsingTopBar( + backgroundColor = MullvadDarkBlue, + onBackClicked = { + onBackClick() + }, + title = stringResource(id = R.string.settings_advanced), + progress = progress, + modifier = scaffoldModifier, + backTitle = stringResource(id = R.string.settings), + ) + } + ) { + LazyColumn( + modifier = Modifier + .drawVerticalScrollbar(lazyListState) + .fillMaxWidth() + .wrapContentHeight() + .animateContentSize(), + state = lazyListState + + ) { + item { + MtuComposeCell( + mtuValue = uiState.mtu, + onEditMtu = { onMtuCellClick() } + ) + } + + item { + NavigationComposeCell( + title = stringResource(id = R.string.split_tunneling), + onClick = { + onSplitTunnelingNavigationClick.invoke() + } + ) + Divider() + } + + item { + CustomDnsComposeCell( + checkboxDefaultState = uiState.isCustomDnsEnabled, + onToggle = { newValue -> + onToggleDnsClick(newValue) + } + ) + Divider() + } + + if (uiState.isCustomDnsEnabled) { + itemsIndexed(uiState.customDnsItems) { index, item -> + DnsCell( + address = item.address, + isUnreachableLocalDnsWarningVisible = item.isLocal && + uiState.isAllowLanEnabled.not(), + onClick = { onDnsClick(index) }, + modifier = Modifier.animateItemPlacement(), + ) + Divider() + } + + item { + BaseCell( + onCellClicked = { onDnsClick(null) }, + title = { + Text( + text = stringResource(id = R.string.add_a_server), + color = Color.White + ) + }, + bodyView = { }, + subtitle = null, + background = MullvadBlue20, + startPadding = biggerPadding + ) + Divider() + } + } + + item { + CustomDnsCellSubtitle( + Modifier + .background(MullvadDarkBlue) + .padding( + start = cellHorizontalSpacing, + top = topPadding, + end = cellHorizontalSpacing, + bottom = cellVerticalSpacing + ) + ) + } + } + } + } +} diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/textfield/DnsTextField.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/textfield/DnsTextField.kt new file mode 100644 index 0000000000..198bb59159 --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/textfield/DnsTextField.kt @@ -0,0 +1,31 @@ +package net.mullvad.mullvadvpn.compose.textfield + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign + +@Composable +fun DnsTextField( + value: String, + isValidValue: Boolean, + modifier: Modifier = Modifier, + onValueChanged: (String) -> Unit = { }, + onFocusChanges: (Boolean) -> Unit = { }, + onSubmit: (String) -> Unit = { }, + placeholderText: String = "", + isEnabled: Boolean = true +) { + CustomTextField( + value = value, + modifier = modifier, + onValueChanged = onValueChanged, + onFocusChange = onFocusChanges, + onSubmit = onSubmit, + isEnabled = isEnabled, + placeholderText = placeholderText, + maxCharLength = Int.MAX_VALUE, + isValidValue = isValidValue, + isDigitsOnlyAllowed = false, + textAlign = TextAlign.Start + ) +} diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/textfield/MtuTextField.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/textfield/MtuTextField.kt new file mode 100644 index 0000000000..c44c16911c --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/textfield/MtuTextField.kt @@ -0,0 +1,30 @@ +package net.mullvad.mullvadvpn.compose.textfield + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +fun MtuTextField( + value: String, + isValidValue: Boolean, + modifier: Modifier = Modifier, + onValueChanged: (String) -> Unit = { }, + onFocusChange: (Boolean) -> Unit = { }, + onSubmit: (String) -> Unit = { }, + isEnabled: Boolean = true, + placeholderText: String = "", + maxCharLength: Int +) { + CustomTextField( + value = value, + modifier = modifier, + onValueChanged = onValueChanged, + onFocusChange = onFocusChange, + onSubmit = onSubmit, + isEnabled = isEnabled, + placeholderText = placeholderText, + maxCharLength = maxCharLength, + isValidValue = isValidValue, + isDigitsOnlyAllowed = true + ) +} diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 2e4c5f0275..f655e99901 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -203,4 +203,11 @@ then the app queries your system for a list of all installed applications. This list is only retrieved in the split tunneling view. The list of installed applications is never sent from the device.</string> + <string name="submit_button">Submit</string> + <string name="remove_button">Remove</string> + <string name="enter_value_placeholder">Enter…</string> + <string name="reset_to_default_button">Reset to default</string> + <string name="add_dns_server_dialog_title">Add DNS server</string> + <string name="update_dns_server_dialog_title">Update DNS server</string> + <string name="duplicate_address_warning">This address has already been entered.</string> </resources> |
