@@ -200,20 +200,21 @@ fun WheelDiagnosticsDialog(
200200
201201 if (stopConfirm) {
202202 AlertDialog (
203+ shape = RoundedCornerShape (0 .dp),
203204 onDismissRequest = { stopConfirm = false },
204205 title = { Text (" Exit Service Mode" ) },
205206 text = {
206207 Text (" This clears the in-memory log and exits Service Mode.\n\n Share the log first if you need it." )
207208 },
208209 confirmButton = {
209- TextButton (onClick = {
210+ TextButton (shape = RoundedCornerShape ( 0 .dp), onClick = {
210211 stopConfirm = false
211212 vm.stopDiagnostics()
212213 onDismiss()
213214 }) { Text (" Exit" ) }
214215 },
215216 dismissButton = {
216- TextButton (onClick = { stopConfirm = false }) { Text (" Cancel" ) }
217+ TextButton (shape = RoundedCornerShape ( 0 .dp), onClick = { stopConfirm = false }) { Text (" Cancel" ) }
217218 }
218219 )
219220 }
@@ -308,7 +309,7 @@ private fun CommentRow(vm: WheelDiagnosticsViewModel) {
308309 comment = " "
309310 }
310311 }
311- OutlinedTextField (
312+ OutlinedTextField (shape = RoundedCornerShape ( 0 .dp),
312313 value = comment,
313314 onValueChange = { comment = it },
314315 modifier = Modifier .weight(1f ),
@@ -353,6 +354,7 @@ private fun AttachDataDialog(
353354 }
354355 }
355356 AlertDialog (
357+ shape = RoundedCornerShape (0 .dp),
356358 onDismissRequest = onDismiss,
357359 title = { Text (" Attach data to log" ) },
358360 text = {
@@ -382,15 +384,15 @@ private fun AttachDataDialog(
382384 },
383385 confirmButton = {
384386 val any = selected.values.any { it }
385- TextButton (
387+ TextButton (shape = RoundedCornerShape ( 0 .dp),
386388 enabled = any,
387389 onClick = {
388390 onSubmit(selected.filterValues { it }.keys)
389391 }
390392 ) { Text (" Attach" ) }
391393 },
392394 dismissButton = {
393- TextButton (onClick = onDismiss) { Text (" Cancel" ) }
395+ TextButton (shape = RoundedCornerShape ( 0 .dp), onClick = onDismiss) { Text (" Cancel" ) }
394396 }
395397 )
396398}
@@ -474,7 +476,7 @@ private fun CommandsTab(vm: WheelDiagnosticsViewModel) {
474476
475477 Column (modifier = Modifier .fillMaxSize()) {
476478 Box (modifier = Modifier .padding(top = 8 .dp, start = 4 .dp)) {
477- OutlinedButton (onClick = { familyMenuExpanded = true }) {
479+ OutlinedButton (shape = RoundedCornerShape ( 0 .dp), onClick = { familyMenuExpanded = true }) {
478480 Text (selectedFamily?.displayName ? : " (no families)" )
479481 Spacer (Modifier .width(6 .dp))
480482 Icon (
@@ -523,7 +525,17 @@ private fun CommandsTab(vm: WheelDiagnosticsViewModel) {
523525
524526 val grouped = remember(cmds) { cmds.groupBy { it.category } }
525527
526- LazyColumn (modifier = Modifier .fillMaxSize().padding(top = 8 .dp)) {
528+ // Each command button has a fixed height that fits the label + a
529+ // 2-line description; this keeps the grid uniform regardless of
530+ // description length and lets the heightIn() calculation below land
531+ // exactly on the row's true size (so the last row can't bleed
532+ // outside its slot and get clipped). Bottom contentPadding gives
533+ // the final category breathing room from the dialog edge.
534+ val commandButtonHeight = 80 .dp
535+ LazyColumn (
536+ modifier = Modifier .fillMaxSize().padding(top = 8 .dp),
537+ contentPadding = PaddingValues (bottom = 24 .dp)
538+ ) {
527539 grouped.forEach { (category, list) ->
528540 item {
529541 Text (
@@ -534,22 +546,34 @@ private fun CommandsTab(vm: WheelDiagnosticsViewModel) {
534546 )
535547 }
536548 item {
549+ val rows = (list.size + 2 ) / 3
537550 LazyVerticalGrid (
538551 columns = GridCells .Adaptive (110 .dp),
539552 contentPadding = PaddingValues (2 .dp),
540553 horizontalArrangement = Arrangement .spacedBy(6 .dp),
541554 verticalArrangement = Arrangement .spacedBy(6 .dp),
542555 modifier = Modifier
543556 .fillMaxWidth()
544- .heightIn(max = (((list.size + 2 ) / 3 ) * 64 + 16 ).dp)
557+ // Each row = button height (80) + vertical spacing (6).
558+ // Plus the contentPadding's 2*2dp.
559+ .heightIn(max = (rows * 80 + (rows - 1 ).coerceAtLeast(0 ) * 6 + 4 ).dp)
545560 ) {
546561 items(list) { cmd ->
547562 OutlinedButton (
563+ shape = RoundedCornerShape (0 .dp),
548564 onClick = { vm.fireCommand(cmd) },
549- modifier = Modifier .fillMaxWidth()
565+ modifier = Modifier
566+ .fillMaxWidth()
567+ .height(commandButtonHeight),
568+ contentPadding = PaddingValues (horizontal = 8 .dp, vertical = 4 .dp)
550569 ) {
551570 Column {
552- Text (cmd.label, style = MaterialTheme .typography.labelLarge)
571+ Text (
572+ cmd.label,
573+ style = MaterialTheme .typography.labelLarge,
574+ maxLines = 1 ,
575+ overflow = TextOverflow .Ellipsis
576+ )
553577 Text (
554578 cmd.description,
555579 style = MaterialTheme .typography.labelSmall,
@@ -637,7 +661,7 @@ private fun InspectTab(vm: WheelDiagnosticsViewModel) {
637661 // their options; families with one prefix auto-pick it.
638662 Row (verticalAlignment = Alignment .CenterVertically ) {
639663 Box {
640- OutlinedButton (onClick = { familyMenuExpanded = true }) {
664+ OutlinedButton (shape = RoundedCornerShape ( 0 .dp), onClick = { familyMenuExpanded = true }) {
641665 Text (selectedFamily?.displayName ? : " " )
642666 Spacer (Modifier .width(6 .dp))
643667 Icon (
@@ -666,7 +690,7 @@ private fun InspectTab(vm: WheelDiagnosticsViewModel) {
666690 val familyName = selectedFamily?.displayName ? : " "
667691 val singleOption = types.size <= 1
668692 Box {
669- OutlinedButton (
693+ OutlinedButton (shape = RoundedCornerShape ( 0 .dp),
670694 onClick = { if (! singleOption) menuExpanded = true },
671695 enabled = ! singleOption
672696 ) {
@@ -723,7 +747,7 @@ private fun InspectTab(vm: WheelDiagnosticsViewModel) {
723747 verticalAlignment = Alignment .CenterVertically ,
724748 modifier = Modifier .fillMaxWidth().padding(bottom = 6 .dp)
725749 ) {
726- androidx.compose.material3.OutlinedTextField (
750+ androidx.compose.material3.OutlinedTextField (shape = RoundedCornerShape ( 0 .dp),
727751 value = minFilterText,
728752 onValueChange = { v -> minFilterText = v.filter { it.isDigit() } },
729753 label = { Text (" min" , style = MaterialTheme .typography.labelSmall) },
@@ -732,7 +756,7 @@ private fun InspectTab(vm: WheelDiagnosticsViewModel) {
732756 textStyle = MaterialTheme .typography.bodySmall
733757 )
734758 Spacer (Modifier .width(6 .dp))
735- androidx.compose.material3.OutlinedTextField (
759+ androidx.compose.material3.OutlinedTextField (shape = RoundedCornerShape ( 0 .dp),
736760 value = maxFilterText,
737761 onValueChange = { v -> maxFilterText = v.filter { it.isDigit() } },
738762 label = { Text (" max" , style = MaterialTheme .typography.labelSmall) },
@@ -757,13 +781,12 @@ private fun InspectTab(vm: WheelDiagnosticsViewModel) {
757781 }
758782
759783 // 6-column grid of byte cells. weight(1f) claims the leftover Column
760- // height so the bottom row isn't clipped — the earlier fillMaxSize
761- // approach was reading "as much as you want" rather than "exactly
762- // the leftover after headers", which let the grid request more than
763- // the column had and the last row was cropped.
784+ // height so the bottom row isn't clipped, plus a bottom contentPadding
785+ // so the last row doesn't kiss the dialog edge.
764786 LazyVerticalGrid (
765787 columns = GridCells .Fixed (6 ),
766- modifier = Modifier .weight(1f ).fillMaxWidth()
788+ modifier = Modifier .weight(1f ).fillMaxWidth(),
789+ contentPadding = PaddingValues (bottom = 16 .dp)
767790 ) {
768791 items(latestBytes.size) { off ->
769792 val v = latestBytes[off]
@@ -791,7 +814,7 @@ private fun ByteInspectCell(
791814 .border(
792815 1 .dp,
793816 MaterialTheme .colorScheme.outline.copy(alpha = 0.3f ),
794- RoundedCornerShape (4 .dp)
817+ RoundedCornerShape (0 .dp)
795818 )
796819 .clickable(onClick = onTap)
797820 .padding(4 .dp),
@@ -889,7 +912,7 @@ private fun RawTab(vm: WheelDiagnosticsViewModel) {
889912 )
890913 } else {
891914 Box (modifier = Modifier .padding(top = 4 .dp, bottom = 6 .dp)) {
892- OutlinedButton (onClick = { presetMenuOpen = true }) {
915+ OutlinedButton (shape = RoundedCornerShape ( 0 .dp), onClick = { presetMenuOpen = true }) {
893916 Text (activePresetFamily.displayName)
894917 Spacer (Modifier .width(6 .dp))
895918 Icon (Icons .Default .KeyboardArrowDown , contentDescription = null )
@@ -911,7 +934,7 @@ private fun RawTab(vm: WheelDiagnosticsViewModel) {
911934 val hex = remember(cmd.bytes) {
912935 cmd.bytes.joinToString(" " ) { " %02x" .format(it) }
913936 }
914- AssistChip (
937+ AssistChip (shape = RoundedCornerShape ( 0 .dp),
915938 onClick = { appendBytes(hex) },
916939 label = {
917940 Column {
@@ -940,7 +963,7 @@ private fun RawTab(vm: WheelDiagnosticsViewModel) {
940963 Column (modifier = Modifier .padding(top = 4 .dp)) {
941964 Row (modifier = Modifier .fillMaxWidth(), horizontalArrangement = Arrangement .spacedBy(2 .dp)) {
942965 hexChars.subList(0 , 8 ).forEach { c ->
943- OutlinedButton (
966+ OutlinedButton (shape = RoundedCornerShape ( 0 .dp),
944967 onClick = { appendChar(c) },
945968 modifier = Modifier .weight(1f ).height(40 .dp),
946969 contentPadding = PaddingValues (0 .dp)
@@ -950,7 +973,7 @@ private fun RawTab(vm: WheelDiagnosticsViewModel) {
950973 Spacer (Modifier .height(2 .dp))
951974 Row (modifier = Modifier .fillMaxWidth(), horizontalArrangement = Arrangement .spacedBy(2 .dp)) {
952975 hexChars.subList(8 , 16 ).forEach { c ->
953- OutlinedButton (
976+ OutlinedButton (shape = RoundedCornerShape ( 0 .dp),
954977 onClick = { appendChar(c) },
955978 modifier = Modifier .weight(1f ).height(40 .dp),
956979 contentPadding = PaddingValues (0 .dp)
@@ -959,25 +982,25 @@ private fun RawTab(vm: WheelDiagnosticsViewModel) {
959982 }
960983 Spacer (Modifier .height(2 .dp))
961984 Row (modifier = Modifier .fillMaxWidth(), horizontalArrangement = Arrangement .spacedBy(2 .dp)) {
962- OutlinedButton (
985+ OutlinedButton (shape = RoundedCornerShape ( 0 .dp),
963986 onClick = { appendChar(' ' ) },
964987 modifier = Modifier .weight(1f ).height(40 .dp),
965988 contentPadding = PaddingValues (0 .dp)
966989 ) { Text (" space" , style = MaterialTheme .typography.labelSmall) }
967- OutlinedButton (
990+ OutlinedButton (shape = RoundedCornerShape ( 0 .dp),
968991 onClick = { if (raw.isNotEmpty()) setRaw(raw.dropLast(1 )) },
969992 modifier = Modifier .weight(1f ).height(40 .dp),
970993 contentPadding = PaddingValues (0 .dp)
971994 ) { Icon (Icons .Default .Backspace , contentDescription = " Backspace" , modifier = Modifier .size(18 .dp)) }
972995 // stackVersion read here so recomposition fires on push/pop
973996 @Suppress(" UNUSED_VARIABLE" ) val v = stackVersion
974- OutlinedButton (
997+ OutlinedButton (shape = RoundedCornerShape ( 0 .dp),
975998 onClick = { undo() },
976999 enabled = undoStack.isNotEmpty(),
9771000 modifier = Modifier .weight(1f ).height(40 .dp),
9781001 contentPadding = PaddingValues (0 .dp)
9791002 ) { Icon (Icons .AutoMirrored .Filled .Undo , contentDescription = " Undo" , modifier = Modifier .size(18 .dp)) }
980- OutlinedButton (
1003+ OutlinedButton (shape = RoundedCornerShape ( 0 .dp),
9811004 onClick = { redo() },
9821005 enabled = redoStack.isNotEmpty(),
9831006 modifier = Modifier .weight(1f ).height(40 .dp),
@@ -994,7 +1017,7 @@ private fun RawTab(vm: WheelDiagnosticsViewModel) {
9941017 modifier = Modifier .fillMaxWidth(),
9951018 verticalAlignment = Alignment .CenterVertically
9961019 ) {
997- OutlinedTextField (
1020+ OutlinedTextField (shape = RoundedCornerShape ( 0 .dp),
9981021 value = raw,
9991022 onValueChange = { setRaw(it) },
10001023 modifier = Modifier .weight(1f ),
@@ -1006,11 +1029,11 @@ private fun RawTab(vm: WheelDiagnosticsViewModel) {
10061029 )
10071030 Spacer (Modifier .width(6 .dp))
10081031 Column (verticalArrangement = Arrangement .spacedBy(4 .dp)) {
1009- OutlinedButton (
1032+ OutlinedButton (shape = RoundedCornerShape ( 0 .dp),
10101033 onClick = { setRaw(vm.formatHex(raw)) },
10111034 contentPadding = PaddingValues (horizontal = 8 .dp, vertical = 4 .dp)
10121035 ) { Text (" fmt" ) }
1013- OutlinedButton (
1036+ OutlinedButton (shape = RoundedCornerShape ( 0 .dp),
10141037 onClick = { if (raw.isNotEmpty()) setRaw(" " ) },
10151038 contentPadding = PaddingValues (horizontal = 8 .dp, vertical = 4 .dp)
10161039 ) { Text (" cls" ) }
@@ -1023,17 +1046,28 @@ private fun RawTab(vm: WheelDiagnosticsViewModel) {
10231046 title = " Wrap mode: ${mode.name.lowercase().replace(' _' , ' ' )} " ,
10241047 defaultExpanded = false
10251048 ) {
1049+ // Veteran and Begode commands are literal (fixed opcodes / ASCII
1050+ // strings already including their full wire format), so LITERAL
1051+ // covers them — no separate WRAP_VETERAN / WRAP_BEGODE entries.
1052+ // The segmented row stays scrollable so newer protocols can
1053+ // join without redesigning the layout.
10261054 val modes = listOf (
10271055 WheelDiagnosticsViewModel .WrapMode .LITERAL to " Literal" ,
1028- WheelDiagnosticsViewModel .WrapMode .WRAP_EXTENDED to " Wrap V2" ,
1029- WheelDiagnosticsViewModel .WrapMode .WRAP_V14_SHORT to " Wrap V14"
1056+ WheelDiagnosticsViewModel .WrapMode .WRAP_EXTENDED to " V2 ext" ,
1057+ WheelDiagnosticsViewModel .WrapMode .WRAP_V14_SHORT to " V14" ,
1058+ WheelDiagnosticsViewModel .WrapMode .WRAP_KINGSONG to " KS" ,
1059+ WheelDiagnosticsViewModel .WrapMode .WRAP_NINEBOT_LEGACY to " 9bot" ,
1060+ WheelDiagnosticsViewModel .WrapMode .WRAP_INMOTION_V1 to " V1"
10301061 )
10311062 SingleChoiceSegmentedButtonRow (modifier = Modifier .fillMaxWidth().padding(top = 4 .dp)) {
10321063 modes.forEachIndexed { index, (m, label) ->
10331064 SegmentedButton (
10341065 selected = mode == m,
10351066 onClick = { mode = m },
1036- shape = SegmentedButtonDefaults .itemShape(index = index, count = modes.size)
1067+ // Force square corners on the segmented row to match
1068+ // the rest of Service Mode (the default itemShape
1069+ // gives rounded ends that bypassed the theme).
1070+ shape = RoundedCornerShape (0 .dp)
10371071 ) { Text (label) }
10381072 }
10391073 }
@@ -1049,7 +1083,7 @@ private fun RawTab(vm: WheelDiagnosticsViewModel) {
10491083 // Read-only output field. Filled background + dimmed border so it
10501084 // visually contrasts with the editable Input field above.
10511085 val readOnlyContainer = MaterialTheme .colorScheme.surfaceVariant.copy(alpha = 0.6f )
1052- OutlinedTextField (
1086+ OutlinedTextField (shape = RoundedCornerShape ( 0 .dp),
10531087 value = preview.display,
10541088 onValueChange = {},
10551089 readOnly = true ,
@@ -1073,7 +1107,7 @@ private fun RawTab(vm: WheelDiagnosticsViewModel) {
10731107 // Match fmt/cls in the hex-input row: same OutlinedButton, same
10741108 // compact padding so the dialog doesn't have one giant CTA when
10751109 // the rest of the right column is small chips.
1076- OutlinedButton (
1110+ OutlinedButton (shape = RoundedCornerShape ( 0 .dp),
10771111 onClick = { vm.fireRawHex(raw, mode) },
10781112 enabled = canSend,
10791113 contentPadding = PaddingValues (horizontal = 8 .dp, vertical = 4 .dp)
0 commit comments