Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions nym-vpn-android/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed
- Add QUIC status and server description (https://github.com/nymtech/nym-vpn-client/pull/3696)

## [2.1.0]

### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,13 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
Expand All @@ -29,7 +26,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
Expand All @@ -38,14 +35,12 @@ import net.nymtech.nymvpn.ui.AppUiState
import net.nymtech.nymvpn.ui.Route
import net.nymtech.nymvpn.ui.common.buttons.MainStyledButton
import net.nymtech.nymvpn.ui.common.navigation.LocalNavController
import net.nymtech.nymvpn.ui.screens.details.components.CountryFlag
import net.nymtech.nymvpn.ui.screens.details.components.DetailsSectionBottom
import net.nymtech.nymvpn.ui.screens.details.components.DetailsSectionIP
import net.nymtech.nymvpn.ui.screens.details.components.DetailsSectionIdentity
import net.nymtech.nymvpn.ui.screens.details.components.DetailsSectionPerformance
import net.nymtech.nymvpn.ui.screens.details.components.DetailsSectionPrivacy
import net.nymtech.nymvpn.ui.screens.details.components.DetailsTopSection
import net.nymtech.nymvpn.ui.screens.hop.GatewayLocation
import net.nymtech.nymvpn.ui.theme.CustomTypography
import net.nymtech.nymvpn.ui.theme.NymVPNTheme
import net.nymtech.nymvpn.ui.theme.Theme
import net.nymtech.nymvpn.ui.theme.Typography
Expand Down Expand Up @@ -77,11 +72,14 @@ fun DetailsScreen(appUiState: AppUiState, id: String, type: GatewayType, gateway
viewModel.onSelected(uiState.identity, location)
navController.navigateAndForget(Route.Main())
},
onEnableQuicProtocolClick = {
navController.navigate(Route.Censorship)
},
)
}

