fix(chat): make chat input composer more compact and flush (#654)
* fix(chat): make chat input composer more compact and flush * fix(chat): fix chat page compose structure (issue #652) - Resolve EmptyState rendering alongside empty LazyColumn by wrapping in if/else - Flatten nested weight(1f) layout by moving search bar to ChatScreen * style(chat): run ktlint auto-format and fix missing padding import * style(chat): remove unused imports in ChatComposer and ChatMessageList
This commit is contained in:
3 files changed
+127
-105
No files matched your search
@@ -11,6 +11,12 @@ import android.speech.SpeechRecognizer
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.expandVertically
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.shrinkVertically
|
||||||
|
import androidx.compose.foundation.BorderStroke
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
@@ -21,6 +27,7 @@ import androidx.compose.foundation.layout.Spacer
|
|||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.imePadding
|
import androidx.compose.foundation.layout.imePadding
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
@@ -35,6 +42,7 @@ import androidx.compose.material3.MaterialTheme
|
|||||||
import androidx.compose.material3.Snackbar
|
import androidx.compose.material3.Snackbar
|
||||||
import androidx.compose.material3.SnackbarHost
|
import androidx.compose.material3.SnackbarHost
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
@@ -72,6 +80,7 @@ import com.m57.hermescontrol.ui.chat.components.ChatMessageList
|
|||||||
import com.m57.hermescontrol.ui.chat.components.ChatScrollToBottomFab
|
import com.m57.hermescontrol.ui.chat.components.ChatScrollToBottomFab
|
||||||
import com.m57.hermescontrol.ui.chat.components.ReactionHeartsOverlay
|
import com.m57.hermescontrol.ui.chat.components.ReactionHeartsOverlay
|
||||||
import com.m57.hermescontrol.ui.chat.components.ReloginDialog
|
import com.m57.hermescontrol.ui.chat.components.ReloginDialog
|
||||||
|
import com.m57.hermescontrol.ui.chat.components.SearchBarRow
|
||||||
import com.m57.hermescontrol.ui.chat.components.scrollToBottom
|
import com.m57.hermescontrol.ui.chat.components.scrollToBottom
|
||||||
import com.m57.hermescontrol.ui.common.AutoScrollingTitleText
|
import com.m57.hermescontrol.ui.common.AutoScrollingTitleText
|
||||||
import com.m57.hermescontrol.ui.common.CredentialWarningBanner
|
import com.m57.hermescontrol.ui.common.CredentialWarningBanner
|
||||||
@@ -375,6 +384,35 @@ fun ChatScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = state.isSearchActive,
|
||||||
|
enter = expandVertically() + fadeIn(),
|
||||||
|
exit = shrinkVertically() + fadeOut(),
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
color = MaterialTheme.colorScheme.surfaceContainerLow,
|
||||||
|
tonalElevation = 2.dp,
|
||||||
|
border =
|
||||||
|
BorderStroke(
|
||||||
|
width = 1.dp,
|
||||||
|
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.12f),
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Box(modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp)) {
|
||||||
|
SearchBarRow(
|
||||||
|
searchQuery = state.searchQuery,
|
||||||
|
onQueryChange = { viewModel.setSearchQuery(it) },
|
||||||
|
searchMatchCount = state.searchMatchIndices.size,
|
||||||
|
currentMatchIndex = state.currentSearchMatchIndex,
|
||||||
|
onNavigateUp = { viewModel.navigateSearchMatch(-1) },
|
||||||
|
onNavigateDown = { viewModel.navigateSearchMatch(1) },
|
||||||
|
onClose = { viewModel.clearSearch() },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import androidx.compose.foundation.BorderStroke
|
|||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
@@ -26,8 +25,8 @@ import androidx.compose.foundation.layout.width
|
|||||||
import androidx.compose.foundation.layout.widthIn
|
import androidx.compose.foundation.layout.widthIn
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.InsertDriveFile
|
import androidx.compose.material.icons.automirrored.filled.InsertDriveFile
|
||||||
import androidx.compose.material.icons.automirrored.filled.Send
|
import androidx.compose.material.icons.automirrored.filled.Send
|
||||||
@@ -35,17 +34,12 @@ import androidx.compose.material.icons.filled.AttachFile
|
|||||||
import androidx.compose.material.icons.filled.Close
|
import androidx.compose.material.icons.filled.Close
|
||||||
import androidx.compose.material.icons.filled.Mic
|
import androidx.compose.material.icons.filled.Mic
|
||||||
import androidx.compose.material.icons.filled.Stop
|
import androidx.compose.material.icons.filled.Stop
|
||||||
import androidx.compose.material3.ButtonDefaults
|
|
||||||
import androidx.compose.material3.Card
|
|
||||||
import androidx.compose.material3.CardDefaults
|
|
||||||
import androidx.compose.material3.DropdownMenu
|
import androidx.compose.material3.DropdownMenu
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.FilledTonalButton
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.IconButtonDefaults
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedTextField
|
|
||||||
import androidx.compose.material3.OutlinedTextFieldDefaults
|
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -56,6 +50,8 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.focus.onFocusChanged
|
||||||
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.testTag
|
import androidx.compose.ui.platform.testTag
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
@@ -99,28 +95,18 @@ fun ChatInputBar(
|
|||||||
|
|
||||||
// Attachment menu state
|
// Attachment menu state
|
||||||
var showAttachmentMenu by remember { mutableStateOf(false) }
|
var showAttachmentMenu by remember { mutableStateOf(false) }
|
||||||
|
var isFocused by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = true,
|
visible = true,
|
||||||
enter = slideInVertically(initialOffsetY = { it }),
|
enter = slideInVertically(initialOffsetY = { it }),
|
||||||
exit = slideOutVertically(targetOffsetY = { it }),
|
exit = slideOutVertically(targetOffsetY = { it }),
|
||||||
) {
|
) {
|
||||||
Card(
|
Surface(
|
||||||
modifier =
|
modifier = Modifier.fillMaxWidth(),
|
||||||
Modifier
|
color = MaterialTheme.colorScheme.surface,
|
||||||
.fillMaxWidth()
|
tonalElevation = 3.dp,
|
||||||
.padding(horizontal = 4.dp, vertical = 4.dp),
|
shadowElevation = 0.dp,
|
||||||
shape = RoundedCornerShape(24.dp),
|
|
||||||
colors =
|
|
||||||
CardDefaults.cardColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.surface.copy(alpha = 0.85f),
|
|
||||||
),
|
|
||||||
border =
|
|
||||||
BorderStroke(
|
|
||||||
width = 1.dp,
|
|
||||||
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.15f),
|
|
||||||
),
|
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = 6.dp),
|
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
// Commands hidden from the suggestion menu — desktop/CLI-only and
|
// Commands hidden from the suggestion menu — desktop/CLI-only and
|
||||||
@@ -200,7 +186,7 @@ fun ChatInputBar(
|
|||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 8.dp, vertical = 4.dp),
|
.padding(horizontal = 8.dp, vertical = 2.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Box {
|
Box {
|
||||||
@@ -263,39 +249,77 @@ fun ChatInputBar(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OutlinedTextField(
|
val placeholderText =
|
||||||
|
when {
|
||||||
|
!isConnected -> {
|
||||||
|
stringResource(R.string.chat_input_placeholder_not_connected)
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatInputPolicy.showQueuePlaceholder(inputFieldValue.text, isAgentTyping) -> {
|
||||||
|
stringResource(R.string.chat_input_placeholder_queue)
|
||||||
|
}
|
||||||
|
|
||||||
|
isAgentTyping -> {
|
||||||
|
stringResource(R.string.chat_input_placeholder_waiting)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
stringResource(R.string.chat_input_placeholder_type_message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicTextField(
|
||||||
value = inputFieldValue,
|
value = inputFieldValue,
|
||||||
onValueChange = onInputChange,
|
onValueChange = onInputChange,
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.heightIn(min = 36.dp, max = 120.dp)
|
.heightIn(min = 42.dp, max = 120.dp)
|
||||||
.padding(horizontal = 4.dp, vertical = 8.dp)
|
.padding(horizontal = 4.dp, vertical = 4.dp)
|
||||||
|
.onFocusChanged { isFocused = it.isFocused }
|
||||||
.testTag("chat_input"),
|
.testTag("chat_input"),
|
||||||
placeholder = {
|
|
||||||
Text(
|
|
||||||
if (!isConnected) {
|
|
||||||
stringResource(R.string.chat_input_placeholder_not_connected)
|
|
||||||
} else if (ChatInputPolicy.showQueuePlaceholder(inputFieldValue.text, isAgentTyping)) {
|
|
||||||
stringResource(R.string.chat_input_placeholder_queue)
|
|
||||||
} else if (isAgentTyping) {
|
|
||||||
stringResource(R.string.chat_input_placeholder_waiting)
|
|
||||||
} else {
|
|
||||||
stringResource(R.string.chat_input_placeholder_type_message)
|
|
||||||
},
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
enabled = isConnected,
|
enabled = isConnected,
|
||||||
|
textStyle =
|
||||||
|
MaterialTheme.typography.bodyMedium.copy(
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
),
|
||||||
singleLine = false,
|
singleLine = false,
|
||||||
maxLines = 4,
|
maxLines = 4,
|
||||||
shape = RoundedCornerShape(20.dp),
|
cursorBrush = SolidColor(MaterialTheme.colorScheme.primary),
|
||||||
colors =
|
decorationBox = { innerTextField ->
|
||||||
OutlinedTextFieldDefaults.colors(
|
Surface(
|
||||||
focusedBorderColor = MaterialTheme.colorScheme.primary,
|
shape = RoundedCornerShape(18.dp),
|
||||||
unfocusedBorderColor = MaterialTheme.colorScheme.outline.copy(alpha = 0.5f),
|
border =
|
||||||
cursorColor = MaterialTheme.colorScheme.primary,
|
BorderStroke(
|
||||||
),
|
width = if (isFocused) 2.dp else 1.dp,
|
||||||
|
color =
|
||||||
|
if (isFocused) {
|
||||||
|
MaterialTheme.colorScheme.primary
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.outline.copy(alpha = 0.5f)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
color = MaterialTheme.colorScheme.surface,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.padding(horizontal = 12.dp, vertical = 9.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
contentAlignment = Alignment.CenterStart,
|
||||||
|
) {
|
||||||
|
if (inputFieldValue.text.isEmpty()) {
|
||||||
|
Text(
|
||||||
|
text = placeholderText,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
innerTextField()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
@@ -316,15 +340,14 @@ fun ChatInputBar(
|
|||||||
) { buttonState ->
|
) { buttonState ->
|
||||||
when (buttonState) {
|
when (buttonState) {
|
||||||
"mic" -> {
|
"mic" -> {
|
||||||
FilledTonalButton(
|
IconButton(
|
||||||
onClick = onMicTap,
|
onClick = onMicTap,
|
||||||
enabled = isConnected,
|
enabled = isConnected,
|
||||||
|
colors = IconButtonDefaults.filledTonalIconButtonColors(),
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.size(40.dp)
|
.size(36.dp)
|
||||||
.testTag("mic_button"),
|
.testTag("mic_button"),
|
||||||
shape = CircleShape,
|
|
||||||
contentPadding = PaddingValues(0.dp),
|
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Mic,
|
imageVector = Icons.Default.Mic,
|
||||||
@@ -334,37 +357,34 @@ fun ChatInputBar(
|
|||||||
}
|
}
|
||||||
|
|
||||||
"listening" -> {
|
"listening" -> {
|
||||||
FilledTonalButton(
|
IconButton(
|
||||||
onClick = onMicTap,
|
onClick = onMicTap,
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.size(40.dp)
|
.size(36.dp)
|
||||||
.testTag("mic_stop_button"),
|
.testTag("mic_stop_button"),
|
||||||
shape = CircleShape,
|
|
||||||
contentPadding = PaddingValues(0.dp),
|
|
||||||
colors =
|
colors =
|
||||||
ButtonDefaults.filledTonalButtonColors(
|
IconButtonDefaults.filledTonalIconButtonColors(
|
||||||
containerColor = MaterialTheme.colorScheme.error,
|
containerColor = MaterialTheme.colorScheme.error,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onError,
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Stop,
|
imageVector = Icons.Default.Stop,
|
||||||
contentDescription = "Stop listening",
|
contentDescription = "Stop listening",
|
||||||
tint = MaterialTheme.colorScheme.onError,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
FilledTonalButton(
|
IconButton(
|
||||||
onClick = onSend,
|
onClick = onSend,
|
||||||
enabled = canSend,
|
enabled = canSend,
|
||||||
|
colors = IconButtonDefaults.filledTonalIconButtonColors(),
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.size(40.dp)
|
.size(36.dp)
|
||||||
.testTag("send_button"),
|
.testTag("send_button"),
|
||||||
shape = CircleShape,
|
|
||||||
contentPadding = PaddingValues(0.dp),
|
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.AutoMirrored.Filled.Send,
|
imageVector = Icons.AutoMirrored.Filled.Send,
|
||||||
|
|||||||
@@ -1,13 +1,6 @@
|
|||||||
package com.m57.hermescontrol.ui.chat.components
|
package com.m57.hermescontrol.ui.chat.components
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.animation.expandVertically
|
|
||||||
import androidx.compose.animation.fadeIn
|
|
||||||
import androidx.compose.animation.fadeOut
|
|
||||||
import androidx.compose.animation.shrinkVertically
|
|
||||||
import androidx.compose.foundation.BorderStroke
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
@@ -17,8 +10,6 @@ import androidx.compose.foundation.lazy.LazyColumn
|
|||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Surface
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -61,47 +52,20 @@ fun ChatMessageList(
|
|||||||
viewModel: ChatViewModel,
|
viewModel: ChatViewModel,
|
||||||
subagentIndicators: List<SubagentIndicator> = emptyList(),
|
subagentIndicators: List<SubagentIndicator> = emptyList(),
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.fillMaxSize()) {
|
if (messages.isEmpty() && !isLoading) {
|
||||||
// Search bar
|
Box(
|
||||||
AnimatedVisibility(
|
modifier = Modifier.fillMaxSize(),
|
||||||
visible = isSearchActive,
|
contentAlignment = Alignment.Center,
|
||||||
enter = expandVertically() + fadeIn(),
|
|
||||||
exit = shrinkVertically() + fadeOut(),
|
|
||||||
) {
|
) {
|
||||||
Surface(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
color = MaterialTheme.colorScheme.surfaceContainerLow,
|
|
||||||
tonalElevation = 2.dp,
|
|
||||||
border =
|
|
||||||
BorderStroke(
|
|
||||||
width = 1.dp,
|
|
||||||
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.12f),
|
|
||||||
),
|
|
||||||
) {
|
|
||||||
Box(modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp)) {
|
|
||||||
SearchBarRow(
|
|
||||||
searchQuery = searchQuery,
|
|
||||||
onQueryChange = { viewModel.setSearchQuery(it) },
|
|
||||||
searchMatchCount = searchMatchIndices.size,
|
|
||||||
currentMatchIndex = currentSearchMatchIndex,
|
|
||||||
onNavigateUp = { viewModel.navigateSearchMatch(-1) },
|
|
||||||
onNavigateDown = { viewModel.navigateSearchMatch(1) },
|
|
||||||
onClose = { viewModel.clearSearch() },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (messages.isEmpty() && !isLoading) {
|
|
||||||
EmptyState(
|
EmptyState(
|
||||||
title = stringResource(R.string.chat_empty_title),
|
title = stringResource(R.string.chat_empty_title),
|
||||||
subtitle = stringResource(R.string.chat_empty_subtitle),
|
subtitle = stringResource(R.string.chat_empty_subtitle),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
state = listState,
|
state = listState,
|
||||||
modifier = Modifier.fillMaxWidth().weight(1f),
|
modifier = Modifier.fillMaxSize(),
|
||||||
contentPadding = PaddingValues(vertical = 8.dp),
|
contentPadding = PaddingValues(vertical = 8.dp),
|
||||||
) {
|
) {
|
||||||
if (isLoadingOlder) {
|
if (isLoadingOlder) {
|
||||||
|
|||||||
Reference in new issue
Block a user