From kiln
Comprehensive audit of adaptive layout implementation for foldables, tablets, and responsive design using Material 3 canonical layouts
npx claudepluginhub moonlightbyte/kiln# Adaptive Layouts Audit for Jetpack Compose Comprehensive audit of Jetpack Compose code against Material Design 3 adaptive layout patterns, including WindowSizeClass implementation, canonical layouts (list-detail, feed, supporting pane), foldable device support, and responsive navigation. **2025 Standards**: Material 3 Adaptive 1.2.0 stable, AndroidX.window 1.5.0 with Large/Extra-Large breakpoints, NavigableListDetailPaneScaffold, SupportingPaneScaffold, adaptive strategies (reflow, levitate). ## Instructions Analyze the provided Compose code for compliance with 2025 Material Design 3 ...
Comprehensive audit of Jetpack Compose code against Material Design 3 adaptive layout patterns, including WindowSizeClass implementation, canonical layouts (list-detail, feed, supporting pane), foldable device support, and responsive navigation.
2025 Standards: Material 3 Adaptive 1.2.0 stable, AndroidX.window 1.5.0 with Large/Extra-Large breakpoints, NavigableListDetailPaneScaffold, SupportingPaneScaffold, adaptive strategies (reflow, levitate).
Analyze the provided Compose code for compliance with 2025 Material Design 3 adaptive layout guidelines. Cover:
Reference the android-design skill for Material 3 adaptive design guidelines.
{{#if (or (eq focus "window-sizing") (eq focus "all") (not focus))}}
WindowSizeClass Setup
androidx.compose.material3.adaptive.WindowSizeClasscurrentWindowAdaptiveInfo() to compute window size classwindowSizeClass.windowWidthSizeClass and windowHeightSizeClassContent-Driven Breakpoints
BoxWithConstraints for dynamic width/height measurementsclamp() for smooth scaling{{/if}}
{{#if (or (eq focus "canonical-layouts") (eq focus "all") (not focus))}}
List-Detail Layout
ListDetailPaneScaffold or NavigableListDetailPaneScaffold for list-detail patternFeed Layout (Grid)
LazyVerticalGrid with adaptive column count based on size classGridCells.Adaptive(minSize = X.dp) for fluid column countSupporting Pane Layout
SupportingPaneScaffold for primary + secondary contentAdaptation Strategies (1.2.0+)
{{/if}}
{{#if (or (eq focus "foldables") (eq focus "all") (not focus))}}
Fold State Detection
WindowInfo.displayFeatures from androidx.window.layoutFoldingFeature for hinge location and orientationfoldingFeature.boundsVERTICAL or HORIZONTALfoldingFeature.isSeparatingSafe Zone Layout
Dual-Pane Layouts for Foldables
Foldable Testing & Samsung/Google Specifics
{{/if}}
{{#if (or (eq focus "navigation") (eq focus "all") (not focus))}}
NavigationSuiteScaffold Implementation
NavigationSuiteScaffold for automatic navigation mode switchingNavigation Behavior Across Size Classes
Destination-Specific Navigation
{{/if}}
{{#if (or (eq focus "breakpoints") (eq focus "all") (not focus))}}
Breakpoint Design
Responsive Grid Systems
LazyVerticalGrid or LazyHorizontalGrid with adaptive sizing(availableWidth / itemWidth).toInt().coerceAtLeast(1)Fluid Typography & Spacing
clamp(minSize.sp, preferredSize.sp, maxSize.sp)sp (scale-independent pixels) for text sizesclamp() for smooth scaling{{/if}}
{{#if (or (eq focus "all") (not focus))}}
Tablet-Specific Layouts (840dp+)
Large Tablet Optimization (1200dp+)
Landscape Orientation
{{/if}}
{{#if (or (eq focus "all") (not focus))}}
BoxWithConstraints for measuring available space{{/if}}
{{#if (or (eq focus "all") (not focus))}}
DO NOT:
if (width > 600) without size class)DO:
WindowSizeClass through composable hierarchy (not recompute){{/if}}
{{#if (or (eq focus "all") (not focus))}}
{{/if}}
{{code}}
Generate a structured adaptive layout audit report:
## Adaptive Layout Audit Results: [Component/Screen Name]
### Critical Issues (๐ด)
Issues that break layout or make content inaccessible on tablets/foldables.
Each with: Line number, affected device type(s), description, severity, fix suggestion.
### Important Issues (๐ )
Issues that violate Material 3 adaptive guidelines or degrade UX on large screens.
### Warnings (๐ก)
Layout patterns that work but should be improved for better coverage.
### Suggestions (๐ต)
Polish opportunities for comprehensive adaptive design.
### Device Coverage Summary
- Compact phones (< 600dp): [Fully supported / Partial / Not tested]
- Medium phones (600-839dp): [Fully supported / Partial / Not tested]
- Expanded tablets (840-1199dp): [Fully supported / Partial / Not tested]
- Large tablets (1200-1599dp): [Fully supported / Partial / Not tested]
- Extra-large displays (> 1600dp): [Fully supported / Partial / Not tested]
- Foldables (FLAT/HALF_OPENED/FULLY_OPENED): [Fully supported / Partial / Not applicable]
- Split-screen/multi-window: [Fully supported / Partial / Not applicable]
### WindowSizeClass Implementation Status
- [currentWindowAdaptiveInfo used / Not used]
- Breakpoints covered: [Compact / Medium / Expanded / Large / Extra-Large]
- Size class properly threaded: [Yes / No / Partial]
### Canonical Layouts Used
- List-Detail: [Used / Not applicable / Not implemented]
- Feed/Grid: [Used / Not applicable / Not implemented]
- Supporting Pane: [Used / Not applicable / Not implemented]
### Foldable Support Status
- Fold detection implemented: [Yes / No / Not applicable]
- Safe zones enforced: [Yes / No / Not applicable]
- Hinge handling: [Complete / Partial / Missing]
### Recommended Actions (Priority Order)
1. [Most urgent fix for critical device type]
2. [Second priority]
3. [Third priority]
### Testing Checklist for Implementation
- [ ] Test on Pixel 4 (compact phone - portrait & landscape)
- [ ] Test on Pixel 6 (medium phone - landscape)
- [ ] Test on Pixel Tablet (expanded tablet - portrait & landscape)
- [ ] Test on iPad Pro size (large tablet - landscape)
- [ ] Test on Galaxy Fold or Pixel Fold (foldable - all postures)
- [ ] Test split-screen mode (50/50 split)
- [ ] Test with font scaling at 100%, 150%, 200%
- [ ] Verify no layout jank at breakpoint transitions
- [ ] Verify touch targets (min 48x48dp) on all sizes
- [ ] Verify text contrast maintained at all sizes
- [ ] Performance check (frame rate stable during transitions)
## Adaptive Layout Audit Results: EventListScreen.kt
### Critical Issues (๐ด)
**Line 24**: No WindowSizeClass usage - hardcoded phone layout
```kotlin
// BAD - Always single column regardless of device
Column(modifier = Modifier.width(400.dp)) {
LazyColumn {
items(events) { event ->
EventCard(event = event)
}
}
}
// GOOD - Responsive to screen size
val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
when (windowSizeClass.windowWidthSizeClass) {
WindowWidthSizeClass.Compact -> {
// Single column layout
LazyColumn {
items(events) { event ->
EventCard(event = event)
}
}
}
WindowWidthSizeClass.Medium -> {
// Two column grid
LazyVerticalGrid(columns = GridCells.Fixed(2)) {
items(events) { event ->
EventCard(event = event)
}
}
}
else -> {
// List-detail layout for expanded
ListDetailPaneScaffold(
directive = PaneScaffoldDirective.VerticalSplit,
listPane = {
LazyColumn { /* list */ }
},
detailPane = {
EventDetailPane(selectedEvent)
}
)
}
}
Line 67: Bottom navigation bar always shown - should use NavigationSuiteScaffold
// BAD - Always bottom nav regardless of screen size
Column {
EventListContent()
BottomNavigation { /* tabs */ }
}
// GOOD - Automatic navigation adaptation
NavigationSuiteScaffold(
navigationSuiteItems = {
navigationItem(
icon = Icons.Default.Home,
label = "Explore",
selected = true,
onClick = { /* navigate */ }
)
// more items...
}
) {
EventListContent()
}
Line 45: No foldable device handling
// BAD - Ignores hinge location
LazyVerticalGrid(columns = GridCells.Fixed(2)) {
items(events) { event ->
EventCard(event = event)
}
}
// GOOD - Avoid hinge in dual-pane layout
val displayFeatures = calculateDisplayFeatures(LocalContext.current)
val foldingFeature = displayFeatures.find { it is FoldingFeature }
if (foldingFeature != null && foldingFeature.isSeparating) {
// Dual-pane layout with safe zones
Row {
Box(
modifier = Modifier
.width(foldingFeature.bounds.left.dp)
.fillMaxHeight()
) {
LazyColumn { /* left pane */ }
}
// Gap for hinge
Box(modifier = Modifier.width(foldingFeature.bounds.width().dp))
Box(
modifier = Modifier
.weight(1f)
.fillMaxHeight()
) {
LazyColumn { /* right pane */ }
}
}
} else {
LazyVerticalGrid(columns = GridCells.Fixed(2)) { /* normal layout */ }
}
Line 89: Hardcoded padding breaks tablet layouts
// BAD - Same padding on all screens
EventCard(modifier = Modifier.padding(8.dp))
// GOOD - Responsive padding
val padding = when (windowSizeClass.windowWidthSizeClass) {
WindowWidthSizeClass.Compact -> 8.dp
WindowWidthSizeClass.Medium -> 12.dp
else -> 16.dp
}
EventCard(modifier = Modifier.padding(padding))
Line 120: No text scaling support
// BAD - Fixed font size
Text(
text = event.name,
fontSize = 16.sp,
modifier = Modifier.width(400.dp)
)
// GOOD - Uses theme typography (scales with accessibility)
Text(
text = event.name,
style = MaterialTheme.typography.titleMedium,
maxLines = 2
)
currentWindowAdaptiveInfo() and compute WindowSizeClass at top leveldisplayFeatures and FoldingFeature detection
## Code Examples - Canonical Layouts
### List-Detail Layout (Recommended)
```kotlin
@Composable
fun EventListDetailScreen() {
val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
val selectedEventId = remember { mutableStateOf<String?>(null) }
ListDetailPaneScaffold(
directive = when (windowSizeClass.windowWidthSizeClass) {
WindowWidthSizeClass.Compact -> PaneScaffoldDirective.SinglePane
else -> PaneScaffoldDirective.VerticalSplit
},
listPane = {
LazyColumn {
items(events) { event ->
EventItem(
event = event,
isSelected = selectedEventId.value == event.id,
onClick = { selectedEventId.value = event.id }
)
}
}
},
detailPane = {
selectedEventId.value?.let { id ->
events.find { it.id == id }?.let { event ->
EventDetailPane(event)
}
}
}
)
}
@Composable
fun EventFeedScreen() {
val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
val columnCount = when (windowSizeClass.windowWidthSizeClass) {
WindowWidthSizeClass.Compact -> 1
WindowWidthSizeClass.Medium -> 2
else -> 3
}
LazyVerticalGrid(
columns = GridCells.Fixed(columnCount),
contentPadding = PaddingValues(
horizontal = when (windowSizeClass.windowWidthSizeClass) {
WindowWidthSizeClass.Compact -> 8.dp
WindowWidthSizeClass.Medium -> 12.dp
else -> 16.dp
},
vertical = 8.dp
),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(events) { event ->
EventCard(event = event)
}
}
}
@Composable
fun EventDetailWithSupportingPane() {
val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
SupportingPaneScaffold(
directive = when (windowSizeClass.windowWidthSizeClass) {
WindowWidthSizeClass.Compact -> PaneScaffoldDirective.SinglePane
else -> PaneScaffoldDirective.VerticalSplit
},
mainPane = {
EventDetailContent(event)
},
supportingPane = {
SupportingInformationPane(event)
}
)
}
@Composable
fun FoldableAwareLayout(content: @Composable () -> Unit) {
val displayFeatures = calculateDisplayFeatures(LocalContext.current)
val foldingFeature = displayFeatures.find { it is FoldingFeature } as? FoldingFeature
if (foldingFeature != null && foldingFeature.isSeparating) {
val hingeWidth = foldingFeature.bounds.right - foldingFeature.bounds.left
Row(modifier = Modifier.fillMaxSize()) {
// Left safe zone
Box(
modifier = Modifier
.width(foldingFeature.bounds.left.dp)
.fillMaxHeight()
) {
content()
}
// Hinge spacer
Box(modifier = Modifier.width(hingeWidth.dp))
// Right safe zone
Box(
modifier = Modifier
.weight(1f)
.fillMaxHeight()
) {
content()
}
}
} else {
content()
}
}
@Composable
fun AdaptiveNavigation() {
val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
val navController = rememberNavController()
val currentBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = currentBackStackEntry?.destination
NavigationSuiteScaffold(
navigationSuiteItems = {
navigationItem(
selected = currentDestination?.route == "explore",
onClick = { navController.navigate("explore") },
icon = { Icon(Icons.Default.Home, contentDescription = "Explore") },
label = { Text("Explore") }
)
navigationItem(
selected = currentDestination?.route == "games",
onClick = { navController.navigate("games") },
icon = { Icon(Icons.Default.Games, contentDescription = "My Games") },
label = { Text("My Games") }
)
navigationItem(
selected = currentDestination?.route == "profile",
onClick = { navController.navigate("profile") },
icon = { Icon(Icons.Default.Person, contentDescription = "Profile") },
label = { Text("Profile") }
)
}
) {
NavHost(navController = navController, startDestination = "explore") {
composable("explore") { ExploreScreen() }
composable("games") { MyGamesScreen() }
composable("profile") { ProfileScreen() }
}
}
}