package com.example import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp import com.example.iosKeyboardFocusable /** * I tried this screen with 3 different setups: * - setup 1: A scaffold with no arguments, consuming the innerPadding as padding in the child LazyColumn, and using a small card. * - setup 2: A scaffold with empty window insets, not using the inner padding and windowInsetsPadding set to statusBars, using small cards * - setup 3: The same as setup 2, but with big cards (included a vertical spacer of 40dp between title and text). * * In these setups, my fix is always applied the same way, as a modifier on the Card; iosKeyboardFocusable */ @Composable fun FocusTestScreen() { val cards = remember { (1..20).map { "Card item #$it" } } Scaffold( contentWindowInsets = WindowInsets() // TODO Remove line for setup 1, keep line for setup 2 and 3 ) { innerPadding -> LazyColumn( modifier = Modifier .padding(innerPadding) // TODO Remove line for setup 2 and 3, keep line for setup 1 .fillMaxSize() .windowInsetsPadding(WindowInsets.statusBars) // TODO Remove line for setup 1, keep line for setup 2 and 3 .padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(12.dp), ) { item { Text( text = "Focus Test - Tab through cards", style = MaterialTheme.typography.headlineSmall, modifier = Modifier.padding(top = 16.dp, bottom = 8.dp), ) } items(cards.size) { index -> Card( modifier = Modifier .semantics(mergeDescendants = true) { contentDescription = "Card ${index + 1}: ${cards[index]}" } .iosKeyboardFocusable() // TODO keep line for the "fixed" variant, remove line to see the problem .fillMaxWidth(), shape = RoundedCornerShape(16.dp), elevation = CardDefaults.cardElevation(defaultElevation = 2.dp), onClick = { println("CLICK: Card #${index + 1} clicked") } ) { Row( modifier = Modifier.fillMaxWidth().padding(16.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { Column { Text( text = cards[index], style = MaterialTheme.typography.titleMedium, ) Spacer(modifier = Modifier.height(40.dp)) // TODO remove line for setup 1 and 2, keep line for setup 3 Text( text = "Subtitle for item ${index + 1}", style = MaterialTheme.typography.bodyMedium, ) } Text( text = "→", style = MaterialTheme.typography.titleLarge, ) } } } } } }