@Composable
fun DetailsScreen(detailsUiState: DetailsUiState, onSelectServerClick: () -> Unit) {
fun DetailsScreen(detailsUiState: DetailsUiState, onSelectServerClick: () -> Unit, onEnableQuicProtocolClick: () -> Unit) {
Column(
verticalArrangement = Arrangement.spacedBy(8.dp.scaledHeight(), Alignment.Top),
horizontalAlignment = Alignment.Start,
Expand All @@ -98,27 +96,16 @@ fun DetailsScreen(detailsUiState: DetailsUiState, onSelectServerClick: () -> Uni
.verticalScroll(rememberScrollState())
.padding(24.dp),
) {
Text(
text = detailsUiState.name,
style = CustomTypography.titleMediumPlus,
color = MaterialTheme.colorScheme.onBackground,
fontFamily = FontFamily(Font(R.font.lab_grotesque_regular)),
DetailsTopSection(
name = detailsUiState.name,
countryCode = detailsUiState.countryCode,
location = detailsUiState.location,
asnKind = detailsUiState.asnKind,
isQuicFeatureFlagEnabled = detailsUiState.isQuicFeatureFlagEnabled,
isQuickSupportedByGateway = detailsUiState.isQuickSupportedByGateway,
description = detailsUiState.description,
onEnableQuicProtocolClick = onEnableQuicProtocolClick,
)
Row(
modifier = Modifier
.padding(top = 16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
CountryFlag(detailsUiState.countryCode, 16.dp)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = detailsUiState.location,
style = Typography.titleMedium,
color = MaterialTheme.colorScheme.onBackground,
fontFamily = FontFamily(Font(R.font.lab_grotesque_regular)),
)
}
DetailsSectionPrivacy(detailsUiState.asnKind)
DetailsSectionPerformance(detailsUiState.score, detailsUiState.load, detailsUiState.uptime, detailsUiState.lastUpdated)
DetailsSectionIP(detailsUiState.exitIpv4, detailsUiState.exitIpv6, detailsUiState.asn, detailsUiState.asnName)
DetailsSectionIdentity(detailsUiState.identity, detailsUiState.buildVersion)
Expand Down Expand Up @@ -153,12 +140,14 @@ fun DetailsScreen(detailsUiState: DetailsUiState, onSelectServerClick: () -> Uni
}

@Composable
@Preview
@PreviewLightDark
internal fun PreviewPrivacyScreen() {
NymVPNTheme(Theme.default()) {
val detailsUiState = DetailsUiState(
identity = "wqewqewqewqewqfade2123123",
name = "Jacksonville-Cloak04",
description = "Enabling safety and privacy in the age of AI and quantum computing." +
" Follow service status announcements at https://t.me/oceanusp17o",
location = "Jacksonville, Texas, United States",
countryCode = "DE",
mixnetScore = Score.HIGH,
Expand All @@ -172,7 +161,9 @@ internal fun PreviewPrivacyScreen() {
buildVersion = "1.2.4",
exitIpv4 = "12.34.152.125",
exitIpv6 = "12:ff:14::155",
isQuicFeatureFlagEnabled = true,
isQuickSupportedByGateway = true,
)
DetailsScreen(detailsUiState = detailsUiState, onSelectServerClick = {})
DetailsScreen(detailsUiState = detailsUiState, onSelectServerClick = {}, onEnableQuicProtocolClick = {})
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package net.nymtech.nymvpn.ui.screens.details

import net.nymtech.vpn.model.BridgeParameter
import net.nymtech.vpn.model.NymGateway
import nym_vpn_lib_types.AsnKind
import nym_vpn_lib_types.NodeIdentity
Expand All @@ -10,6 +11,7 @@ data class DetailsUiState(
val identity: NodeIdentity = "",
val name: String = "",
val location: String = "",
val description: String? = null,
val countryCode: String? = null,
val mixnetScore: Score? = null,
val score: Score? = null,
Expand All @@ -22,13 +24,16 @@ data class DetailsUiState(
val buildVersion: String? = null,
val exitIpv4: String? = null,
val exitIpv6: String? = null,
val isQuicFeatureFlagEnabled: Boolean = false,
val isQuickSupportedByGateway: Boolean = false,
) {
companion object {
fun from(gateway: NymGateway): DetailsUiState {
val country = Locale(gateway.twoLetterCountryISO.orEmpty(), gateway.twoLetterCountryISO.orEmpty()).displayCountry
return DetailsUiState(
identity = gateway.identity,
name = gateway.name,
description = gateway.description,
location = listOfNotNull(gateway.city, gateway.region, country).joinToString(", "),
countryCode = gateway.twoLetterCountryISO,
mixnetScore = gateway.mixnetScore,
Expand All @@ -42,7 +47,14 @@ data class DetailsUiState(
buildVersion = gateway.buildVersion,
exitIpv4 = gateway.exitIpv4s.firstOrNull(),
exitIpv6 = gateway.exitIpv6s.firstOrNull(),
isQuickSupportedByGateway = gateway.isQuicSupported(),
)
}

private fun NymGateway.isQuicSupported(): Boolean = run {
return bridgeInformation?.transports?.find {
it is BridgeParameter.QuicPlain
} != null
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import net.nymtech.nymvpn.data.SettingsRepository
import net.nymtech.nymvpn.manager.environment.EnvironmentManager
import net.nymtech.nymvpn.manager.environment.model.FeatureFlagKeys
import net.nymtech.nymvpn.ui.screens.hop.GatewayLocation
import net.nymtech.vpn.model.NymGateway
import net.nymtech.vpn.util.extensions.asEntryPoint
Expand All @@ -17,14 +19,18 @@ import javax.inject.Inject
@HiltViewModel
class DetailsViewModel @Inject constructor(
private val settingsRepository: SettingsRepository,
private val environmentManager: EnvironmentManager,
) : ViewModel() {

private val _uiState = MutableStateFlow(DetailsUiState())
val uiState = _uiState.asStateFlow()

fun filterGateways(id: String, gateways: List<NymGateway>) = viewModelScope.launch {
gateways.firstOrNull { gateway -> gateway.identity == id }?.let {
_uiState.value = DetailsUiState.from(it)
val isQuicFeatureFlagEnabled = environmentManager.isFeatureFlagEnabled(FeatureFlagKeys.QUIC)
_uiState.value = DetailsUiState.from(it).copy(
isQuicFeatureFlagEnabled = isQuicFeatureFlagEnabled,
)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package net.nymtech.nymvpn.ui.screens.details.components

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.size
Expand All @@ -16,16 +17,25 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import net.nymtech.nymvpn.R
import net.nymtech.nymvpn.ui.theme.CustomColors
import net.nymtech.nymvpn.ui.theme.Typography
import nym_vpn_lib_types.AsnKind

@Composable
fun DetailsSectionPrivacy(asnKind: AsnKind?) {
fun DetailsSectionPrivacy(
asnKind: AsnKind?,
isQuicFeatureFlagEnabled: Boolean,
isQuicSupportedByGateway: Boolean,
onEnableQuicProtocolClick: () -> Unit,
) {
val items = buildList<Pair<String, @Composable () -> Unit>> {
add(
stringResource(R.string.details_advanced_privacy) to {
Expand Down Expand Up @@ -78,59 +88,70 @@ fun DetailsSectionPrivacy(asnKind: AsnKind?) {
},
)
}
// stringResource(R.string.details_anti_censorship) to {
// val isResidential = asnKind == AsnKind.RESIDENTIAL
// val icon = if (isResidential) Icons.Outlined.Check else Icons.Filled.Circle
// val iconTint = if (isResidential) Color.Green else CustomColors.warning
// val text = stringResource(
// if (isResidential) R.string.details_quic_protocol
// else R.string.details_standard_protocol
// )
// Row(verticalAlignment = Alignment.CenterVertically) {
// Icon(
// painter = rememberVectorPainter(icon),
// contentDescription = null,
// tint = iconTint,
// modifier = Modifier.size(12.dp)
// )
// Spacer(modifier = Modifier.width(6.dp))
// Text(
// text = text,
// style = Typography.bodyMedium,
// color = MaterialTheme.colorScheme.onBackground,
// fontFamily = FontFamily(Font(R.font.lab_grotesque_regular))
// )
// }
// },
// bottomContent = {
// val annotatedText = buildAnnotatedString {
// pushStringAnnotation(tag = "QUIC", annotation = "quic_action")
// withStyle(
// style = SpanStyle(
// color = MaterialTheme.colorScheme.onBackground,
// textDecoration = TextDecoration.Underline,
// ),
// ) {
// append(stringResource(R.string.details_enable_quic_start))
// }
// pop()
// append(" ")
// append(stringResource(R.string.details_enable_quic_end))
// }
//
// Text(
// text = annotatedText,
// style = Typography.labelSmall.copy(
// color = MaterialTheme.colorScheme.outline,
// fontFamily = FontFamily(Font(R.font.lab_grotesque_regular)),
// ),
// modifier = Modifier.clickable {
//
// },
// )
//
// },

if (isQuicFeatureFlagEnabled) {
add(
stringResource(R.string.details_anti_censorship) to {
val icon = if (isQuicSupportedByGateway) Icons.Outlined.Check else Icons.Filled.Circle
val iconTint = if (isQuicSupportedByGateway) Color.Green else CustomColors.warning
val text = stringResource(
if (isQuicSupportedByGateway) {
R.string.details_quic_protocol
} else {
R.string.details_standard_protocol
},
)

Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
painter = rememberVectorPainter(icon),
contentDescription = null,
tint = iconTint,
modifier = Modifier.size(12.dp),
)
Spacer(modifier = Modifier.width(6.dp))
Text(
text = text,
style = Typography.bodyMedium,
color = MaterialTheme.colorScheme.onBackground,
fontFamily = FontFamily(Font(R.font.lab_grotesque_regular)),
)
}
},
)
}
}

InfoSection(items = items)
InfoSection(
items = items,
bottomContent = {
if (isQuicFeatureFlagEnabled && isQuicSupportedByGateway) {
val annotatedText = buildAnnotatedString {
pushStringAnnotation(tag = "QUIC", annotation = "quic_action")
withStyle(
style = SpanStyle(
color = MaterialTheme.colorScheme.onBackground,
textDecoration = TextDecoration.Underline,
),
) {
append(stringResource(R.string.details_enable_quic_start))
}
pop()
append(" ")
append(stringResource(R.string.details_enable_quic_end))
}

Text(
text = annotatedText,
style = Typography.labelSmall.copy(
color = MaterialTheme.colorScheme.outline,
fontFamily = FontFamily(Font(R.font.lab_grotesque_regular)),
),
modifier = Modifier.clickable {
onEnableQuicProtocolClick()
},
)
}
},
)
}
Loading