diff --git a/android/app/src/main/java/com/masterdns/vpn/ui/home/HomeScreen.kt b/android/app/src/main/java/com/masterdns/vpn/ui/home/HomeScreen.kt index e7ca32d..d7d056c 100644 --- a/android/app/src/main/java/com/masterdns/vpn/ui/home/HomeScreen.kt +++ b/android/app/src/main/java/com/masterdns/vpn/ui/home/HomeScreen.kt @@ -78,6 +78,9 @@ fun HomeScreen( val socksAuthEnabled = advanced["SOCKS5_AUTH"].equals("true", ignoreCase = true) val socksUser = advanced["SOCKS5_USER"]?.trim().orEmpty() val socksPass = advanced["SOCKS5_PASS"]?.trim().orEmpty() + val configuredResolverCount = remember(selectedProfile?.resolvers) { + countConfiguredResolvers(selectedProfile?.resolvers.orEmpty()) + } val vpnPermissionLauncher = rememberLauncherForActivityResult( ActivityResultContracts.StartActivityForResult() @@ -223,6 +226,7 @@ fun HomeScreen( MdvConnectionTelemetryCard( vpnState = vpnState, scanStatus = scanStatus, + configuredResolverCount = configuredResolverCount, scannedCount = scannedCount, totalResolvers = totalResolvers, scanProgress = scanProgress, @@ -292,6 +296,7 @@ fun HomeScreen( MdvConnectionTelemetryCard( vpnState = vpnState, scanStatus = scanStatus, + configuredResolverCount = configuredResolverCount, scannedCount = scannedCount, totalResolvers = totalResolvers, scanProgress = scanProgress, @@ -321,6 +326,13 @@ fun HomeScreen( } } +private fun countConfiguredResolvers(resolvers: String): Int { + return resolvers + .lineSequence() + .map { it.substringBefore("#").trim() } + .count { it.isNotEmpty() } +} + private fun parseAdvanced(json: String): Map { return try { val type = object : TypeToken>() {}.type diff --git a/android/app/src/main/java/com/masterdns/vpn/ui/home/HomeStatusCards.kt b/android/app/src/main/java/com/masterdns/vpn/ui/home/HomeStatusCards.kt index 1548d7b..f36b890 100644 --- a/android/app/src/main/java/com/masterdns/vpn/ui/home/HomeStatusCards.kt +++ b/android/app/src/main/java/com/masterdns/vpn/ui/home/HomeStatusCards.kt @@ -32,6 +32,7 @@ import com.masterdns.vpn.util.VpnManager fun MdvConnectionTelemetryCard( vpnState: VpnManager.VpnState, scanStatus: VpnManager.ScanStatus, + configuredResolverCount: Int, scannedCount: Int, totalResolvers: Int, scanProgress: Float, @@ -129,6 +130,15 @@ fun MdvConnectionTelemetryCard( color = MdvColor.OnSurface ) } + ResolverTroubleshootingBlock( + configuredResolverCount = configuredResolverCount, + coreTotalResolvers = totalResolvers, + scannedCount = scannedCount, + validCount = scanStatus.validCount, + rejectedCount = scanStatus.rejectedCount, + isConnecting = isConnecting, + isError = vpnState == VpnManager.VpnState.ERROR + ) androidx.compose.foundation.layout.Spacer(modifier = Modifier.height(MdvSpace.S1)) Text( text = stringResource(R.string.home_speed_row, formatSpeed(downBps), formatSpeed(upBps)), @@ -167,6 +177,58 @@ fun MdvConnectionTelemetryCard( } } +@Composable +private fun ResolverTroubleshootingBlock( + configuredResolverCount: Int, + coreTotalResolvers: Int, + scannedCount: Int, + validCount: Int, + rejectedCount: Int, + isConnecting: Boolean, + isError: Boolean +) { + val shouldShow = configuredResolverCount > 0 || + coreTotalResolvers > 0 || + scannedCount > 0 || + isConnecting || + isError + if (!shouldShow) return + + androidx.compose.foundation.layout.Spacer(modifier = Modifier.height(MdvSpace.S2)) + Text( + text = stringResource(R.string.home_resolver_diagnostics_title), + style = MaterialTheme.typography.bodySmall.copy(fontWeight = FontWeight.SemiBold), + color = MdvColor.OnSurface + ) + androidx.compose.foundation.layout.Spacer(modifier = Modifier.height(2.dp)) + Text( + text = stringResource( + R.string.home_resolver_diagnostics_counts, + configuredResolverCount, + coreTotalResolvers + ), + style = MaterialTheme.typography.bodySmall, + color = MdvColor.OnSurfaceVariant + ) + + val warningText = when { + configuredResolverCount == 0 -> stringResource(R.string.home_resolver_no_configured_warning) + isConnecting && coreTotalResolvers == 0 -> stringResource(R.string.home_resolver_waiting_core_scan) + scannedCount > 0 && validCount == 0 && rejectedCount > 0 -> + stringResource(R.string.home_resolver_all_rejected_warning) + else -> null + } + + warningText?.let { + androidx.compose.foundation.layout.Spacer(modifier = Modifier.height(2.dp)) + Text( + text = it, + style = MaterialTheme.typography.bodySmall, + color = if (validCount == 0 && rejectedCount > 0) DisconnectedRed else MdvColor.OnSurfaceVariant + ) + } +} + @Composable fun MdvProfileSelectorCard( profileName: String, diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 6b3f371..49af672 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -65,6 +65,11 @@ DNS Scan Progress: %1$d / %2$d Synced MTU: UP %1$d / DOWN %2$d Active Resolvers: %1$d + Resolver diagnostics + Configured lines: %1$d Core scan total: %2$d + No inline resolvers are configured for this profile. + Waiting for core resolver scan totals from runtime logs. + No accepted resolver yet. Check resolver format, network reachability, and profile settings. Download: %1$s Upload: %2$s SOCKS5: %1$s:%2$d SOCKS5 authentication