Anonymous
Der Extractmode kann wie WhatsApp oder Messenger nicht sauber beendet werden. Ein holpriger Übergang
Post
by Anonymous » 03 Jan 2026, 04:52
Ich baue eine Chat-App zum Erlernen der Android-Entwicklung mit Kotlin und Jetpack Compose. Ich versuche, den Extraktionsmodus (Querformat + KeyboardVisible) reibungslos zu beenden. Aber eine nach unten gerichtete Tastatur und eine Textfeldreihe, die den verfügbaren Platz für die Animation und die Neuzusammenstellung des gesamten Bildschirms einnimmt. Ich habe zwei verschiedene Kompositionen für die untere Leiste und eine einzige Komposition für beide Modi ausprobiert. Hier ist der Github-Link:
https://github.com/Akibilies20001/Pokor_Pokor
Hier ist der Code:
Code: Select all
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
@Composable
fun ChatroomScreen(
newMessages: List,
oldMessages: LazyPagingItems,
state: ChatroomState,
onEvent: (ChatroomEvent) -> Unit,
onBackClick: () -> Unit
) {
val keyboardController = LocalSoftwareKeyboardController.current
val focusManager = LocalFocusManager.current
val isImeVisible = WindowInsets.isImeVisible
val configuration = LocalConfiguration.current
val isLandscape = configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
val extractMode = isLandscape && isImeVisible
val focusRequester = remember { FocusRequester() }
LaunchedEffect(isLandscape, isImeVisible) {
if (isImeVisible) {
focusRequester.requestFocus()
}
}
Scaffold(
topBar = {
TopAppBar(
title = { ChatPartner(partner = state.chatPartner) },
navigationIcon = {
IconButton(onClick = { onBackClick() }) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Back",
tint = MaterialTheme.colorScheme.onPrimary
)
}
},
actions = {
//
},
colors =
TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primary
)
)
},
bottomBar = {
ChatBottomAppBar2(
text = state.newMessage,
onSendClick = { onEvent(ChatroomEvent.SendMessage) },
onImageClick = { onEvent(ChatroomEvent.SendImage) },
onTextChange = { onEvent(ChatroomEvent.UpdateNewMessage(it)) },
isLandscape = isLandscape,
focusRequester = focusRequester,
isExtractMode = extractMode,
onDoneClick = {
keyboardController?.hide() // Instant hide here too if using IME Done
focusManager.clearFocus()
}
)
// if (extractMode){
// LandscapeExtractInput(
// text = state.newMessage,
// onTextChange ={onEvent(ChatroomEvent.UpdateNewMessage(it))},
// onDoneClick = {focusManager.clearFocus()},
// focusRequester = focusRequester
// ) }else{
// ChatBottomAppBar(
// text = state.newMessage,
// onSendClick = { onEvent(ChatroomEvent.SendMessage) },
// onImageClick = { onEvent(ChatroomEvent.SendImage) },
// onTextChange = {
// onEvent(ChatroomEvent.UpdateNewMessage(it))
// },
// isLandscape = isLandscape,
// focusRequester = focusRequester
// )
// }
},
modifier = Modifier.fillMaxSize().navigationBarsPadding()
) { paddingValues ->
val listState = rememberLazyListState()
Box(modifier = Modifier.fillMaxSize()) {
Image(
modifier = Modifier.fillMaxSize(),
contentDescription = null,
painter =
painterResource(
id =
if (isSystemInDarkTheme()) {
R.drawable.background_dark
} else {
R.drawable.background_light
}
),
contentScale = ContentScale.Crop
)
Column(modifier = Modifier.fillMaxSize().padding(paddingValues)) {
// Replace this with LazyColumn for chat messages later
LazyColumn(
state = listState,
reverseLayout = true,
modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
item { Spacer(modifier = Modifier.weight(1f, fill = true)) }
items(
items = newMessages,
key = { it.timestamp } // stable key
) { message ->
MessageBox(message, chatOwner = message.sentBy == state.currentUserId)
}
// --- 2. Paging old messages ---
items(
count = oldMessages.itemCount,
key = { index -> oldMessages[index]?.timestamp ?: index }
) { index ->
oldMessages[index]?.let { message ->
MessageBox(message, chatOwner = message.sentBy == state.currentUserId)
}
}
}
}
}
}
}
@Composable
fun ChatBottomAppBar(
text: TextFieldValue,
onSendClick: () -> Unit,
onImageClick: () -> Unit,
onTextChange: (TextFieldValue) -> Unit,
isLandscape: Boolean,
focusRequester: FocusRequester
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier =
Modifier.fillMaxWidth()
.windowInsetsPadding(
if (isLandscape) WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal)
else WindowInsets(0, 0, 0, 0)
)
.imePadding()
.padding(4.dp)
) {
Row(
verticalAlignment = Alignment.Bottom,
modifier =
Modifier.weight(1f)
.clip(RoundedCornerShape(16.dp))
.background(
if (isSystemInDarkTheme()) {
Color.Gray
} else {
Color.White
}
)
) {
IconButton(
onClick = { onImageClick() },
modifier = Modifier.padding(16.dp).size(24.dp),
colors =
IconButtonDefaults.iconButtonColors(
containerColor = Color.Transparent, // No background
contentColor = MaterialTheme.colorScheme.primary // Icon color
)
) {
Icon(Icons.Default.Image, contentDescription = "Image")
}
TextField(
value = text,
onValueChange = { newValue ->
Log.d("ChatDebug", "IME sent: '$newValue' (length = ${newValue.text.length})")
onTextChange(newValue)
},
placeholder = {
Text(
text = "Message",
color = MaterialTheme.colorScheme.primary.copy(alpha = 0.5f)
)
},
maxLines = 6,
minLines = 1,
keyboardOptions =
KeyboardOptions(
capitalization = KeyboardCapitalization.Sentences, // capital after newline
autoCorrectEnabled = false, // prevent IME commit
keyboardType = KeyboardType.Text,
imeAction = ImeAction.Default // allow newline properly
),
keyboardActions =
KeyboardActions(onAny = { /* no-op: prevents IME overriding on newline */}),
modifier =
Modifier.fillMaxWidth().focusRequester(focusRequester).heightIn(min = 48.dp),
shape = RoundedCornerShape(25.dp),
colors =
TextFieldDefaults.colors(
focusedContainerColor = Color.Transparent,
unfocusedContainerColor = Color.Transparent,
disabledContainerColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
disabledIndicatorColor = Color.Transparent,
cursorColor = MaterialTheme.colorScheme.primary,
)
)
}
Spacer(modifier = Modifier.width(8.dp))
IconButton(
onClick = { onSendClick() },
shape = RoundedCornerShape(100.dp),
modifier = Modifier.size(48.dp),
colors =
IconButtonDefaults.iconButtonColors(
containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.onPrimary
)
) {
Icon(imageVector = Icons.AutoMirrored.Filled.Send, contentDescription = "Send")
}
}
}
@Composable
fun LandscapeExtractInput(
text: TextFieldValue,
onTextChange: (TextFieldValue) -> Unit,
onDoneClick: () -> Unit,
focusRequester: FocusRequester
) {
Box(
modifier =
Modifier.fillMaxWidth()
.windowInsetsPadding(WindowInsets.safeDrawing)
.imePadding()
.background(MaterialTheme.colorScheme.background)
) {
Row(
modifier = Modifier.fillMaxSize().padding(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
TextField(
value = text,
onValueChange = { newValue ->
Log.d("ChatDebug", "IME sent: '$newValue' (length = ${newValue.text.length})")
onTextChange(newValue)
},
modifier =
Modifier.weight(1f)
.clip(RoundedCornerShape(24.dp))
.background(MaterialTheme.colorScheme.surfaceVariant)
.focusRequester(focusRequester),
colors =
TextFieldDefaults.colors(
focusedContainerColor = MaterialTheme.colorScheme.surfaceVariant,
unfocusedContainerColor = MaterialTheme.colorScheme.surfaceVariant,
focusedTextColor = MaterialTheme.colorScheme.onSurfaceVariant,
unfocusedTextColor = MaterialTheme.colorScheme.onSurfaceVariant,
cursorColor = MaterialTheme.colorScheme.primary,
// Remove underline
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent
),
placeholder = {
Text(
"Type a message...",
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f)
)
},
// Allow up to 4 lines
maxLines = 4,
minLines = 1,
keyboardOptions =
KeyboardOptions(
capitalization = KeyboardCapitalization.Sentences, // capital after newline
autoCorrectEnabled = false, // prevent IME commit
keyboardType = KeyboardType.Text,
imeAction = ImeAction.Default // allow newline properly
),
)
Spacer(modifier = Modifier.width(8.dp))
Button(
onClick = { onDoneClick() },
shape = RoundedCornerShape(24.dp),
contentPadding = PaddingValues(8.dp),
colors =
ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary),
) {
Text(
text = "DONE",
style = MaterialTheme.typography.bodySmall,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onPrimary
)
}
}
}
}
@Composable
fun ChatBottomAppBar2(
text: TextFieldValue,
onSendClick: () -> Unit,
onImageClick: () -> Unit,
onTextChange: (TextFieldValue) -> Unit,
isLandscape: Boolean,
isExtractMode: Boolean,
onDoneClick: () -> Unit,
focusRequester: FocusRequester
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier =
if (isExtractMode) {
Modifier.fillMaxWidth()
.windowInsetsPadding(WindowInsets.safeDrawing)
.background(MaterialTheme.colorScheme.background)
} else {
Modifier.fillMaxWidth()
.windowInsetsPadding(
if (isLandscape) WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal)
else WindowInsets(0, 0, 0, 0)
)
.imePadding()
.padding(4.dp)
}
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier =
if (isExtractMode) {
Modifier.fillMaxSize().padding(8.dp)
} else {
Modifier.weight(1f)
.clip(RoundedCornerShape(16.dp))
.background(
if (isSystemInDarkTheme()) {
Color.Gray
} else {
Color.White
}
)
}
) {
if (!isExtractMode) {
IconButton(
onClick = { onImageClick() },
modifier = Modifier.padding(16.dp).size(24.dp),
colors =
IconButtonDefaults.iconButtonColors(
containerColor = Color.Transparent, // No background
contentColor = MaterialTheme.colorScheme.primary // Icon color
)
) {
Icon(Icons.Default.Image, contentDescription = "Image")
}
}
TextField(
value = text,
onValueChange = { newValue ->
Log.d("ChatDebug", "IME sent: '$newValue' (length = ${newValue.text.length})")
onTextChange(newValue)
},
placeholder = {
Text(
text = "Message",
color = MaterialTheme.colorScheme.primary.copy(alpha = 0.5f)
)
},
maxLines = 6,
minLines = 1,
keyboardOptions =
KeyboardOptions(
capitalization = KeyboardCapitalization.Sentences, // capital after newline
autoCorrectEnabled = false, // prevent IME commit
keyboardType = KeyboardType.Text,
imeAction = ImeAction.Default // allow newline properly
),
keyboardActions =
KeyboardActions(onAny = { /* no-op: prevents IME overriding on newline */}),
modifier = Modifier.weight(1f).focusRequester(focusRequester).heightIn(min = 48.dp),
shape = RoundedCornerShape(25.dp),
colors =
TextFieldDefaults.colors(
focusedContainerColor = Color.Transparent,
unfocusedContainerColor = Color.Transparent,
disabledContainerColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
disabledIndicatorColor = Color.Transparent,
cursorColor = MaterialTheme.colorScheme.primary,
)
)
if (isExtractMode) {
Button(
onClick = { onDoneClick() },
shape = RoundedCornerShape(24.dp),
contentPadding = PaddingValues(8.dp),
colors =
ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary
),
) {
Text(
text = "DONE",
style = MaterialTheme.typography.bodySmall,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onPrimary
)
}
}
}
Spacer(modifier = Modifier.width(8.dp))
if (!isExtractMode) {
IconButton(
onClick = { onSendClick() },
shape = RoundedCornerShape(100.dp),
modifier = Modifier.size(48.dp),
colors =
IconButtonDefaults.iconButtonColors(
containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.onPrimary
)
) {
Icon(imageVector = Icons.AutoMirrored.Filled.Send, contentDescription = "Send")
}
}
}
}
1767412362
Anonymous
Ich baue eine Chat-App zum Erlernen der Android-Entwicklung mit Kotlin und Jetpack Compose. Ich versuche, den Extraktionsmodus (Querformat + KeyboardVisible) reibungslos zu beenden. Aber eine nach unten gerichtete Tastatur und eine Textfeldreihe, die den verfügbaren Platz für die Animation und die Neuzusammenstellung des gesamten Bildschirms einnimmt. Ich habe zwei verschiedene Kompositionen für die untere Leiste und eine einzige Komposition für beide Modi ausprobiert. Hier ist der Github-Link: https://github.com/Akibilies20001/Pokor_Pokor Hier ist der Code: [code]@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) @Composable fun ChatroomScreen( newMessages: List, oldMessages: LazyPagingItems, state: ChatroomState, onEvent: (ChatroomEvent) -> Unit, onBackClick: () -> Unit ) { val keyboardController = LocalSoftwareKeyboardController.current val focusManager = LocalFocusManager.current val isImeVisible = WindowInsets.isImeVisible val configuration = LocalConfiguration.current val isLandscape = configuration.orientation == Configuration.ORIENTATION_LANDSCAPE val extractMode = isLandscape && isImeVisible val focusRequester = remember { FocusRequester() } LaunchedEffect(isLandscape, isImeVisible) { if (isImeVisible) { focusRequester.requestFocus() } } Scaffold( topBar = { TopAppBar( title = { ChatPartner(partner = state.chatPartner) }, navigationIcon = { IconButton(onClick = { onBackClick() }) { Icon( imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back", tint = MaterialTheme.colorScheme.onPrimary ) } }, actions = { // }, colors = TopAppBarDefaults.topAppBarColors( containerColor = MaterialTheme.colorScheme.primary ) ) }, bottomBar = { ChatBottomAppBar2( text = state.newMessage, onSendClick = { onEvent(ChatroomEvent.SendMessage) }, onImageClick = { onEvent(ChatroomEvent.SendImage) }, onTextChange = { onEvent(ChatroomEvent.UpdateNewMessage(it)) }, isLandscape = isLandscape, focusRequester = focusRequester, isExtractMode = extractMode, onDoneClick = { keyboardController?.hide() // Instant hide here too if using IME Done focusManager.clearFocus() } ) // if (extractMode){ // LandscapeExtractInput( // text = state.newMessage, // onTextChange ={onEvent(ChatroomEvent.UpdateNewMessage(it))}, // onDoneClick = {focusManager.clearFocus()}, // focusRequester = focusRequester // ) }else{ // ChatBottomAppBar( // text = state.newMessage, // onSendClick = { onEvent(ChatroomEvent.SendMessage) }, // onImageClick = { onEvent(ChatroomEvent.SendImage) }, // onTextChange = { // onEvent(ChatroomEvent.UpdateNewMessage(it)) // }, // isLandscape = isLandscape, // focusRequester = focusRequester // ) // } }, modifier = Modifier.fillMaxSize().navigationBarsPadding() ) { paddingValues -> val listState = rememberLazyListState() Box(modifier = Modifier.fillMaxSize()) { Image( modifier = Modifier.fillMaxSize(), contentDescription = null, painter = painterResource( id = if (isSystemInDarkTheme()) { R.drawable.background_dark } else { R.drawable.background_light } ), contentScale = ContentScale.Crop ) Column(modifier = Modifier.fillMaxSize().padding(paddingValues)) { // Replace this with LazyColumn for chat messages later LazyColumn( state = listState, reverseLayout = true, modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp), verticalArrangement = Arrangement.spacedBy(12.dp) ) { item { Spacer(modifier = Modifier.weight(1f, fill = true)) } items( items = newMessages, key = { it.timestamp } // stable key ) { message -> MessageBox(message, chatOwner = message.sentBy == state.currentUserId) } // --- 2. Paging old messages --- items( count = oldMessages.itemCount, key = { index -> oldMessages[index]?.timestamp ?: index } ) { index -> oldMessages[index]?.let { message -> MessageBox(message, chatOwner = message.sentBy == state.currentUserId) } } } } } } } @Composable fun ChatBottomAppBar( text: TextFieldValue, onSendClick: () -> Unit, onImageClick: () -> Unit, onTextChange: (TextFieldValue) -> Unit, isLandscape: Boolean, focusRequester: FocusRequester ) { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth() .windowInsetsPadding( if (isLandscape) WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal) else WindowInsets(0, 0, 0, 0) ) .imePadding() .padding(4.dp) ) { Row( verticalAlignment = Alignment.Bottom, modifier = Modifier.weight(1f) .clip(RoundedCornerShape(16.dp)) .background( if (isSystemInDarkTheme()) { Color.Gray } else { Color.White } ) ) { IconButton( onClick = { onImageClick() }, modifier = Modifier.padding(16.dp).size(24.dp), colors = IconButtonDefaults.iconButtonColors( containerColor = Color.Transparent, // No background contentColor = MaterialTheme.colorScheme.primary // Icon color ) ) { Icon(Icons.Default.Image, contentDescription = "Image") } TextField( value = text, onValueChange = { newValue -> Log.d("ChatDebug", "IME sent: '$newValue' (length = ${newValue.text.length})") onTextChange(newValue) }, placeholder = { Text( text = "Message", color = MaterialTheme.colorScheme.primary.copy(alpha = 0.5f) ) }, maxLines = 6, minLines = 1, keyboardOptions = KeyboardOptions( capitalization = KeyboardCapitalization.Sentences, // capital after newline autoCorrectEnabled = false, // prevent IME commit keyboardType = KeyboardType.Text, imeAction = ImeAction.Default // allow newline properly ), keyboardActions = KeyboardActions(onAny = { /* no-op: prevents IME overriding on newline */}), modifier = Modifier.fillMaxWidth().focusRequester(focusRequester).heightIn(min = 48.dp), shape = RoundedCornerShape(25.dp), colors = TextFieldDefaults.colors( focusedContainerColor = Color.Transparent, unfocusedContainerColor = Color.Transparent, disabledContainerColor = Color.Transparent, focusedIndicatorColor = Color.Transparent, unfocusedIndicatorColor = Color.Transparent, disabledIndicatorColor = Color.Transparent, cursorColor = MaterialTheme.colorScheme.primary, ) ) } Spacer(modifier = Modifier.width(8.dp)) IconButton( onClick = { onSendClick() }, shape = RoundedCornerShape(100.dp), modifier = Modifier.size(48.dp), colors = IconButtonDefaults.iconButtonColors( containerColor = MaterialTheme.colorScheme.primary, contentColor = MaterialTheme.colorScheme.onPrimary ) ) { Icon(imageVector = Icons.AutoMirrored.Filled.Send, contentDescription = "Send") } } } @Composable fun LandscapeExtractInput( text: TextFieldValue, onTextChange: (TextFieldValue) -> Unit, onDoneClick: () -> Unit, focusRequester: FocusRequester ) { Box( modifier = Modifier.fillMaxWidth() .windowInsetsPadding(WindowInsets.safeDrawing) .imePadding() .background(MaterialTheme.colorScheme.background) ) { Row( modifier = Modifier.fillMaxSize().padding(8.dp), verticalAlignment = Alignment.CenterVertically ) { TextField( value = text, onValueChange = { newValue -> Log.d("ChatDebug", "IME sent: '$newValue' (length = ${newValue.text.length})") onTextChange(newValue) }, modifier = Modifier.weight(1f) .clip(RoundedCornerShape(24.dp)) .background(MaterialTheme.colorScheme.surfaceVariant) .focusRequester(focusRequester), colors = TextFieldDefaults.colors( focusedContainerColor = MaterialTheme.colorScheme.surfaceVariant, unfocusedContainerColor = MaterialTheme.colorScheme.surfaceVariant, focusedTextColor = MaterialTheme.colorScheme.onSurfaceVariant, unfocusedTextColor = MaterialTheme.colorScheme.onSurfaceVariant, cursorColor = MaterialTheme.colorScheme.primary, // Remove underline focusedIndicatorColor = Color.Transparent, unfocusedIndicatorColor = Color.Transparent ), placeholder = { Text( "Type a message...", color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f) ) }, // Allow up to 4 lines maxLines = 4, minLines = 1, keyboardOptions = KeyboardOptions( capitalization = KeyboardCapitalization.Sentences, // capital after newline autoCorrectEnabled = false, // prevent IME commit keyboardType = KeyboardType.Text, imeAction = ImeAction.Default // allow newline properly ), ) Spacer(modifier = Modifier.width(8.dp)) Button( onClick = { onDoneClick() }, shape = RoundedCornerShape(24.dp), contentPadding = PaddingValues(8.dp), colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary), ) { Text( text = "DONE", style = MaterialTheme.typography.bodySmall, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onPrimary ) } } } } @Composable fun ChatBottomAppBar2( text: TextFieldValue, onSendClick: () -> Unit, onImageClick: () -> Unit, onTextChange: (TextFieldValue) -> Unit, isLandscape: Boolean, isExtractMode: Boolean, onDoneClick: () -> Unit, focusRequester: FocusRequester ) { Row( verticalAlignment = Alignment.CenterVertically, modifier = if (isExtractMode) { Modifier.fillMaxWidth() .windowInsetsPadding(WindowInsets.safeDrawing) .background(MaterialTheme.colorScheme.background) } else { Modifier.fillMaxWidth() .windowInsetsPadding( if (isLandscape) WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal) else WindowInsets(0, 0, 0, 0) ) .imePadding() .padding(4.dp) } ) { Row( verticalAlignment = Alignment.CenterVertically, modifier = if (isExtractMode) { Modifier.fillMaxSize().padding(8.dp) } else { Modifier.weight(1f) .clip(RoundedCornerShape(16.dp)) .background( if (isSystemInDarkTheme()) { Color.Gray } else { Color.White } ) } ) { if (!isExtractMode) { IconButton( onClick = { onImageClick() }, modifier = Modifier.padding(16.dp).size(24.dp), colors = IconButtonDefaults.iconButtonColors( containerColor = Color.Transparent, // No background contentColor = MaterialTheme.colorScheme.primary // Icon color ) ) { Icon(Icons.Default.Image, contentDescription = "Image") } } TextField( value = text, onValueChange = { newValue -> Log.d("ChatDebug", "IME sent: '$newValue' (length = ${newValue.text.length})") onTextChange(newValue) }, placeholder = { Text( text = "Message", color = MaterialTheme.colorScheme.primary.copy(alpha = 0.5f) ) }, maxLines = 6, minLines = 1, keyboardOptions = KeyboardOptions( capitalization = KeyboardCapitalization.Sentences, // capital after newline autoCorrectEnabled = false, // prevent IME commit keyboardType = KeyboardType.Text, imeAction = ImeAction.Default // allow newline properly ), keyboardActions = KeyboardActions(onAny = { /* no-op: prevents IME overriding on newline */}), modifier = Modifier.weight(1f).focusRequester(focusRequester).heightIn(min = 48.dp), shape = RoundedCornerShape(25.dp), colors = TextFieldDefaults.colors( focusedContainerColor = Color.Transparent, unfocusedContainerColor = Color.Transparent, disabledContainerColor = Color.Transparent, focusedIndicatorColor = Color.Transparent, unfocusedIndicatorColor = Color.Transparent, disabledIndicatorColor = Color.Transparent, cursorColor = MaterialTheme.colorScheme.primary, ) ) if (isExtractMode) { Button( onClick = { onDoneClick() }, shape = RoundedCornerShape(24.dp), contentPadding = PaddingValues(8.dp), colors = ButtonDefaults.buttonColors( containerColor = MaterialTheme.colorScheme.primary ), ) { Text( text = "DONE", style = MaterialTheme.typography.bodySmall, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onPrimary ) } } } Spacer(modifier = Modifier.width(8.dp)) if (!isExtractMode) { IconButton( onClick = { onSendClick() }, shape = RoundedCornerShape(100.dp), modifier = Modifier.size(48.dp), colors = IconButtonDefaults.iconButtonColors( containerColor = MaterialTheme.colorScheme.primary, contentColor = MaterialTheme.colorScheme.onPrimary ) ) { Icon(imageVector = Icons.AutoMirrored.Filled.Send, contentDescription = "Send") } } } } [/code]