feat: Refactor MessageInputBar to improve emoji picker integration and UI responsiveness
This commit is contained in:
@@ -19,8 +19,10 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
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.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.SolidColor
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
@@ -107,7 +109,7 @@ fun ChatDetailScreen(
|
|||||||
// Кастомный TopAppBar для чата
|
// Кастомный TopAppBar для чата
|
||||||
Surface(
|
Surface(
|
||||||
color = backgroundColor,
|
color = backgroundColor,
|
||||||
shadowElevation = 4.dp
|
shadowElevation = 0.dp
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -209,7 +211,6 @@ fun ChatDetailScreen(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(paddingValues)
|
.padding(paddingValues)
|
||||||
.imePadding() // Весь контент поднимается с клавиатурой
|
|
||||||
) {
|
) {
|
||||||
// Список сообщений
|
// Список сообщений
|
||||||
Box(
|
Box(
|
||||||
@@ -407,29 +408,23 @@ private fun MessageInputBar(
|
|||||||
// Состояние эмодзи пикера
|
// Состояние эмодзи пикера
|
||||||
var showEmojiPicker by remember { mutableStateOf(false) }
|
var showEmojiPicker by remember { mutableStateOf(false) }
|
||||||
val keyboardController = LocalSoftwareKeyboardController.current
|
val keyboardController = LocalSoftwareKeyboardController.current
|
||||||
|
val focusManager = LocalFocusManager.current
|
||||||
// Скрываем клавиатуру когда открыт эмодзи пикер
|
|
||||||
LaunchedEffect(showEmojiPicker) {
|
|
||||||
if (showEmojiPicker) {
|
|
||||||
keyboardController?.hide()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Цвета для glass morphism эффекта
|
// Цвета для glass morphism эффекта
|
||||||
val glassBackground = if (isDarkTheme)
|
val glassBackground = if (isDarkTheme)
|
||||||
Color(0xFF1A1A1A).copy(alpha = 0.85f)
|
Color(0xFF1A1A1A).copy(alpha = 0.95f)
|
||||||
else
|
else
|
||||||
Color(0xFFFFFFFF).copy(alpha = 0.9f)
|
Color(0xFFFFFFFF).copy(alpha = 0.95f)
|
||||||
|
|
||||||
val inputGlass = if (isDarkTheme)
|
val inputGlass = if (isDarkTheme)
|
||||||
Color(0xFF2C2C2E).copy(alpha = 0.7f)
|
Color(0xFF2C2C2E).copy(alpha = 0.8f)
|
||||||
else
|
else
|
||||||
Color(0xFFF2F2F7).copy(alpha = 0.85f)
|
Color(0xFFF2F2F7).copy(alpha = 0.9f)
|
||||||
|
|
||||||
val inputBorder = if (isDarkTheme)
|
val inputBorder = if (isDarkTheme)
|
||||||
Color(0xFFFFFFFF).copy(alpha = 0.15f)
|
Color(0xFFFFFFFF).copy(alpha = 0.12f)
|
||||||
else
|
else
|
||||||
Color(0xFF000000).copy(alpha = 0.08f)
|
Color(0xFF000000).copy(alpha = 0.06f)
|
||||||
|
|
||||||
val iconColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
|
val iconColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
|
||||||
|
|
||||||
@@ -458,33 +453,11 @@ private fun MessageInputBar(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.imePadding() // Только инпут поднимается с клавиатурой
|
||||||
) {
|
) {
|
||||||
// Эмодзи пикер (показывается над инпутом)
|
// Основной контейнер инпута
|
||||||
AnimatedVisibility(
|
|
||||||
visible = showEmojiPicker,
|
|
||||||
enter = slideInVertically(
|
|
||||||
initialOffsetY = { it },
|
|
||||||
animationSpec = spring(
|
|
||||||
dampingRatio = Spring.DampingRatioMediumBouncy,
|
|
||||||
stiffness = Spring.StiffnessMediumLow
|
|
||||||
)
|
|
||||||
) + fadeIn(),
|
|
||||||
exit = slideOutVertically(
|
|
||||||
targetOffsetY = { it },
|
|
||||||
animationSpec = tween(200)
|
|
||||||
) + fadeOut()
|
|
||||||
) {
|
|
||||||
EmojiPickerPanel(
|
|
||||||
isDarkTheme = isDarkTheme,
|
|
||||||
onEmojiSelected = { emoji ->
|
|
||||||
onValueChange(value + emoji)
|
|
||||||
},
|
|
||||||
onClose = { showEmojiPicker = false }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Основной контейнер
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -493,9 +466,7 @@ private fun MessageInputBar(
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 12.dp, vertical = 8.dp)
|
.padding(horizontal = 12.dp, vertical = 8.dp),
|
||||||
.padding(bottom = 4.dp)
|
|
||||||
.navigationBarsPadding(),
|
|
||||||
verticalAlignment = Alignment.Bottom
|
verticalAlignment = Alignment.Bottom
|
||||||
) {
|
) {
|
||||||
// Единый стеклянный контейнер для всего инпута
|
// Единый стеклянный контейнер для всего инпута
|
||||||
@@ -514,9 +485,9 @@ private fun MessageInputBar(
|
|||||||
.background(
|
.background(
|
||||||
brush = Brush.verticalGradient(
|
brush = Brush.verticalGradient(
|
||||||
colors = listOf(
|
colors = listOf(
|
||||||
Color.White.copy(alpha = if (isDarkTheme) 0.06f else 0.4f),
|
Color.White.copy(alpha = if (isDarkTheme) 0.05f else 0.3f),
|
||||||
Color.Transparent,
|
Color.Transparent,
|
||||||
Color.Black.copy(alpha = if (isDarkTheme) 0.03f else 0.02f)
|
Color.Black.copy(alpha = if (isDarkTheme) 0.02f else 0.01f)
|
||||||
),
|
),
|
||||||
startY = 0f,
|
startY = 0f,
|
||||||
endY = 80f
|
endY = 80f
|
||||||
@@ -532,7 +503,14 @@ private fun MessageInputBar(
|
|||||||
) {
|
) {
|
||||||
// Кнопка смайликов (слева внутри инпута)
|
// Кнопка смайликов (слева внутри инпута)
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = { showEmojiPicker = !showEmojiPicker },
|
onClick = {
|
||||||
|
if (showEmojiPicker) {
|
||||||
|
showEmojiPicker = false
|
||||||
|
} else {
|
||||||
|
keyboardController?.hide()
|
||||||
|
showEmojiPicker = true
|
||||||
|
}
|
||||||
|
},
|
||||||
modifier = Modifier.size(36.dp)
|
modifier = Modifier.size(36.dp)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -547,7 +525,10 @@ private fun MessageInputBar(
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.padding(vertical = 8.dp),
|
.padding(vertical = 8.dp)
|
||||||
|
.clickable {
|
||||||
|
showEmojiPicker = false
|
||||||
|
},
|
||||||
contentAlignment = Alignment.CenterStart
|
contentAlignment = Alignment.CenterStart
|
||||||
) {
|
) {
|
||||||
BasicTextField(
|
BasicTextField(
|
||||||
@@ -558,7 +539,13 @@ private fun MessageInputBar(
|
|||||||
fontSize = 16.sp
|
fontSize = 16.sp
|
||||||
),
|
),
|
||||||
cursorBrush = SolidColor(PrimaryBlue),
|
cursorBrush = SolidColor(PrimaryBlue),
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.onFocusChanged { focusState ->
|
||||||
|
if (focusState.isFocused) {
|
||||||
|
showEmojiPicker = false
|
||||||
|
}
|
||||||
|
},
|
||||||
maxLines = 5,
|
maxLines = 5,
|
||||||
decorationBox = { innerTextField ->
|
decorationBox = { innerTextField ->
|
||||||
Box(
|
Box(
|
||||||
@@ -645,6 +632,35 @@ private fun MessageInputBar(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Эмодзи пикер (показывается под инпутом, заменяя клавиатуру)
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = showEmojiPicker,
|
||||||
|
enter = expandVertically(
|
||||||
|
expandFrom = Alignment.Top,
|
||||||
|
animationSpec = spring(
|
||||||
|
dampingRatio = Spring.DampingRatioMediumBouncy,
|
||||||
|
stiffness = Spring.StiffnessMediumLow
|
||||||
|
)
|
||||||
|
) + fadeIn(animationSpec = tween(150)),
|
||||||
|
exit = shrinkVertically(
|
||||||
|
shrinkTowards = Alignment.Top,
|
||||||
|
animationSpec = tween(200)
|
||||||
|
) + fadeOut(animationSpec = tween(100))
|
||||||
|
) {
|
||||||
|
EmojiPickerPanel(
|
||||||
|
isDarkTheme = isDarkTheme,
|
||||||
|
onEmojiSelected = { emoji ->
|
||||||
|
onValueChange(value + emoji)
|
||||||
|
},
|
||||||
|
onClose = { showEmojiPicker = false }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spacer для navigation bar когда эмодзи пикер НЕ открыт
|
||||||
|
if (!showEmojiPicker) {
|
||||||
|
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user