Skip to content

Commit 0ef5f72

Browse files
committed
style(ui): completely redesign connection telemetry card for a premium and organized layout
1 parent 127e561 commit 0ef5f72

1 file changed

Lines changed: 224 additions & 131 deletions

File tree

android/app/src/main/java/com/masterdns/vpn/ui/home/HomeStatusCards.kt

Lines changed: 224 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)