@@ -48,142 +48,235 @@ fun MdvConnectionTelemetryCard(
4848 isConnecting : Boolean
4949) {
5050 MdvCardLow (modifier = Modifier .fillMaxWidth()) {
51- Column (modifier = Modifier .fillMaxWidth().padding(MdvSpace .S3 )) {
52- Text (
53- text = stringResource(R .string.home_connection_status_title),
54- style = MaterialTheme .typography.labelSmall,
55- color = MdvColor .PrimaryDim
56- )
57- androidx.compose.foundation.layout.Spacer (modifier = Modifier .height(MdvSpace .S1 ))
58- Text (
59- text = when (vpnState) {
60- VpnManager .VpnState .CONNECTED -> stringResource(R .string.home_connection_running)
61- VpnManager .VpnState .CONNECTING -> stringResource(R .string.home_connection_preparing)
62- VpnManager .VpnState .DISCONNECTING -> stringResource(R .string.home_state_disconnecting)
63- VpnManager .VpnState .ERROR -> stringResource(R .string.home_connection_error_check_logs)
64- else -> stringResource(R .string.home_state_disconnected)
65- },
66- style = MaterialTheme .typography.bodyMedium,
67- color = MdvColor .OnSurface
68- )
51+ Column (
52+ modifier = Modifier
53+ .fillMaxWidth()
54+ .padding(MdvSpace .S4 ),
55+ verticalArrangement = Arrangement .spacedBy(MdvSpace .S3 )
56+ ) {
57+ // Header Row: Status and Duration
58+ Row (
59+ modifier = Modifier .fillMaxWidth(),
60+ horizontalArrangement = Arrangement .SpaceBetween ,
61+ verticalAlignment = Alignment .CenterVertically
62+ ) {
63+ Column {
64+ Text (
65+ text = stringResource(R .string.home_connection_status_title).uppercase(),
66+ style = MaterialTheme .typography.labelSmall,
67+ color = MdvColor .PrimaryDim ,
68+ fontWeight = FontWeight .Bold
69+ )
70+ val statusText = when (vpnState) {
71+ VpnManager .VpnState .CONNECTED -> stringResource(R .string.home_connection_running)
72+ VpnManager .VpnState .CONNECTING -> stringResource(R .string.home_connection_preparing)
73+ VpnManager .VpnState .DISCONNECTING -> stringResource(R .string.home_state_disconnecting)
74+ VpnManager .VpnState .ERROR -> stringResource(R .string.home_connection_error_check_logs)
75+ else -> stringResource(R .string.home_state_disconnected)
76+ }
77+ val statusColor = when (vpnState) {
78+ VpnManager .VpnState .CONNECTED -> ConnectedGreen
79+ VpnManager .VpnState .ERROR -> DisconnectedRed
80+ else -> MdvColor .OnSurface
81+ }
82+ Text (
83+ text = statusText,
84+ style = MaterialTheme .typography.titleMedium,
85+ color = statusColor,
86+ fontWeight = FontWeight .Bold
87+ )
88+ }
6989
70- if (scanStatus.lastResolver.isNotBlank()) {
71- androidx.compose.foundation.layout.Spacer (modifier = Modifier .height(MdvSpace .S1 ))
72- Text (
73- text = stringResource(
74- R .string.home_resolver_row,
75- scanStatus.lastResolver,
76- scanStatus.lastDecision
77- ),
78- style = MaterialTheme .typography.bodySmall,
79- color = MdvColor .OnSurfaceVariant
80- )
81- }
82- if (scanStatus.validCount > 0 || scanStatus.rejectedCount > 0 ) {
83- androidx.compose.foundation.layout.Spacer (modifier = Modifier .height(2 .dp))
84- Text (
85- text = buildAnnotatedString {
86- append(stringResource(R .string.home_valid_prefix))
87- pushStyle(SpanStyle (color = ConnectedGreen , fontWeight = FontWeight .Bold ))
88- append(scanStatus.validCount.toString())
89- pop()
90- append(stringResource(R .string.home_rejected_prefix))
91- pushStyle(SpanStyle (color = DisconnectedRed , fontWeight = FontWeight .Bold ))
92- append(scanStatus.rejectedCount.toString())
93- pop()
94- },
95- style = MaterialTheme .typography.bodySmall
96- )
90+ if (connectedDurationSeconds > 0 ) {
91+ Column (horizontalAlignment = Alignment .End ) {
92+ Text (
93+ text = " SESSION" ,
94+ style = MaterialTheme .typography.labelSmall,
95+ color = MdvColor .OnSurfaceVariant ,
96+ fontWeight = FontWeight .Bold
97+ )
98+ Text (
99+ text = formatDuration(connectedDurationSeconds),
100+ style = MaterialTheme .typography.titleMedium,
101+ color = MdvColor .OnSurface ,
102+ fontWeight = FontWeight .Bold
103+ )
104+ }
105+ }
97106 }
107+
108+ // Scanning Progress Section
98109 if (scanStatus.scanning || isConnecting) {
99- androidx.compose.foundation.layout.Spacer (modifier = Modifier .height(MdvSpace .S2 ))
100- Text (
101- text = stringResource(R .string.home_dns_scan_progress, scannedCount, totalResolvers),
102- style = MaterialTheme .typography.bodySmall,
103- color = MdvColor .OnSurfaceVariant
104- )
105- androidx.compose.foundation.layout.Spacer (modifier = Modifier .height(MdvSpace .S1 ))
106- LinearProgressIndicator (
107- progress = { scanProgress },
108- modifier = Modifier
109- .fillMaxWidth()
110- .height(8 .dp),
111- color = MdvColor .PrimaryContainer ,
112- trackColor = MdvColor .SurfaceBright
113- )
114- }
115- if (scanStatus.syncedUploadMtu > 0 || scanStatus.syncedDownloadMtu > 0 ) {
116- androidx.compose.foundation.layout.Spacer (modifier = Modifier .height(2 .dp))
117- Text (
118- text = stringResource(
119- R .string.home_synced_mtu,
120- scanStatus.syncedUploadMtu,
121- scanStatus.syncedDownloadMtu
122- ),
123- style = MaterialTheme .typography.bodySmall,
124- color = MdvColor .OnSurfaceVariant
125- )
126- }
127- if (scanStatus.activeResolvers > 0 ) {
128- androidx.compose.foundation.layout.Spacer (modifier = Modifier .height(2 .dp))
129- Text (
130- text = stringResource(R .string.home_active_resolvers, scanStatus.activeResolvers),
131- style = MaterialTheme .typography.bodySmall.copy(fontWeight = FontWeight .SemiBold ),
132- color = MdvColor .OnSurface
133- )
134- }
135- androidx.compose.foundation.layout.Spacer (modifier = Modifier .height(MdvSpace .S1 ))
136- Text (
137- text = stringResource(R .string.home_speed_row, formatSpeed(downBps), formatSpeed(upBps)),
138- style = MaterialTheme .typography.bodySmall,
139- color = MdvColor .OnSurfaceVariant
140- )
141- if (downloadTotalBytes > 0 || uploadTotalBytes > 0 || connectedDurationSeconds > 0 ) {
142- androidx.compose.foundation.layout.Spacer (modifier = Modifier .height(2 .dp))
143- Text (
144- text = stringResource(
145- R .string.home_traffic_totals,
146- formatBytes(downloadTotalBytes),
147- formatBytes(uploadTotalBytes)
148- ),
149- style = MaterialTheme .typography.bodySmall,
150- color = MdvColor .OnSurfaceVariant
151- )
152- Text (
153- text = stringResource(
154- R .string.home_session_duration,
155- formatDuration(connectedDurationSeconds)
156- ),
157- style = MaterialTheme .typography.bodySmall,
158- color = MdvColor .OnSurfaceVariant
159- )
110+ androidx.compose.material3.Surface (
111+ shape = androidx.compose.foundation.shape.RoundedCornerShape (12 .dp),
112+ color = MdvColor .SurfaceHigh ,
113+ modifier = Modifier .fillMaxWidth()
114+ ) {
115+ Column (
116+ modifier = Modifier .padding(MdvSpace .S3 ),
117+ verticalArrangement = Arrangement .spacedBy(8 .dp)
118+ ) {
119+ Row (
120+ modifier = Modifier .fillMaxWidth(),
121+ horizontalArrangement = Arrangement .SpaceBetween
122+ ) {
123+ Text (
124+ text = " DNS Scan" ,
125+ style = MaterialTheme .typography.labelMedium,
126+ color = MdvColor .OnSurfaceVariant
127+ )
128+ Text (
129+ text = " $scannedCount / $totalResolvers " ,
130+ style = MaterialTheme .typography.labelMedium,
131+ color = MdvColor .PrimaryContainer ,
132+ fontWeight = FontWeight .Bold
133+ )
134+ }
135+ LinearProgressIndicator (
136+ progress = { scanProgress },
137+ modifier = Modifier
138+ .fillMaxWidth()
139+ .height(6 .dp),
140+ color = MdvColor .PrimaryContainer ,
141+ trackColor = MdvColor .SurfaceBright ,
142+ strokeCap = androidx.compose.ui.graphics.StrokeCap .Round
143+ )
144+ if (scanStatus.validCount > 0 || scanStatus.rejectedCount > 0 ) {
145+ Row (
146+ modifier = Modifier .fillMaxWidth(),
147+ horizontalArrangement = Arrangement .SpaceBetween
148+ ) {
149+ Text (
150+ text = buildAnnotatedString {
151+ append(" Valid: " )
152+ pushStyle(SpanStyle (color = ConnectedGreen , fontWeight = FontWeight .Bold ))
153+ append(scanStatus.validCount.toString())
154+ pop()
155+ },
156+ style = MaterialTheme .typography.bodySmall
157+ )
158+ Text (
159+ text = buildAnnotatedString {
160+ append(" Rejected: " )
161+ pushStyle(SpanStyle (color = DisconnectedRed , fontWeight = FontWeight .Bold ))
162+ append(scanStatus.rejectedCount.toString())
163+ pop()
164+ },
165+ style = MaterialTheme .typography.bodySmall
166+ )
167+ }
168+ }
169+ if (scanStatus.lastResolver.isNotBlank()) {
170+ Text (
171+ text = " ${scanStatus.lastResolver} • ${scanStatus.lastDecision} " ,
172+ style = MaterialTheme .typography.labelSmall,
173+ color = MdvColor .OnSurfaceVariant ,
174+ maxLines = 1 ,
175+ overflow = androidx.compose.ui.text.style.TextOverflow .Ellipsis
176+ )
177+ }
178+ }
179+ }
160180 }
161- androidx.compose.foundation.layout.Spacer (modifier = Modifier .height(MdvSpace .S2 ))
162- Text (
163- text = stringResource(R .string.home_socks_address, proxyHost, proxyPort),
164- style = MaterialTheme .typography.bodySmall.copy(fontWeight = FontWeight .SemiBold ),
165- color = MdvColor .OnSurface
166- )
167- if (socksAuthEnabled) {
168- androidx.compose.foundation.layout.Spacer (modifier = Modifier .height(MdvSpace .S1 ))
169- Text (
170- text = stringResource(R .string.home_socks_auth_title),
171- style = MaterialTheme .typography.bodySmall.copy(fontWeight = FontWeight .SemiBold ),
172- color = MdvColor .OnSurface
173- )
174- if (socksUser.isNotBlank()) {
175- Text (
176- text = stringResource(R .string.home_socks_username, socksUser),
177- style = MaterialTheme .typography.bodySmall,
178- color = MdvColor .OnSurfaceVariant
179- )
181+
182+ // Network Traffic Grid
183+ if (downloadTotalBytes > 0 || uploadTotalBytes > 0 || downBps > 0 || upBps > 0 ) {
184+ Row (
185+ modifier = Modifier .fillMaxWidth(),
186+ horizontalArrangement = Arrangement .spacedBy(MdvSpace .S3 )
187+ ) {
188+ // Download Column
189+ androidx.compose.material3.Surface (
190+ modifier = Modifier .weight(1f ),
191+ shape = androidx.compose.foundation.shape.RoundedCornerShape (12 .dp),
192+ color = MdvColor .SurfaceHigh
193+ ) {
194+ Column (
195+ modifier = Modifier .padding(MdvSpace .S3 ),
196+ horizontalAlignment = Alignment .CenterHorizontally
197+ ) {
198+ Text (
199+ text = " ↓ DOWNLOAD" ,
200+ style = MaterialTheme .typography.labelSmall,
201+ color = ConnectedGreen ,
202+ fontWeight = FontWeight .Bold
203+ )
204+ androidx.compose.foundation.layout.Spacer (modifier = Modifier .height(4 .dp))
205+ Text (
206+ text = formatSpeed(downBps),
207+ style = MaterialTheme .typography.titleMedium,
208+ color = MdvColor .OnSurface ,
209+ fontWeight = FontWeight .Bold
210+ )
211+ Text (
212+ text = formatBytes(downloadTotalBytes),
213+ style = MaterialTheme .typography.bodySmall,
214+ color = MdvColor .OnSurfaceVariant
215+ )
216+ }
217+ }
218+
219+ // Upload Column
220+ androidx.compose.material3.Surface (
221+ modifier = Modifier .weight(1f ),
222+ shape = androidx.compose.foundation.shape.RoundedCornerShape (12 .dp),
223+ color = MdvColor .SurfaceHigh
224+ ) {
225+ Column (
226+ modifier = Modifier .padding(MdvSpace .S3 ),
227+ horizontalAlignment = Alignment .CenterHorizontally
228+ ) {
229+ Text (
230+ text = " ↑ UPLOAD" ,
231+ style = MaterialTheme .typography.labelSmall,
232+ color = MdvColor .PrimaryContainer ,
233+ fontWeight = FontWeight .Bold
234+ )
235+ androidx.compose.foundation.layout.Spacer (modifier = Modifier .height(4 .dp))
236+ Text (
237+ text = formatSpeed(upBps),
238+ style = MaterialTheme .typography.titleMedium,
239+ color = MdvColor .OnSurface ,
240+ fontWeight = FontWeight .Bold
241+ )
242+ Text (
243+ text = formatBytes(uploadTotalBytes),
244+ style = MaterialTheme .typography.bodySmall,
245+ color = MdvColor .OnSurfaceVariant
246+ )
247+ }
248+ }
180249 }
181- if (socksPass.isNotBlank()) {
182- Text (
183- text = stringResource(R .string.home_socks_password, socksPass),
184- style = MaterialTheme .typography.bodySmall,
185- color = MdvColor .OnSurfaceVariant
186- )
250+ }
251+
252+ // Extra Info (MTU, Active Resolvers, SOCKS)
253+ if (vpnState == VpnManager .VpnState .CONNECTED ) {
254+ Column (
255+ modifier = Modifier .fillMaxWidth(),
256+ verticalArrangement = Arrangement .spacedBy(4 .dp)
257+ ) {
258+ if (scanStatus.activeResolvers > 0 ) {
259+ Row (modifier = Modifier .fillMaxWidth(), horizontalArrangement = Arrangement .SpaceBetween ) {
260+ Text (" Active Resolvers" , style = MaterialTheme .typography.bodySmall, color = MdvColor .OnSurfaceVariant )
261+ Text (scanStatus.activeResolvers.toString(), style = MaterialTheme .typography.bodySmall, color = MdvColor .OnSurface , fontWeight = FontWeight .Bold )
262+ }
263+ }
264+ if (scanStatus.syncedUploadMtu > 0 || scanStatus.syncedDownloadMtu > 0 ) {
265+ Row (modifier = Modifier .fillMaxWidth(), horizontalArrangement = Arrangement .SpaceBetween ) {
266+ Text (" Synced MTU (Up/Down)" , style = MaterialTheme .typography.bodySmall, color = MdvColor .OnSurfaceVariant )
267+ Text (" ${scanStatus.syncedUploadMtu} / ${scanStatus.syncedDownloadMtu} " , style = MaterialTheme .typography.bodySmall, color = MdvColor .OnSurface , fontWeight = FontWeight .Bold )
268+ }
269+ }
270+ Row (modifier = Modifier .fillMaxWidth(), horizontalArrangement = Arrangement .SpaceBetween ) {
271+ Text (" SOCKS5 Proxy" , style = MaterialTheme .typography.bodySmall, color = MdvColor .OnSurfaceVariant )
272+ Text (" $proxyHost :$proxyPort " , style = MaterialTheme .typography.bodySmall, color = MdvColor .OnSurface , fontWeight = FontWeight .Bold )
273+ }
274+ if (socksAuthEnabled && socksUser.isNotBlank()) {
275+ Row (modifier = Modifier .fillMaxWidth(), horizontalArrangement = Arrangement .SpaceBetween ) {
276+ Text (" SOCKS5 Auth" , style = MaterialTheme .typography.bodySmall, color = MdvColor .OnSurfaceVariant )
277+ Text (" Enabled ($socksUser )" , style = MaterialTheme .typography.bodySmall, color = MdvColor .OnSurface , fontWeight = FontWeight .Bold )
278+ }
279+ }
187280 }
188281 }
189282 }
0 commit comments