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..4954021 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 @@ -65,6 +65,9 @@ fun HomeScreen( val vpnState by VpnManager.state.collectAsState() val upBps by VpnManager.uploadSpeedBps.collectAsState() val downBps by VpnManager.downloadSpeedBps.collectAsState() + val uploadTotalBytes by VpnManager.uploadTotalBytes.collectAsState() + val downloadTotalBytes by VpnManager.downloadTotalBytes.collectAsState() + val connectedDurationSeconds by VpnManager.connectedDurationSeconds.collectAsState() val scanStatus by VpnManager.scanStatus.collectAsState() val selectedProfile by viewModel.selectedProfile.collectAsState() val error by VpnManager.errorMessage.collectAsState() @@ -228,6 +231,9 @@ fun HomeScreen( scanProgress = scanProgress, downBps = downBps, upBps = upBps, + downloadTotalBytes = downloadTotalBytes, + uploadTotalBytes = uploadTotalBytes, + connectedDurationSeconds = connectedDurationSeconds, proxyHost = proxyHost, proxyPort = proxyPort, socksAuthEnabled = socksAuthEnabled, @@ -297,6 +303,9 @@ fun HomeScreen( scanProgress = scanProgress, downBps = downBps, upBps = upBps, + downloadTotalBytes = downloadTotalBytes, + uploadTotalBytes = uploadTotalBytes, + connectedDurationSeconds = connectedDurationSeconds, proxyHost = proxyHost, proxyPort = proxyPort, socksAuthEnabled = socksAuthEnabled, 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..4c0e382 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 @@ -37,6 +37,9 @@ fun MdvConnectionTelemetryCard( scanProgress: Float, downBps: Long, upBps: Long, + downloadTotalBytes: Long, + uploadTotalBytes: Long, + connectedDurationSeconds: Long, proxyHost: String, proxyPort: Int, socksAuthEnabled: Boolean, @@ -135,6 +138,26 @@ fun MdvConnectionTelemetryCard( style = MaterialTheme.typography.bodySmall, color = MdvColor.OnSurfaceVariant ) + if (downloadTotalBytes > 0 || uploadTotalBytes > 0 || connectedDurationSeconds > 0) { + androidx.compose.foundation.layout.Spacer(modifier = Modifier.height(2.dp)) + Text( + text = stringResource( + R.string.home_traffic_totals, + formatBytes(downloadTotalBytes), + formatBytes(uploadTotalBytes) + ), + style = MaterialTheme.typography.bodySmall, + color = MdvColor.OnSurfaceVariant + ) + Text( + text = stringResource( + R.string.home_session_duration, + formatDuration(connectedDurationSeconds) + ), + style = MaterialTheme.typography.bodySmall, + color = MdvColor.OnSurfaceVariant + ) + } androidx.compose.foundation.layout.Spacer(modifier = Modifier.height(MdvSpace.S2)) Text( text = stringResource(R.string.home_socks_address, proxyHost, proxyPort), @@ -227,3 +250,27 @@ private fun formatSpeed(bps: Long): String { else -> "${bps} B/s" } } + +private fun formatBytes(bytes: Long): String { + val kb = 1024.0 + val mb = kb * 1024.0 + val gb = mb * 1024.0 + return when { + bytes >= gb -> String.format("%.2f GB", bytes / gb) + bytes >= mb -> String.format("%.2f MB", bytes / mb) + bytes >= kb -> String.format("%.1f KB", bytes / kb) + else -> "$bytes B" + } +} + +private fun formatDuration(seconds: Long): String { + val safeSeconds = seconds.coerceAtLeast(0L) + val hours = safeSeconds / 3600L + val minutes = (safeSeconds % 3600L) / 60L + val secs = safeSeconds % 60L + return if (hours > 0) { + "%d:%02d:%02d".format(hours, minutes, secs) + } else { + "%02d:%02d".format(minutes, secs) + } +} diff --git a/android/app/src/main/java/com/masterdns/vpn/util/VpnManager.kt b/android/app/src/main/java/com/masterdns/vpn/util/VpnManager.kt index 3287f90..9379d14 100644 --- a/android/app/src/main/java/com/masterdns/vpn/util/VpnManager.kt +++ b/android/app/src/main/java/com/masterdns/vpn/util/VpnManager.kt @@ -60,6 +60,12 @@ object VpnManager { val uploadSpeedBps: StateFlow = _uploadSpeedBps.asStateFlow() private val _downloadSpeedBps = MutableStateFlow(0L) val downloadSpeedBps: StateFlow = _downloadSpeedBps.asStateFlow() + private val _uploadTotalBytes = MutableStateFlow(0L) + val uploadTotalBytes: StateFlow = _uploadTotalBytes.asStateFlow() + private val _downloadTotalBytes = MutableStateFlow(0L) + val downloadTotalBytes: StateFlow = _downloadTotalBytes.asStateFlow() + private val _connectedDurationSeconds = MutableStateFlow(0L) + val connectedDurationSeconds: StateFlow = _connectedDurationSeconds.asStateFlow() data class ScanStatus( val scanning: Boolean = false, @@ -243,14 +249,23 @@ object VpnManager { var prevTx = TrafficStats.getUidTxBytes(uid).coerceAtLeast(0L) var prevRx = TrafficStats.getUidRxBytes(uid).coerceAtLeast(0L) var prevTime = System.currentTimeMillis() + val startedAt = prevTime + _uploadTotalBytes.value = 0L + _downloadTotalBytes.value = 0L + _connectedDurationSeconds.value = 0L while (isActive) { delay(1000L) val now = System.currentTimeMillis() val tx = TrafficStats.getUidTxBytes(uid).coerceAtLeast(0L) val rx = TrafficStats.getUidRxBytes(uid).coerceAtLeast(0L) val dt = (now - prevTime).coerceAtLeast(1L) - _uploadSpeedBps.value = ((tx - prevTx).coerceAtLeast(0L) * 1000L) / dt - _downloadSpeedBps.value = ((rx - prevRx).coerceAtLeast(0L) * 1000L) / dt + val uploadDelta = (tx - prevTx).coerceAtLeast(0L) + val downloadDelta = (rx - prevRx).coerceAtLeast(0L) + _uploadSpeedBps.value = (uploadDelta * 1000L) / dt + _downloadSpeedBps.value = (downloadDelta * 1000L) / dt + _uploadTotalBytes.value = _uploadTotalBytes.value + uploadDelta + _downloadTotalBytes.value = _downloadTotalBytes.value + downloadDelta + _connectedDurationSeconds.value = ((now - startedAt) / 1000L).coerceAtLeast(0L) prevTx = tx prevRx = rx prevTime = now diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 6b3f371..e4cc30e 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -66,6 +66,8 @@ Synced MTU: UP %1$d / DOWN %2$d Active Resolvers: %1$d Download: %1$s Upload: %2$s + Total: down %1$s / up %2$s + Session: %1$s SOCKS5: %1$s:%2$d SOCKS5 authentication Username: %1$s