diff --git a/app/src/main/java/com/bitchat/android/ui/debug/DebugSettingsSheet.kt b/app/src/main/java/com/bitchat/android/ui/debug/DebugSettingsSheet.kt index f060fe9d..6881494d 100644 --- a/app/src/main/java/com/bitchat/android/ui/debug/DebugSettingsSheet.kt +++ b/app/src/main/java/com/bitchat/android/ui/debug/DebugSettingsSheet.kt @@ -27,6 +27,71 @@ import com.bitchat.android.mesh.BluetoothMeshService import com.bitchat.android.services.meshgraph.MeshGraphService import kotlinx.coroutines.launch +@Composable +fun MeshTopologySection() { + val colorScheme = MaterialTheme.colorScheme + val graphService = remember { MeshGraphService.getInstance() } + val snapshot by graphService.graphState.collectAsState() + + Surface(shape = RoundedCornerShape(12.dp), color = colorScheme.surfaceVariant.copy(alpha = 0.2f)) { + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) { + Icon(Icons.Filled.SettingsEthernet, contentDescription = null, tint = Color(0xFF8E8E93)) + Text("mesh topology", fontFamily = FontFamily.Monospace, fontSize = 14.sp, fontWeight = FontWeight.Medium) + } + val nodes = snapshot.nodes + val edges = snapshot.edges + val empty = nodes.isEmpty() + if (empty) { + Text("no gossip yet", fontFamily = FontFamily.Monospace, fontSize = 11.sp, color = colorScheme.onSurface.copy(alpha = 0.6f)) + } else { + androidx.compose.foundation.Canvas(Modifier.fillMaxWidth().height(220.dp).background(colorScheme.surface.copy(alpha = 0.4f))) { + val w = size.width + val h = size.height + val cx = w / 2f + val cy = h / 2f + val radius = (minOf(w, h) * 0.36f) + val n = nodes.size + if (n == 1) { + // Single node centered + drawCircle(color = Color(0xFF00C851), radius = 12f, center = androidx.compose.ui.geometry.Offset(cx, cy)) + } else { + // Circular layout + val positions = nodes.mapIndexed { i, node -> + val angle = (2 * Math.PI * i.toDouble()) / n + val x = cx + (radius * Math.cos(angle)).toFloat() + val y = cy + (radius * Math.sin(angle)).toFloat() + node.peerID to androidx.compose.ui.geometry.Offset(x, y) + }.toMap() + + // Draw edges + edges.forEach { e -> + val p1 = positions[e.a] + val p2 = positions[e.b] + if (p1 != null && p2 != null) { + drawLine(color = Color(0xFF4A90E2), start = p1, end = p2, strokeWidth = 2f) + } + } + + // Draw nodes + nodes.forEach { node -> + val pos = positions[node.peerID] ?: androidx.compose.ui.geometry.Offset(cx, cy) + drawCircle(color = Color(0xFF00C851), radius = 10f, center = pos) + } + } + } + // Label list for clarity under the canvas + LazyColumn(modifier = Modifier.fillMaxWidth().heightIn(max = 140.dp)) { + items(nodes) { node -> + val label = "${node.peerID.take(8)} • ${node.nickname ?: "unknown"}" + Text(label, fontFamily = FontFamily.Monospace, fontSize = 11.sp, color = colorScheme.onSurface.copy(alpha = 0.85f)) + } + } + } + } + } +} + @OptIn(ExperimentalMaterial3Api::class) @Composable fun DebugSettingsSheet(