diff options
| author | Jonatan Rhodin <jonatan.rhodin@mullvad.net> | 2023-07-05 16:55:10 +0200 |
|---|---|---|
| committer | Jonatan Rhodin <jonatan.rhodin@mullvad.net> | 2023-07-14 11:11:18 +0200 |
| commit | bc0b9d5a717c664711a0c4bc1029171afe10cccf (patch) | |
| tree | 4f12a5d5f0e042daf443b542f4b9b48d9f575293 /android/app/src | |
| parent | 50a01d641fe081fafb648132e56476e9a320053e (diff) | |
| download | mullvadvpn-bc0b9d5a717c664711a0c4bc1029171afe10cccf.tar.xz mullvadvpn-bc0b9d5a717c664711a0c4bc1029171afe10cccf.zip | |
Migrate connect fragment, expect notification bar, to compose
Diffstat (limited to 'android/app/src')
36 files changed, 733 insertions, 758 deletions
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/Button.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/button/ActionButton.kt index ef5e283c29..8b8a6cd319 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/Button.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/button/ActionButton.kt @@ -1,6 +1,7 @@ -package net.mullvad.mullvadvpn.compose.component +package net.mullvad.mullvadvpn.compose.button import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -18,11 +19,19 @@ import net.mullvad.mullvadvpn.compose.theme.Dimens @Composable fun ActionButton( - text: String, onClick: () -> Unit, colors: ButtonColors, modifier: Modifier = Modifier, - isEnabled: Boolean = true + text: String = "", + isEnabled: Boolean = true, + content: @Composable RowScope.() -> Unit = { + Text( + text = text, + textAlign = TextAlign.Center, + fontSize = 18.sp, + fontWeight = FontWeight.Bold + ) + } ) { Button( onClick = onClick, @@ -37,11 +46,6 @@ fun ActionButton( colors = colors, shape = MaterialTheme.shapes.small ) { - Text( - text = text, - textAlign = TextAlign.Center, - fontSize = 18.sp, - fontWeight = FontWeight.Bold - ) + content() } } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/button/ConnectionButton.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/button/ConnectionButton.kt new file mode 100644 index 0000000000..7722ef0533 --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/button/ConnectionButton.kt @@ -0,0 +1,171 @@ +package net.mullvad.mullvadvpn.compose.button + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CornerSize +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.FilledIconButton +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButtonDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import net.mullvad.mullvadvpn.R +import net.mullvad.mullvadvpn.compose.component.textResource +import net.mullvad.mullvadvpn.compose.theme.AlphaInactive +import net.mullvad.mullvadvpn.compose.theme.AppTheme +import net.mullvad.mullvadvpn.compose.theme.Dimens +import net.mullvad.mullvadvpn.model.TunnelState +import net.mullvad.talpid.tunnel.ActionAfterDisconnect + +@Composable +fun ConnectionButton( + modifier: Modifier = Modifier, + state: TunnelState, + disconnectClick: () -> Unit, + reconnectClick: () -> Unit, + cancelClick: () -> Unit, + connectClick: () -> Unit +) { + when (state) { + is TunnelState.Disconnected -> ConnectButton(modifier = modifier, onClick = connectClick) + is TunnelState.Disconnecting -> { + when (state.actionAfterDisconnect) { + ActionAfterDisconnect.Nothing -> + ConnectButton(modifier = modifier, onClick = connectClick) + ActionAfterDisconnect.Block -> + DisconnectButton( + modifier = modifier, + text = stringResource(id = R.string.disconnect), + mainClick = connectClick, + reconnectClick = reconnectClick + ) + ActionAfterDisconnect.Reconnect -> + DisconnectButton( + modifier = modifier, + text = stringResource(id = R.string.disconnect), + mainClick = connectClick, + reconnectClick = reconnectClick + ) + } + } + is TunnelState.Connecting -> + DisconnectButton( + modifier = modifier, + text = stringResource(id = R.string.cancel), + mainClick = cancelClick, + reconnectClick = reconnectClick + ) + is TunnelState.Connected -> + DisconnectButton( + modifier = modifier, + text = stringResource(id = R.string.disconnect), + mainClick = disconnectClick, + reconnectClick = reconnectClick + ) + is TunnelState.Error -> { + if (state.errorState.isBlocking) { + DisconnectButton( + modifier = modifier, + text = stringResource(id = R.string.disconnect), + mainClick = disconnectClick, + reconnectClick = reconnectClick + ) + } else { + DisconnectButton( + modifier = modifier, + text = stringResource(id = R.string.dismiss), + mainClick = cancelClick, + reconnectClick = reconnectClick + ) + } + } + } +} + +@Preview +@Composable +private fun PreviewConnectButton() { + AppTheme { ConnectButton(onClick = {}) } +} + +@Composable +private fun ConnectButton(modifier: Modifier = Modifier, onClick: () -> Unit) { + ActionButton( + text = textResource(id = R.string.connect), + modifier = modifier, + onClick = onClick, + colors = + ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.surface, + contentColor = MaterialTheme.colorScheme.onSurface + ) + ) +} + +@Preview +@Composable +fun PreviewDisconnectButton() { + AppTheme { DisconnectButton(text = "Disconnect", mainClick = {}, reconnectClick = {}) } +} + +@Composable +private fun DisconnectButton( + text: String, + modifier: Modifier = Modifier, + height: Dp = Dimens.connectButtonHeight, + mainClick: () -> Unit, + reconnectClick: () -> Unit +) { + Row(modifier = modifier.height(height)) { + Button( + onClick = mainClick, + shape = + MaterialTheme.shapes.small.copy( + topEnd = CornerSize(percent = 0), + bottomEnd = CornerSize(percent = 0) + ), + colors = + ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.error.copy(alpha = AlphaInactive), + contentColor = MaterialTheme.colorScheme.onError + ), + modifier = Modifier.weight(1f).height(height) + ) { + Text( + text = text, + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold + ) + } + + Spacer(modifier = Modifier.width(Dimens.listItemDivider)) + + FilledIconButton( + shape = + MaterialTheme.shapes.small.copy( + topStart = CornerSize(percent = 0), + bottomStart = CornerSize(percent = 0) + ), + colors = + IconButtonDefaults.filledIconButtonColors( + containerColor = MaterialTheme.colorScheme.error.copy(alpha = AlphaInactive), + contentColor = MaterialTheme.colorScheme.onError + ), + onClick = reconnectClick, + modifier = Modifier.height(height).aspectRatio(1f, true) + ) { + Icon(painter = painterResource(id = R.drawable.icon_reload), contentDescription = null) + } + } +} diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/button/SwitchLocationButton.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/button/SwitchLocationButton.kt new file mode 100644 index 0000000000..2d6e149af8 --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/button/SwitchLocationButton.kt @@ -0,0 +1,66 @@ +package net.mullvad.mullvadvpn.compose.button + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import net.mullvad.mullvadvpn.R +import net.mullvad.mullvadvpn.compose.component.SpacedColumn +import net.mullvad.mullvadvpn.compose.theme.Alpha20 +import net.mullvad.mullvadvpn.compose.theme.AppTheme +import net.mullvad.mullvadvpn.compose.theme.Dimens + +@Preview +@Composable +fun PreviewSwitchLocationButton() { + AppTheme { + SpacedColumn { + SwitchLocationButton(onClick = {}, text = "Switch Location", showChevron = false) + SwitchLocationButton(onClick = {}, text = "Switch Location", showChevron = true) + } + } +} + +@Composable +fun SwitchLocationButton( + modifier: Modifier = Modifier, + text: String, + showChevron: Boolean, + onClick: () -> Unit, +) { + ActionButton( + onClick = onClick, + colors = + ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.inverseSurface.copy(alpha = Alpha20), + contentColor = MaterialTheme.colorScheme.inverseSurface + ), + modifier = modifier + ) { + Box(modifier = Modifier.fillMaxWidth().fillMaxHeight().padding(all = Dimens.smallPadding)) { + Text( + text = text, + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold, + modifier = Modifier.align(Alignment.Center) + ) + if (showChevron) { + Icon( + painter = painterResource(id = R.drawable.icon_chevron), + contentDescription = null, + modifier = Modifier.align(Alignment.CenterEnd) + ) + } + } + } +} diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/Chevron.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/Chevron.kt index 1e34b13896..c1c01dc6c1 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/Chevron.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/Chevron.kt @@ -9,11 +9,16 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate +import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.res.painterResource import net.mullvad.mullvadvpn.R @Composable -fun ChevronView(modifier: Modifier = Modifier, isExpanded: Boolean) { +fun ChevronView( + modifier: Modifier = Modifier, + colorFilter: ColorFilter? = null, + isExpanded: Boolean +) { val resourceId = R.drawable.icon_chevron val rotation = remember { Animatable(90f + if (isExpanded) 180f else 0f) } @@ -27,6 +32,7 @@ fun ChevronView(modifier: Modifier = Modifier, isExpanded: Boolean) { Image( painterResource(id = resourceId), contentDescription = null, + colorFilter = colorFilter, modifier = modifier.rotate(rotation.value), ) } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/ConnectionStatusText.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/ConnectionStatusText.kt new file mode 100644 index 0000000000..4cc1d82fcd --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/ConnectionStatusText.kt @@ -0,0 +1,94 @@ +package net.mullvad.mullvadvpn.compose.component + +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.Preview +import net.mullvad.mullvadvpn.R +import net.mullvad.mullvadvpn.compose.theme.AppTheme +import net.mullvad.mullvadvpn.compose.theme.typeface.connectionStatus +import net.mullvad.mullvadvpn.model.TunnelState +import net.mullvad.talpid.tunnel.ActionAfterDisconnect +import net.mullvad.talpid.tunnel.ErrorState +import net.mullvad.talpid.tunnel.ErrorStateCause + +@Preview +@Composable +fun PreviewConnectionStatusText() { + AppTheme { + SpacedColumn { + ConnectionStatusText(TunnelState.Disconnected) + ConnectionStatusText(TunnelState.Connecting(null, null)) + ConnectionStatusText( + state = TunnelState.Error(ErrorState(ErrorStateCause.Ipv6Unavailable, true)) + ) + } + } +} + +@Composable +fun ConnectionStatusText(state: TunnelState) { + when (state) { + is TunnelState.Disconnecting -> { + when (state.actionAfterDisconnect) { + ActionAfterDisconnect.Nothing -> DisconnectedText() + ActionAfterDisconnect.Block -> ConnectedText(false) + ActionAfterDisconnect.Reconnect -> ConnectingText(false) + } + } + is TunnelState.Disconnected -> DisconnectedText() + is TunnelState.Connecting -> ConnectingText(state.endpoint?.quantumResistant == true) + is TunnelState.Connected -> ConnectedText(state.endpoint.quantumResistant) + is TunnelState.Error -> ErrorText(state.errorState.isBlocking) + } +} + +@Composable +private fun DisconnectedText() { + Text( + text = textResource(id = R.string.unsecured_connection), + color = MaterialTheme.colorScheme.error, + style = MaterialTheme.typography.connectionStatus + ) +} + +@Composable +private fun ConnectingText(isQuantumResistant: Boolean) { + Text( + text = + textResource( + id = + if (isQuantumResistant) R.string.quantum_creating_secure_connection + else R.string.creating_secure_connection + ), + color = MaterialTheme.colorScheme.onPrimary, + style = MaterialTheme.typography.connectionStatus + ) +} + +@Composable +private fun ConnectedText(isQuantumResistant: Boolean) { + Text( + text = + textResource( + id = + if (isQuantumResistant) R.string.quantum_secure_connection + else R.string.secure_connection + ), + color = MaterialTheme.colorScheme.surface, + style = MaterialTheme.typography.connectionStatus + ) +} + +@Composable +private fun ErrorText(isBlocking: Boolean) { + Text( + text = + textResource( + id = if (isBlocking) R.string.blocked_connection else R.string.error_state + ), + color = + if (isBlocking) MaterialTheme.colorScheme.surface else MaterialTheme.colorScheme.error, + style = MaterialTheme.typography.connectionStatus + ) +} diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/LocationInfo.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/LocationInfo.kt new file mode 100644 index 0000000000..9980908353 --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/LocationInfo.kt @@ -0,0 +1,119 @@ +package net.mullvad.mullvadvpn.compose.component + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +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.graphics.ColorFilter +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import net.mullvad.mullvadvpn.R +import net.mullvad.mullvadvpn.compose.theme.AlphaInactive +import net.mullvad.mullvadvpn.compose.theme.AlphaInvisible +import net.mullvad.mullvadvpn.compose.theme.AlphaVisible +import net.mullvad.mullvadvpn.compose.theme.AppTheme +import net.mullvad.mullvadvpn.compose.theme.Dimens +import net.mullvad.mullvadvpn.model.GeoIpLocation +import net.mullvad.talpid.net.TransportProtocol + +@Preview +@Composable +fun PreviewLocationInfo() { + AppTheme { + LocationInfo( + onToggleTunnelInfo = {}, + isVisible = true, + isExpanded = true, + location = null, + inAddress = null, + outAddress = "" + ) + } +} + +@Composable +fun LocationInfo( + modifier: Modifier = Modifier, + colorExpanded: Color = MaterialTheme.colorScheme.onPrimary, + colorCollapsed: Color = MaterialTheme.colorScheme.onPrimary.copy(alpha = AlphaInactive), + onToggleTunnelInfo: () -> Unit, + isVisible: Boolean, + isExpanded: Boolean, + location: GeoIpLocation?, + inAddress: Triple<String, Int, TransportProtocol>?, + outAddress: String +) { + Column( + modifier = + if (isVisible) { + Modifier.clickable { onToggleTunnelInfo() }.alpha(AlphaVisible) + } else { + Modifier.alpha(AlphaInvisible) + } + .then(modifier) + ) { + Row { + Text( + text = location?.hostname ?: "", + color = + if (isExpanded) { + colorExpanded + } else { + colorCollapsed + }, + style = MaterialTheme.typography.labelLarge.copy(fontWeight = FontWeight.SemiBold) + ) + ChevronView( + isExpanded = isExpanded, + colorFilter = + ColorFilter.tint( + if (isExpanded) { + colorExpanded + } else { + colorCollapsed + } + ), + modifier = Modifier.padding(horizontal = Dimens.chevronMargin) + ) + } + Text( + text = + if (isExpanded) { + stringResource(id = R.string.wireguard) + } else { + "" + }, + color = colorExpanded, + style = MaterialTheme.typography.labelMedium + ) + val textInAddress = + inAddress?.let { + val protocol = + when (inAddress.third) { + TransportProtocol.Tcp -> stringResource(id = R.string.tcp) + TransportProtocol.Udp -> stringResource(id = R.string.udp) + } + "${inAddress.first}:${inAddress.second} $protocol" + } + ?: "" + Text( + text = "${stringResource(id = R.string.in_address)} $textInAddress", + color = colorExpanded, + style = MaterialTheme.typography.labelMedium, + modifier = Modifier.alpha(if (isExpanded) AlphaVisible else AlphaInvisible) + ) + Text( + text = "${stringResource(id = R.string.out_address)} $outAddress", + color = colorExpanded, + style = MaterialTheme.typography.labelMedium, + modifier = Modifier.alpha(if (isExpanded) AlphaVisible else AlphaInvisible) + ) + } +} diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/CustomPortDialog.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/CustomPortDialog.kt index c53b91dfae..57d2073672 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/CustomPortDialog.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/CustomPortDialog.kt @@ -15,7 +15,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import net.mullvad.mullvadvpn.R -import net.mullvad.mullvadvpn.compose.component.ActionButton +import net.mullvad.mullvadvpn.compose.button.ActionButton import net.mullvad.mullvadvpn.compose.textfield.CustomPortTextField import net.mullvad.mullvadvpn.compose.theme.AlphaDescription import net.mullvad.mullvadvpn.compose.theme.AlphaDisabled diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt new file mode 100644 index 0000000000..cfc7dab08f --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt @@ -0,0 +1,120 @@ +package net.mullvad.mullvadvpn.compose.screen + +import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.scrollable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import net.mullvad.mullvadvpn.R +import net.mullvad.mullvadvpn.compose.button.ConnectionButton +import net.mullvad.mullvadvpn.compose.button.SwitchLocationButton +import net.mullvad.mullvadvpn.compose.component.ConnectionStatusText +import net.mullvad.mullvadvpn.compose.component.LocationInfo +import net.mullvad.mullvadvpn.compose.state.ConnectUiState +import net.mullvad.mullvadvpn.compose.theme.AppTheme +import net.mullvad.mullvadvpn.compose.theme.Dimens +import net.mullvad.mullvadvpn.model.TunnelState +import net.mullvad.talpid.tunnel.ActionAfterDisconnect + +@Preview +@Composable +fun PreviewConnectScreen() { + val state = ConnectUiState.INITIAL + AppTheme { ConnectScreen(state) } +} + +@Composable +fun ConnectScreen( + uiState: ConnectUiState, + onDisconnectClick: () -> Unit = {}, + onReconnectClick: () -> Unit = {}, + onConnectClick: () -> Unit = {}, + onCancelClick: () -> Unit = {}, + onSwitchLocationClick: () -> Unit = {}, + onToggleTunnelInfo: () -> Unit = {} +) { + val scrollState = rememberScrollState() + Column( + verticalArrangement = Arrangement.Bottom, + horizontalAlignment = Alignment.Start, + modifier = + Modifier.background(color = MaterialTheme.colorScheme.primary) + .fillMaxHeight() + .padding(horizontal = Dimens.sideMargin, vertical = Dimens.screenVerticalMargin) + .scrollable(scrollState, Orientation.Vertical) + ) { + if ( + uiState.tunnelRealState is TunnelState.Connecting || + (uiState.tunnelRealState is TunnelState.Disconnecting && + uiState.tunnelRealState.actionAfterDisconnect == + ActionAfterDisconnect.Reconnect) + ) { + CircularProgressIndicator( + color = MaterialTheme.colorScheme.onPrimary, + modifier = + Modifier.size( + width = Dimens.progressIndicatorSize, + height = Dimens.progressIndicatorSize + ) + .align(Alignment.CenterHorizontally) + ) + } + Spacer(modifier = Modifier.height(Dimens.smallPadding)) + ConnectionStatusText(state = uiState.tunnelRealState) + Text( + text = uiState.location?.country ?: "", + style = MaterialTheme.typography.headlineLarge, + color = MaterialTheme.colorScheme.onPrimary + ) + Text( + text = uiState.location?.city ?: "", + style = MaterialTheme.typography.headlineLarge, + color = MaterialTheme.colorScheme.onPrimary + ) + LocationInfo( + onToggleTunnelInfo = onToggleTunnelInfo, + isVisible = uiState.tunnelRealState != TunnelState.Disconnected, + isExpanded = uiState.isTunnelInfoExpanded, + location = uiState.location, + inAddress = uiState.inAddress, + outAddress = uiState.outAddress, + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(Dimens.buttonSeparation)) + SwitchLocationButton( + modifier = Modifier.fillMaxWidth().height(Dimens.selectLocationButtonHeight), + onClick = onSwitchLocationClick, + showChevron = uiState.showLocation, + text = + if (uiState.showLocation) { + uiState.relayLocation?.locationName ?: "" + } else { + stringResource(id = R.string.switch_location) + } + ) + Spacer(modifier = Modifier.height(Dimens.buttonSeparation)) + ConnectionButton( + state = uiState.tunnelUiState, + modifier = Modifier.fillMaxWidth().height(Dimens.connectButtonHeight), + disconnectClick = onDisconnectClick, + reconnectClick = onReconnectClick, + cancelClick = onCancelClick, + connectClick = onConnectClick, + ) + } +} diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/DeviceListScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/DeviceListScreen.kt index adf641e965..27824c5b28 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/DeviceListScreen.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/DeviceListScreen.kt @@ -26,7 +26,7 @@ import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension import net.mullvad.mullvadvpn.R -import net.mullvad.mullvadvpn.compose.component.ActionButton +import net.mullvad.mullvadvpn.compose.button.ActionButton import net.mullvad.mullvadvpn.compose.component.ListItem import net.mullvad.mullvadvpn.compose.component.ScaffoldWithTopBar import net.mullvad.mullvadvpn.compose.dialog.ShowDeviceRemovalDialog diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/DeviceRevokedScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/DeviceRevokedScreen.kt index 07a2a759b2..4b4107a586 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/DeviceRevokedScreen.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/DeviceRevokedScreen.kt @@ -23,7 +23,7 @@ import androidx.compose.ui.unit.sp import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension import net.mullvad.mullvadvpn.R -import net.mullvad.mullvadvpn.compose.component.ActionButton +import net.mullvad.mullvadvpn.compose.button.ActionButton import net.mullvad.mullvadvpn.compose.component.ScaffoldWithTopBar import net.mullvad.mullvadvpn.compose.state.DeviceRevokedUiState import net.mullvad.mullvadvpn.compose.theme.AppTheme diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/PrivacyDisclaimerScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/PrivacyDisclaimerScreen.kt index 29146fe634..832179f95c 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/PrivacyDisclaimerScreen.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/PrivacyDisclaimerScreen.kt @@ -30,7 +30,7 @@ import androidx.compose.ui.unit.sp import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension import net.mullvad.mullvadvpn.R -import net.mullvad.mullvadvpn.compose.component.ActionButton +import net.mullvad.mullvadvpn.compose.button.ActionButton import net.mullvad.mullvadvpn.compose.component.ScaffoldWithTopBar import net.mullvad.mullvadvpn.compose.theme.AppTheme diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/ConnectUiState.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/ConnectUiState.kt index a69f96a891..417f589b6e 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/ConnectUiState.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/ConnectUiState.kt @@ -4,6 +4,7 @@ import net.mullvad.mullvadvpn.model.GeoIpLocation import net.mullvad.mullvadvpn.model.TunnelState import net.mullvad.mullvadvpn.relaylist.RelayItem import net.mullvad.mullvadvpn.ui.VersionInfo +import net.mullvad.talpid.net.TransportProtocol data class ConnectUiState( val location: GeoIpLocation?, @@ -11,6 +12,9 @@ data class ConnectUiState( val versionInfo: VersionInfo?, val tunnelUiState: TunnelState, val tunnelRealState: TunnelState, + val inAddress: Triple<String, Int, TransportProtocol>?, + val outAddress: String, + val showLocation: Boolean, val isTunnelInfoExpanded: Boolean ) { companion object { @@ -21,6 +25,9 @@ data class ConnectUiState( versionInfo = null, tunnelUiState = TunnelState.Disconnected, tunnelRealState = TunnelState.Disconnected, + inAddress = null, + outAddress = "", + showLocation = false, isTunnelInfoExpanded = false ) } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/theme/Color.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/theme/Color.kt index d089a467ca..49c64f75d4 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/theme/Color.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/theme/Color.kt @@ -23,6 +23,7 @@ val MullvadWhite80 = Color(0xCCFFFFFF) const val AlphaVisible = 1f const val AlphaDisabled = 0.2f +const val Alpha20 = 0.2f const val AlphaInactive = 0.4f const val AlphaDescription = 0.6f const val AlphaInvisible = 0f diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/theme/Theme.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/theme/Theme.kt index c2cc862b8d..a558d817ae 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/theme/Theme.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/theme/Theme.kt @@ -21,6 +21,7 @@ import net.mullvad.mullvadvpn.compose.theme.typeface.TypeScale // Add our own definitions here private val MullvadTypography = Typography( + headlineLarge = TextStyle(fontSize = TypeScale.TextHuge, fontWeight = FontWeight.Bold), headlineSmall = TextStyle( color = MullvadWhite, @@ -61,9 +62,12 @@ private val MullvadColorPalette = onSurfaceVariant = MullvadWhite, onPrimary = MullvadWhite, onSecondary = MullvadWhite60, + onError = MullvadWhite, + onSurface = MullvadWhite, inversePrimary = MullvadGreen, error = MullvadRed, - outlineVariant = Color.Transparent // Used by divider + outlineVariant = Color.Transparent, // Used by divider + inverseSurface = MullvadWhite ) val Shapes = diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/theme/dimensions/Dimensions.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/theme/dimensions/Dimensions.kt index 8c949531ff..9881bfe818 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/theme/dimensions/Dimensions.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/theme/dimensions/Dimensions.kt @@ -5,6 +5,7 @@ import androidx.compose.ui.unit.dp data class Dimensions( val buttonHeight: Dp = 44.dp, + val buttonSeparation: Dp = 18.dp, val cellEndPadding: Dp = 16.dp, val cellFooterTopPadding: Dp = 6.dp, val cellHeight: Dp = 52.dp, @@ -12,7 +13,9 @@ data class Dimensions( val cellStartPadding: Dp = 22.dp, val cellTopPadding: Dp = 6.dp, val cellVerticalSpacing: Dp = 14.dp, + val chevronMargin: Dp = 4.dp, val cityRowPadding: Dp = 34.dp, + val connectButtonHeight: Dp = 50.dp, val countryRowPadding: Dp = 18.dp, val customPortBoxMinWidth: Dp = 80.dp, val expandableCellChevronSize: Dp = 30.dp, @@ -29,9 +32,11 @@ data class Dimensions( val progressIndicatorSize: Dp = 60.dp, val relayCircleSize: Dp = 16.dp, val relayRowPadding: Dp = 50.dp, + val screenVerticalMargin: Dp = 22.dp, val searchFieldHeight: Dp = 42.dp, val searchFieldHorizontalPadding: Dp = 22.dp, val searchIconSize: Dp = 24.dp, + val selectLocationButtonHeight: Dp = 50.dp, val selectLocationTitlePadding: Dp = 12.dp, val selectableCellTextMargin: Dp = 12.dp, val sideMargin: Dp = 22.dp, diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/theme/typeface/TypeScale.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/theme/typeface/TypeScale.kt index 53116ad7e8..41396152c1 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/theme/typeface/TypeScale.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/theme/typeface/TypeScale.kt @@ -11,6 +11,7 @@ import androidx.compose.ui.unit.sp * * Order entries within each type by descending size. */ internal object TypeScale { + val TextHuge = 30.sp val TextBig = 24.sp val TextMediumPlus = 18.sp val TextMedium = 16.sp diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/theme/typeface/Typeface.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/theme/typeface/Typeface.kt index 71c72bd1af..aadf2601ac 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/theme/typeface/Typeface.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/theme/typeface/Typeface.kt @@ -26,3 +26,13 @@ val Typography.listItemSubText: TextStyle fontSize = TypeScale.TextSmall ) } + +val Typography.connectionStatus: TextStyle + @Composable + get() { + return TextStyle( + fontWeight = FontWeight.Bold, + letterSpacing = TextUnit.Unspecified, + fontSize = TypeScale.TextMedium + ) + } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/model/TunnelState.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/model/TunnelState.kt index c513c77d8e..c3d58d2ca7 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/model/TunnelState.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/model/TunnelState.kt @@ -7,7 +7,7 @@ import net.mullvad.talpid.tunnel.ActionAfterDisconnect import net.mullvad.talpid.tunnel.ErrorState import net.mullvad.talpid.tunnel.ErrorStateCause -sealed class TunnelState() : Parcelable { +sealed class TunnelState : Parcelable { @Parcelize object Disconnected : TunnelState(), Parcelable @Parcelize @@ -45,33 +45,33 @@ sealed class TunnelState() : Parcelable { fun fromString(description: String, endpoint: TunnelEndpoint?): TunnelState { return when (description) { - DISCONNECTED -> TunnelState.Disconnected - CONNECTING -> TunnelState.Connecting(endpoint, null) - CONNECTED -> TunnelState.Connected(endpoint!!, null) - RECONNECTING -> TunnelState.Disconnecting(ActionAfterDisconnect.Reconnect) - DISCONNECTING -> TunnelState.Disconnecting(ActionAfterDisconnect.Nothing) - BLOCKING -> TunnelState.Error(ErrorState(ErrorStateCause.StartTunnelError, true)) + DISCONNECTED -> Disconnected + CONNECTING -> Connecting(endpoint, null) + CONNECTED -> Connected(endpoint!!, null) + RECONNECTING -> Disconnecting(ActionAfterDisconnect.Reconnect) + DISCONNECTING -> Disconnecting(ActionAfterDisconnect.Nothing) + BLOCKING -> Error(ErrorState(ErrorStateCause.StartTunnelError, true)) ERROR -> { - TunnelState.Error(ErrorState(ErrorStateCause.SetFirewallPolicyError, false)) + Error(ErrorState(ErrorStateCause.SetFirewallPolicyError, false)) } - else -> TunnelState.Error(ErrorState(ErrorStateCause.SetFirewallPolicyError, false)) + else -> Error(ErrorState(ErrorStateCause.SetFirewallPolicyError, false)) } } } override fun toString(): String = when (this) { - is TunnelState.Disconnected -> DISCONNECTED - is TunnelState.Connecting -> CONNECTING - is TunnelState.Connected -> CONNECTED - is TunnelState.Disconnecting -> { + is Disconnected -> DISCONNECTED + is Connecting -> CONNECTING + is Connected -> CONNECTED + is Disconnecting -> { if (actionAfterDisconnect == ActionAfterDisconnect.Reconnect) { RECONNECTING } else { DISCONNECTING } } - is TunnelState.Error -> { + is Error -> { if (errorState.isBlocking) { BLOCKING } else { diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/ConnectActionButton.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/ConnectActionButton.kt deleted file mode 100644 index 664eb1bc49..0000000000 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/ConnectActionButton.kt +++ /dev/null @@ -1,134 +0,0 @@ -package net.mullvad.mullvadvpn.ui - -import android.view.View -import android.view.ViewGroup.MarginLayoutParams -import android.widget.Button -import android.widget.ImageButton -import net.mullvad.mullvadvpn.R -import net.mullvad.mullvadvpn.model.TunnelState -import net.mullvad.talpid.tunnel.ActionAfterDisconnect - -class ConnectActionButton(val parentView: View) { - private val mainButton: Button = parentView.findViewById(R.id.action_button) - private val reconnectButton: ImageButton = parentView.findViewById(R.id.reconnect_button) - - private val resources = parentView.context.resources - private val greenBackground = resources.getDrawable(R.drawable.green_button_background, null) - private val leftRedBackground = - resources.getDrawable(R.drawable.transparent_red_left_half_button_background, null) - - private var showReconnectButton = false - set(value) { - if (field != value) { - field = value - updateReconnectButton() - } - } - - private var reconnectButtonSpace = 0 - set(value) { - if (field != value) { - field = value - updateReconnectButton() - } - } - - var tunnelState: TunnelState = TunnelState.Disconnected - set(value) { - when (value) { - is TunnelState.Disconnected -> disconnected() - is TunnelState.Disconnecting -> { - when (value.actionAfterDisconnect) { - ActionAfterDisconnect.Nothing -> disconnected() - ActionAfterDisconnect.Block -> connected() - ActionAfterDisconnect.Reconnect -> connecting() - } - } - is TunnelState.Connecting -> connecting() - is TunnelState.Connected -> connected() - is TunnelState.Error -> { - if (value.errorState.isBlocking) { - connected() - } else { - blockError() - } - } - } - - field = value - } - - var onConnect: (() -> Unit)? = null - var onCancel: (() -> Unit)? = null - var onReconnect: (() -> Unit)? = null - var onDisconnect: (() -> Unit)? = null - - init { - mainButton.setOnClickListener { action() } - reconnectButton.setOnClickListener { onReconnect?.invoke() } - - reconnectButton.addOnLayoutChangeListener { _, left, _, right, _, _, _, _, _ -> - val width = right - left - val layoutParams = reconnectButton.layoutParams - val leftMargin = - when (layoutParams) { - is MarginLayoutParams -> layoutParams.leftMargin - else -> 0 - } - - reconnectButtonSpace = width + leftMargin - } - } - - private fun action() { - val state = tunnelState - - when (state) { - is TunnelState.Disconnected -> onConnect?.invoke() - is TunnelState.Disconnecting -> onConnect?.invoke() - is TunnelState.Connecting -> onCancel?.invoke() - is TunnelState.Connected -> onDisconnect?.invoke() - is TunnelState.Error -> { - if (state.errorState.isBlocking) { - onDisconnect?.invoke() - } else { - onCancel?.invoke() - } - } - } - } - - private fun disconnected() { - mainButton.background = greenBackground - mainButton.setText(R.string.connect) - showReconnectButton = false - } - - private fun connecting() { - redButton(R.string.cancel) - } - - private fun connected() { - redButton(R.string.disconnect) - } - - private fun blockError() { - redButton(R.string.dismiss) - } - - private fun redButton(text: Int) { - mainButton.background = leftRedBackground - mainButton.setText(text) - showReconnectButton = true - } - - private fun updateReconnectButton() { - if (showReconnectButton) { - reconnectButton.visibility = View.VISIBLE - mainButton.setPadding(reconnectButtonSpace, 0, 0, 0) - } else { - reconnectButton.visibility = View.GONE - mainButton.setPadding(0, 0, 0, 0) - } - } -} diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/ConnectionStatus.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/ConnectionStatus.kt deleted file mode 100644 index f0cf18f65c..0000000000 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/ConnectionStatus.kt +++ /dev/null @@ -1,78 +0,0 @@ -package net.mullvad.mullvadvpn.ui - -import android.content.Context -import android.view.View -import android.widget.TextView -import net.mullvad.mullvadvpn.R -import net.mullvad.mullvadvpn.model.TunnelState -import net.mullvad.talpid.tunnel.ActionAfterDisconnect - -class ConnectionStatus(parentView: View, context: Context) { - private val spinner: View = parentView.findViewById(R.id.connecting_spinner) - private val text: TextView = parentView.findViewById(R.id.connection_status) - - private val unsecuredTextColor = context.getColor(R.color.red) - private val connectingTextColor = context.getColor(R.color.white) - private val securedTextColor = context.getColor(R.color.green) - - fun setState(state: TunnelState) { - when (state) { - is TunnelState.Disconnecting -> { - when (state.actionAfterDisconnect) { - ActionAfterDisconnect.Nothing -> disconnected() - ActionAfterDisconnect.Block -> connected(false) - ActionAfterDisconnect.Reconnect -> connecting(false) - } - } - is TunnelState.Disconnected -> disconnected() - is TunnelState.Connecting -> connecting(state.endpoint?.quantumResistant == true) - is TunnelState.Connected -> connected(state.endpoint.quantumResistant) - is TunnelState.Error -> errorState(state.errorState.isBlocking) - } - } - - private fun disconnected() { - spinner.visibility = View.GONE - - text.setTextColor(unsecuredTextColor) - text.setText(R.string.unsecured_connection) - } - - private fun connecting(isQuantumResistant: Boolean) { - spinner.visibility = View.VISIBLE - - text.setTextColor(connectingTextColor) - text.setText( - if (isQuantumResistant) { - R.string.quantum_creating_secure_connection - } else { - R.string.creating_secure_connection - } - ) - } - - private fun connected(isQuantumResistant: Boolean) { - spinner.visibility = View.GONE - - text.setTextColor(securedTextColor) - text.setText( - if (isQuantumResistant) { - R.string.quantum_secure_connection - } else { - R.string.secure_connection - } - ) - } - - private fun errorState(isBlocking: Boolean) { - spinner.visibility = View.GONE - - if (isBlocking) { - text.setTextColor(securedTextColor) - text.setText(R.string.blocked_connection) - } else { - text.setTextColor(unsecuredTextColor) - text.setText(R.string.error_state) - } - } -} diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/LocationInfo.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/LocationInfo.kt deleted file mode 100644 index be432375cc..0000000000 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/LocationInfo.kt +++ /dev/null @@ -1,146 +0,0 @@ -package net.mullvad.mullvadvpn.ui - -import android.content.Context -import android.view.View -import android.widget.TextView -import net.mullvad.mullvadvpn.R -import net.mullvad.mullvadvpn.model.GeoIpLocation -import net.mullvad.mullvadvpn.model.TunnelState -import net.mullvad.talpid.net.TransportProtocol -import net.mullvad.talpid.net.TunnelEndpoint - -class LocationInfo( - parentView: View, - private val context: Context, - private val onToggleTunnelInfo: () -> Unit -) { - private val hostnameColorCollapsed = context.getColor(R.color.white40) - private val hostnameColorExpanded = context.getColor(R.color.white) - - private val country: TextView = parentView.findViewById(R.id.country) - private val city: TextView = parentView.findViewById(R.id.city) - private val tunnelInfo: View = parentView.findViewById(R.id.tunnel_info) - private val hostname: TextView = parentView.findViewById(R.id.hostname) - private val chevron: View = parentView.findViewById(R.id.chevron) - private val protocol: TextView = parentView.findViewById(R.id.tunnel_protocol) - private val inAddress: TextView = parentView.findViewById(R.id.in_address) - private val outAddress: TextView = parentView.findViewById(R.id.out_address) - - private var tunnelEndpoint: TunnelEndpoint? = null - private var isTunnelInfoVisible = false - - var isTunnelInfoExpanded = false - set(value) { - field = value - updateTunnelInfo() - } - - var location: GeoIpLocation? = null - set(value) { - field = value - - country.text = value?.country ?: "" - city.text = value?.city ?: "" - hostname.text = value?.hostname ?: "" - - updateOutAddress(value) - } - - var state: TunnelState = TunnelState.Disconnected - set(value) { - field = value - - when (value) { - is TunnelState.Connecting -> { - tunnelEndpoint = value.endpoint - isTunnelInfoVisible = true - } - is TunnelState.Connected -> { - tunnelEndpoint = value.endpoint - isTunnelInfoVisible = true - } - else -> { - tunnelEndpoint = null - isTunnelInfoVisible = false - } - } - - updateTunnelInfo() - } - - init { - tunnelInfo.setOnClickListener { onToggleTunnelInfo() } - } - - private fun updateTunnelInfo() { - if (isTunnelInfoVisible) { - showTunnelInfo() - } else { - hideTunnelInfo() - } - } - - private fun hideTunnelInfo() { - chevron.visibility = View.INVISIBLE - - protocol.text = "" - inAddress.text = "" - outAddress.text = "" - } - - private fun showTunnelInfo() { - chevron.visibility = View.VISIBLE - - if (isTunnelInfoExpanded) { - hostname.setTextColor(hostnameColorExpanded) - chevron.rotation = 180.0F - protocol.setText(R.string.wireguard) - showInAddress(tunnelEndpoint) - updateOutAddress(location) - } else { - hostname.setTextColor(hostnameColorCollapsed) - chevron.rotation = 0.0F - protocol.text = "" - inAddress.text = "" - outAddress.text = "" - } - } - - private fun showInAddress(tunnelEndpoint: TunnelEndpoint?) { - if (tunnelEndpoint != null) { - val relayEndpoint = tunnelEndpoint.obfuscation?.endpoint ?: tunnelEndpoint.endpoint - val host = relayEndpoint.address.address.hostAddress - val port = relayEndpoint.address.port - val protocol = - when (relayEndpoint.protocol) { - TransportProtocol.Tcp -> context.getString(R.string.tcp) - TransportProtocol.Udp -> context.getString(R.string.udp) - } - inAddress.text = context.getString(R.string.in_address) + " $host:$port $protocol" - } else { - inAddress.text = "" - } - } - - private fun updateOutAddress(location: GeoIpLocation?) { - val addressAvailable = location != null && (location.ipv4 != null || location.ipv6 != null) - - if (isTunnelInfoVisible && addressAvailable && isTunnelInfoExpanded) { - val ipv4 = location!!.ipv4 - val ipv6 = location.ipv6 - val ipAddress: String - - if (ipv6 == null) { - ipAddress = ipv4!!.hostAddress - } else if (ipv4 == null) { - ipAddress = ipv6.hostAddress - } else { - ipAddress = "${ipv4.hostAddress} / ${ipv6.hostAddress}" - } - - outAddress.text = context.getString(R.string.out_address) + " $ipAddress" - } else { - outAddress.text = "" - } - } -} diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/UnderNotificationBannerBehavior.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/UnderNotificationBannerBehavior.kt index 8abf20bb12..4f330cb6be 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/UnderNotificationBannerBehavior.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/UnderNotificationBannerBehavior.kt @@ -3,19 +3,19 @@ package net.mullvad.mullvadvpn.ui import android.content.Context import android.util.AttributeSet import android.view.View -import android.widget.ScrollView +import androidx.compose.ui.platform.ComposeView import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout.Behavior import net.mullvad.mullvadvpn.R class UnderNotificationBannerBehavior(context: Context, attributes: AttributeSet) : - Behavior<ScrollView>(context, attributes) { - override fun layoutDependsOn(parent: CoordinatorLayout, body: ScrollView, dependency: View) = + Behavior<ComposeView>(context, attributes) { + override fun layoutDependsOn(parent: CoordinatorLayout, body: ComposeView, dependency: View) = dependency.id == R.id.notification_banner override fun onDependentViewChanged( parent: CoordinatorLayout, - body: ScrollView, + body: ComposeView, dependency: View ): Boolean { val newPaddingTop = @@ -26,11 +26,11 @@ class UnderNotificationBannerBehavior(context: Context, attributes: AttributeSet } body.getChildAt(0).apply { - if (paddingTop != newPaddingTop) { + return if (paddingTop != newPaddingTop) { setPadding(paddingLeft, newPaddingTop, paddingRight, paddingBottom) - return true + true } else { - return false + false } } } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/ConnectFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/ConnectFragment.kt index 86cbd3b49b..725c70eeb6 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/ConnectFragment.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/ConnectFragment.kt @@ -6,6 +6,8 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.platform.ComposeView import androidx.core.content.ContextCompat import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope @@ -14,24 +16,20 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import net.mullvad.mullvadvpn.BuildConfig import net.mullvad.mullvadvpn.R +import net.mullvad.mullvadvpn.compose.screen.ConnectScreen +import net.mullvad.mullvadvpn.compose.theme.AppTheme import net.mullvad.mullvadvpn.constant.BuildTypes import net.mullvad.mullvadvpn.model.TunnelState import net.mullvad.mullvadvpn.repository.AccountRepository -import net.mullvad.mullvadvpn.ui.ConnectActionButton -import net.mullvad.mullvadvpn.ui.ConnectionStatus -import net.mullvad.mullvadvpn.ui.LocationInfo import net.mullvad.mullvadvpn.ui.NavigationBarPainter -import net.mullvad.mullvadvpn.ui.extension.requireMainActivity import net.mullvad.mullvadvpn.ui.notification.AccountExpiryNotification import net.mullvad.mullvadvpn.ui.notification.TunnelStateNotification import net.mullvad.mullvadvpn.ui.notification.VersionInfoNotification import net.mullvad.mullvadvpn.ui.paintNavigationBar import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager import net.mullvad.mullvadvpn.ui.serviceconnection.authTokenCache -import net.mullvad.mullvadvpn.ui.serviceconnection.connectionProxy import net.mullvad.mullvadvpn.ui.widget.HeaderBar import net.mullvad.mullvadvpn.ui.widget.NotificationBanner -import net.mullvad.mullvadvpn.ui.widget.SwitchLocationButton import net.mullvad.mullvadvpn.util.JobTracker import net.mullvad.mullvadvpn.viewmodel.ConnectViewModel import net.mullvad.talpid.tunnel.ErrorStateCause @@ -48,12 +46,8 @@ class ConnectFragment : BaseFragment(), NavigationBarPainter { private val tunnelStateNotification: TunnelStateNotification by inject() private val versionInfoNotification: VersionInfoNotification by inject() - private lateinit var actionButton: ConnectActionButton - private lateinit var switchLocationButton: SwitchLocationButton private lateinit var headerBar: HeaderBar private lateinit var notificationBanner: NotificationBanner - private lateinit var status: ConnectionStatus - private lateinit var locationInfo: LocationInfo @Deprecated("Refactor code to instead rely on Lifecycle.") private val jobTracker = JobTracker() @@ -96,24 +90,20 @@ class ConnectFragment : BaseFragment(), NavigationBarPainter { } } - status = ConnectionStatus(view, requireMainActivity()) - - locationInfo = - LocationInfo(view, requireContext()) { connectViewModel.toggleTunnelInfoExpansion() } - - actionButton = ConnectActionButton(view) - - actionButton.apply { - onConnect = { serviceConnectionManager.connectionProxy()?.connect() } - onCancel = { serviceConnectionManager.connectionProxy()?.disconnect() } - onReconnect = { serviceConnectionManager.connectionProxy()?.reconnect() } - onDisconnect = { serviceConnectionManager.connectionProxy()?.disconnect() } - } - - switchLocationButton = - view.findViewById<SwitchLocationButton>(R.id.switch_location).apply { - onClick = { openSwitchLocationScreen() } + view.findViewById<ComposeView>(R.id.compose_view).setContent { + AppTheme { + val state = connectViewModel.uiState.collectAsState().value + ConnectScreen( + uiState = state, + onDisconnectClick = connectViewModel::onDisconnectClick, + onReconnectClick = connectViewModel::onReconnectClick, + onConnectClick = connectViewModel::onConnectClick, + onCancelClick = connectViewModel::onCancelClick, + onSwitchLocationClick = { openSwitchLocationScreen() }, + onToggleTunnelInfo = connectViewModel::toggleTunnelInfoExpansion + ) } + } return view } @@ -137,14 +127,11 @@ class ConnectFragment : BaseFragment(), NavigationBarPainter { private fun CoroutineScope.launchViewModelSubscription() = launch { connectViewModel.uiState.collect { uiState -> - locationInfo.location = uiState.location - switchLocationButton.location = uiState.relayLocation uiState.versionInfo?.let { versionInfoNotification.updateVersionInfo(uiState.versionInfo) } tunnelStateNotification.updateTunnelState(uiState.tunnelUiState) - updateTunnelState(uiState.tunnelUiState, uiState.tunnelRealState) - locationInfo.isTunnelInfoExpanded = uiState.isTunnelInfoExpanded + updateTunnelState(uiState.tunnelRealState) } } @@ -154,13 +141,8 @@ class ConnectFragment : BaseFragment(), NavigationBarPainter { } } - private fun updateTunnelState(uiState: TunnelState, realState: TunnelState) { - locationInfo.state = realState + private fun updateTunnelState(realState: TunnelState) { headerBar.tunnelState = realState - status.setState(realState) - - actionButton.tunnelState = uiState - switchLocationButton.tunnelState = uiState if (realState.isTunnelErrorStateDueToExpiredAccount()) { openOutOfTimeScreen() diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ConnectionProxy.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ConnectionProxy.kt index 33a1ffb4e9..8cb33586d1 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ConnectionProxy.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ConnectionProxy.kt @@ -14,7 +14,7 @@ import net.mullvad.mullvadvpn.util.trySendRequest import net.mullvad.talpid.tunnel.ActionAfterDisconnect import net.mullvad.talpid.util.EventNotifier -val ANTICIPATED_STATE_TIMEOUT_MS = 1500L +const val ANTICIPATED_STATE_TIMEOUT_MS = 1500L class ConnectionProxy(private val connection: Messenger, eventDispatcher: EventDispatcher) { private var resetAnticipatedStateJob: Job? = null diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/widget/SwitchLocationButton.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/widget/SwitchLocationButton.kt deleted file mode 100644 index 02ec153f0a..0000000000 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/widget/SwitchLocationButton.kt +++ /dev/null @@ -1,89 +0,0 @@ -package net.mullvad.mullvadvpn.ui.widget - -import android.content.Context -import android.util.AttributeSet -import android.view.LayoutInflater -import android.view.View -import android.widget.FrameLayout -import android.widget.TextView -import kotlin.properties.Delegates.observable -import net.mullvad.mullvadvpn.R -import net.mullvad.mullvadvpn.model.TunnelState -import net.mullvad.mullvadvpn.relaylist.RelayItem -import net.mullvad.talpid.tunnel.ActionAfterDisconnect - -class SwitchLocationButton : FrameLayout { - private val container = - context.getSystemService(Context.LAYOUT_INFLATER_SERVICE).let { service -> - val inflater = service as LayoutInflater - - inflater.inflate(R.layout.switch_location_button, this) - } - - private val buttonWithLabel = - container.findViewById<View>(R.id.button_with_label).apply { - setOnClickListener { onClick?.invoke() } - } - - private val buttonWithLocation = - container.findViewById<TextView>(R.id.button_with_location).apply { - setOnClickListener { onClick?.invoke() } - } - - var onClick: (() -> Unit)? = null - - var location by - observable<RelayItem?>(null) { _, _, location -> - buttonWithLocation.text = location?.locationName ?: "" - } - - var tunnelState by - observable<TunnelState>(TunnelState.Disconnected) { _, _, state -> - when (state) { - is TunnelState.Disconnected -> showLocation() - is TunnelState.Disconnecting -> { - when (state.actionAfterDisconnect) { - ActionAfterDisconnect.Nothing -> showLocation() - ActionAfterDisconnect.Block -> showLocation() - ActionAfterDisconnect.Reconnect -> showLabel() - } - } - is TunnelState.Connecting -> showLabel() - is TunnelState.Connected -> showLabel() - is TunnelState.Error -> showLocation() - } - } - - constructor(context: Context) : super(context) - - constructor(context: Context, attributes: AttributeSet) : super(context, attributes) - - constructor( - context: Context, - attributes: AttributeSet, - defaultStyleAttribute: Int - ) : super(context, attributes, defaultStyleAttribute) - - private fun showLabel() { - updateButton(buttonWithLabel, true) - updateButton(buttonWithLocation, false) - } - - private fun showLocation() { - updateButton(buttonWithLabel, false) - updateButton(buttonWithLocation, true) - } - - private fun updateButton(button: View, show: Boolean) { - button.apply { - setEnabled(show) - - visibility = - if (show) { - View.VISIBLE - } else { - View.INVISIBLE - } - } - } -} diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/GeoIpLocationExtensions.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/GeoIpLocationExtensions.kt new file mode 100644 index 0000000000..dcde072970 --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/GeoIpLocationExtensions.kt @@ -0,0 +1,11 @@ +package net.mullvad.mullvadvpn.util + +import net.mullvad.mullvadvpn.model.GeoIpLocation + +fun GeoIpLocation.toOutAddress(): String = + when { + ipv6 != null && ipv4 != null -> "${ipv4.hostAddress} / ${ipv6.hostAddress}" + ipv6 != null -> ipv6.hostAddress ?: "" + ipv4 != null -> ipv4.hostAddress ?: "" + else -> "" + } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/TunnelEndpointExtensions.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/TunnelEndpointExtensions.kt new file mode 100644 index 0000000000..d39104e67a --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/TunnelEndpointExtensions.kt @@ -0,0 +1,12 @@ +package net.mullvad.mullvadvpn.util + +import net.mullvad.talpid.net.TransportProtocol +import net.mullvad.talpid.net.TunnelEndpoint + +fun TunnelEndpoint.toInAddress(): Triple<String, Int, TransportProtocol> { + val relayEndpoint = this.obfuscation?.endpoint ?: this.endpoint + val host = relayEndpoint.address.address.hostAddress ?: "" + val port = relayEndpoint.address.port + val protocol = relayEndpoint.protocol + return Triple(host, port, protocol) +} diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt index fb2702628d..826376ad9f 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt @@ -23,11 +23,16 @@ import net.mullvad.mullvadvpn.ui.serviceconnection.RelayListListener import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionContainer import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionState +import net.mullvad.mullvadvpn.ui.serviceconnection.connectionProxy import net.mullvad.mullvadvpn.util.appVersionCallbackFlow import net.mullvad.mullvadvpn.util.callbackFlowFromNotifier import net.mullvad.mullvadvpn.util.combine +import net.mullvad.mullvadvpn.util.toInAddress +import net.mullvad.mullvadvpn.util.toOutAddress +import net.mullvad.talpid.tunnel.ActionAfterDisconnect -class ConnectViewModel(serviceConnectionManager: ServiceConnectionManager) : ViewModel() { +class ConnectViewModel(private val serviceConnectionManager: ServiceConnectionManager) : + ViewModel() { private val _shared: SharedFlow<ServiceConnectionContainer> = serviceConnectionManager.connectionState .flatMapLatest { state -> @@ -64,7 +69,28 @@ class ConnectViewModel(serviceConnectionManager: ServiceConnectionManager) : Vie versionInfo = versionInfo, tunnelUiState = tunnelUiState, tunnelRealState = tunnelRealState, - isTunnelInfoExpanded = isTunnelInfoExpanded + isTunnelInfoExpanded = isTunnelInfoExpanded, + inAddress = + when (tunnelRealState) { + is TunnelState.Connected -> tunnelRealState.endpoint.toInAddress() + is TunnelState.Connecting -> tunnelRealState.endpoint?.toInAddress() + else -> null + }, + outAddress = location?.toOutAddress() ?: "", + showLocation = + when (tunnelUiState) { + is TunnelState.Disconnected -> true + is TunnelState.Disconnecting -> { + when (tunnelUiState.actionAfterDisconnect) { + ActionAfterDisconnect.Nothing -> true + ActionAfterDisconnect.Block -> true + ActionAfterDisconnect.Reconnect -> false + } + } + is TunnelState.Connecting -> false + is TunnelState.Connected -> false + is TunnelState.Error -> true + } ) } } @@ -92,6 +118,19 @@ class ConnectViewModel(serviceConnectionManager: ServiceConnectionManager) : Vie _isTunnelInfoExpanded.value = _isTunnelInfoExpanded.value.not() } + fun onDisconnectClick() { + serviceConnectionManager.connectionProxy()?.disconnect() + } + fun onReconnectClick() { + serviceConnectionManager.connectionProxy()?.reconnect() + } + fun onConnectClick() { + serviceConnectionManager.connectionProxy()?.connect() + } + fun onCancelClick() { + serviceConnectionManager.connectionProxy()?.disconnect() + } + companion object { const val TUNNEL_STATE_UPDATE_DEBOUNCE_DURATION_MILLIS: Long = 200 } diff --git a/android/app/src/main/res/drawable/icon_chevron_expand.xml b/android/app/src/main/res/drawable/icon_chevron_expand.xml deleted file mode 100644 index f85e172a00..0000000000 --- a/android/app/src/main/res/drawable/icon_chevron_expand.xml +++ /dev/null @@ -1,7 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<rotate xmlns:android="http://schemas.android.com/apk/res/android" - android:fromDegrees="90" - android:toDegrees="90" - android:pivotX="50%" - android:pivotY="50%" - android:drawable="@drawable/icon_chevron" /> diff --git a/android/app/src/main/res/drawable/transparent_red_left_half_button_background.xml b/android/app/src/main/res/drawable/transparent_red_left_half_button_background.xml deleted file mode 100644 index dab41c1f57..0000000000 --- a/android/app/src/main/res/drawable/transparent_red_left_half_button_background.xml +++ /dev/null @@ -1,26 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_pressed="false" - android:state_focused="false"> - <shape android:shape="rectangle"> - <corners android:topLeftRadius="4dp" - android:bottomLeftRadius="4dp" /> - <solid android:color="@color/red60" /> - </shape> - </item> - <item android:state_pressed="false" - android:state_focused="true"> - <shape android:shape="rectangle"> - <corners android:topLeftRadius="4dp" - android:bottomLeftRadius="4dp" /> - <solid android:color="@color/red95" /> - </shape> - </item> - <item android:state_pressed="true"> - <shape android:shape="rectangle"> - <corners android:topLeftRadius="4dp" - android:bottomLeftRadius="4dp" /> - <solid android:color="@color/red80" /> - </shape> - </item> -</selector> diff --git a/android/app/src/main/res/drawable/transparent_red_right_half_button_background.xml b/android/app/src/main/res/drawable/transparent_red_right_half_button_background.xml deleted file mode 100644 index f23bde9841..0000000000 --- a/android/app/src/main/res/drawable/transparent_red_right_half_button_background.xml +++ /dev/null @@ -1,26 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_pressed="false" - android:state_focused="false"> - <shape android:shape="rectangle"> - <corners android:topRightRadius="4dp" - android:bottomRightRadius="4dp" /> - <solid android:color="@color/red60" /> - </shape> - </item> - <item android:state_pressed="false" - android:state_focused="true"> - <shape android:shape="rectangle"> - <corners android:topRightRadius="4dp" - android:bottomRightRadius="4dp" /> - <solid android:color="@color/red95" /> - </shape> - </item> - <item android:state_pressed="true"> - <shape android:shape="rectangle"> - <corners android:topRightRadius="4dp" - android:bottomRightRadius="4dp" /> - <solid android:color="@color/red80" /> - </shape> - </item> -</selector> diff --git a/android/app/src/main/res/drawable/white20_button_background.xml b/android/app/src/main/res/drawable/white20_button_background.xml deleted file mode 100644 index f52c7cf182..0000000000 --- a/android/app/src/main/res/drawable/white20_button_background.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_pressed="false" - android:state_focused="false"> - <shape android:shape="rectangle"> - <corners android:radius="4dp" /> - <solid android:color="@color/white20" /> - </shape> - </item> - <item android:state_pressed="false" - android:state_focused="true"> - <shape android:shape="rectangle"> - <corners android:radius="4dp" /> - <solid android:color="@color/white60" /> - </shape> - </item> - <item android:state_pressed="true"> - <shape android:shape="rectangle"> - <corners android:radius="4dp" /> - <solid android:color="@color/white40" /> - </shape> - </item> -</selector> diff --git a/android/app/src/main/res/layout/connect.xml b/android/app/src/main/res/layout/connect.xml index 595832397e..9db162a667 100644 --- a/android/app/src/main/res/layout/connect.xml +++ b/android/app/src/main/res/layout/connect.xml @@ -1,7 +1,7 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" + xmlns:app="http://schemas.android.com/apk/res-auto" android:orientation="vertical"> <net.mullvad.mullvadvpn.ui.widget.HeaderBar android:id="@+id/header_bar" android:layout_width="match_parent" @@ -13,139 +13,9 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:elevation="0.25dp" /> - <ScrollView android:id="@+id/body" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:fillViewport="true" - app:layout_behavior="net.mullvad.mullvadvpn.ui.UnderNotificationBannerBehavior"> - - <LinearLayout android:layout_width="match_parent" - android:layout_height="match_parent" - android:gravity="bottom" - android:orientation="vertical"> - <ProgressBar android:id="@+id/connecting_spinner" - android:layout_width="60dp" - android:layout_height="60dp" - android:layout_gravity="center" - android:layout_marginBottom="7dp" - android:indeterminate="true" - android:indeterminateDrawable="@drawable/icon_spinner" - android:indeterminateDuration="600" - android:indeterminateOnly="true" - android:visibility="invisible" /> - <LinearLayout android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="7dp" - android:layout_weight="0" - android:gravity="start" - android:orientation="vertical"> - <TextView android:id="@+id/connection_status" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginHorizontal="@dimen/side_margin" - android:layout_marginBottom="2dp" - android:text="@string/unsecured_connection" - android:textAllCaps="true" - android:textColor="@color/red" - android:textSize="@dimen/text_medium" - android:textStyle="bold" /> - <TextView android:id="@+id/country" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginHorizontal="@dimen/side_margin" - android:text="" - android:textColor="@color/white" - android:textSize="@dimen/text_huge" - android:textStyle="bold" /> - <TextView android:id="@+id/city" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginHorizontal="@dimen/side_margin" - android:layout_marginBottom="2dp" - android:text="" - android:textColor="@color/white" - android:textSize="@dimen/text_huge" - android:textStyle="bold" /> - <LinearLayout android:id="@+id/tunnel_info" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_weight="0" - android:background="?android:attr/selectableItemBackground" - android:clickable="true" - android:focusable="true" - android:gravity="bottom" - android:orientation="vertical" - android:paddingHorizontal="@dimen/side_margin"> - <LinearLayout android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:gravity="center" - android:orientation="horizontal"> - <TextView android:id="@+id/hostname" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="" - android:textColor="@color/white40" - android:textSize="@dimen/text_hostname" /> - <ImageView android:id="@+id/chevron" - android:layout_width="24dp" - android:layout_height="24dp" - android:layout_marginHorizontal="5dp" - android:alpha="0.4" - android:src="@drawable/icon_chevron_expand" /> - </LinearLayout> - <TextView android:id="@+id/tunnel_protocol" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="1dp" - android:text="" - android:textColor="@color/white" - android:textSize="@dimen/text_small" /> - <TextView android:id="@+id/in_address" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="1dp" - android:text="" - android:textColor="@color/white" - android:textSize="@dimen/text_small" /> - <TextView android:id="@+id/out_address" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="1dp" - android:text="" - android:textColor="@color/white" - android:textSize="@dimen/text_small" /> - </LinearLayout> - </LinearLayout> - <LinearLayout android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_weight="0" - android:orientation="vertical" - android:paddingHorizontal="@dimen/side_margin" - android:paddingTop="@dimen/button_separation" - android:paddingBottom="@dimen/screen_vertical_margin"> - <net.mullvad.mullvadvpn.ui.widget.SwitchLocationButton android:id="@+id/switch_location" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/button_separation" /> - <LinearLayout android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal"> - <Button android:id="@+id/action_button" - style="@style/GreenButton" - android:layout_weight="1" - android:text="@string/connect" /> - <ImageButton android:id="@+id/reconnect_button" - android:layout_width="50dp" - android:layout_height="match_parent" - android:layout_marginLeft="1dp" - android:layout_weight="0" - android:background="@drawable/transparent_red_right_half_button_background" - android:padding="9dp" - android:src="@drawable/icon_reload" - android:visibility="gone" /> - </LinearLayout> - </LinearLayout> - </LinearLayout> - </ScrollView> + <androidx.compose.ui.platform.ComposeView android:id="@+id/compose_view" + app:layout_behavior="net.mullvad.mullvadvpn.ui.UnderNotificationBannerBehavior" + android:layout_width="match_parent" + android:layout_height="match_parent" /> </androidx.coordinatorlayout.widget.CoordinatorLayout> </LinearLayout> diff --git a/android/app/src/main/res/layout/switch_location_button.xml b/android/app/src/main/res/layout/switch_location_button.xml deleted file mode 100644 index d9ed79956f..0000000000 --- a/android/app/src/main/res/layout/switch_location_button.xml +++ /dev/null @@ -1,14 +0,0 @@ -<merge xmlns:android="http://schemas.android.com/apk/res/android"> - <Button android:id="@+id/button_with_label" - android:layout_gravity="bottom" - android:paddingHorizontal="9dp" - android:text="@string/switch_location" - style="@style/White20Button" /> - <Button android:id="@+id/button_with_location" - android:layout_gravity="bottom" - android:paddingHorizontal="9dp" - android:text="@string/switch_location" - android:drawableRight="@drawable/icon_chevron" - android:visibility="invisible" - style="@style/White20Button" /> -</merge> diff --git a/android/app/src/main/res/values/dimensions.xml b/android/app/src/main/res/values/dimensions.xml index 7c55e8f310..9928c3c759 100644 --- a/android/app/src/main/res/values/dimensions.xml +++ b/android/app/src/main/res/values/dimensions.xml @@ -25,7 +25,6 @@ <dimen name="chevron_width">14dp</dimen> <dimen name="chevron_height">24dp</dimen> <dimen name="text_small">13sp</dimen> - <dimen name="text_hostname">15sp</dimen> <dimen name="text_medium">16sp</dimen> <dimen name="text_medium_plus">18sp</dimen> <dimen name="text_big">24sp</dimen> diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index dab9229592..e03dd86700 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -38,9 +38,6 @@ <style name="BlueButton" parent="Button"> <item name="android:background">@drawable/blue_button_background</item> </style> - <style name="White20Button" parent="Button"> - <item name="android:background">@drawable/white20_button_background</item> - </style> <style name="SettingsHeader"> <item name="android:textColor">@color/white</item> <item name="android:textStyle">bold</item> |
