feat: Add dropdown menu for chat options and confirmation dialogs for delete and block actions
This commit is contained in:
@@ -54,6 +54,10 @@ import com.rosetta.messenger.ui.components.AppleEmojiText
|
|||||||
import com.rosetta.messenger.ui.components.AppleEmojiTextField
|
import com.rosetta.messenger.ui.components.AppleEmojiTextField
|
||||||
import com.rosetta.messenger.ui.components.VerifiedBadge
|
import com.rosetta.messenger.ui.components.VerifiedBadge
|
||||||
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
|
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalView
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
@@ -226,6 +230,11 @@ fun ChatDetailScreen(
|
|||||||
var showLogs by remember { mutableStateOf(false) }
|
var showLogs by remember { mutableStateOf(false) }
|
||||||
// val debugLogs by ProtocolManager.debugLogs.collectAsState()
|
// val debugLogs by ProtocolManager.debugLogs.collectAsState()
|
||||||
val debugLogs = remember { emptyList<String>() }
|
val debugLogs = remember { emptyList<String>() }
|
||||||
|
|
||||||
|
// Состояние выпадающего меню
|
||||||
|
var showMenu by remember { mutableStateOf(false) }
|
||||||
|
var showDeleteConfirm by remember { mutableStateOf(false) }
|
||||||
|
var showBlockConfirm by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
// Подключаем к ViewModel
|
// Подключаем к ViewModel
|
||||||
val messages by viewModel.messages.collectAsState()
|
val messages by viewModel.messages.collectAsState()
|
||||||
@@ -446,18 +455,82 @@ fun ChatDetailScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
IconButton(
|
// Кнопка меню с выпадающим списком
|
||||||
onClick = {
|
Box {
|
||||||
keyboardController?.hide()
|
IconButton(
|
||||||
focusManager.clearFocus()
|
onClick = {
|
||||||
/* TODO: More options */
|
keyboardController?.hide()
|
||||||
|
focusManager.clearFocus()
|
||||||
|
showMenu = true
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.MoreVert,
|
||||||
|
contentDescription = "More",
|
||||||
|
tint = textColor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Выпадающее меню
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = showMenu,
|
||||||
|
onDismissRequest = { showMenu = false },
|
||||||
|
modifier = Modifier
|
||||||
|
.background(
|
||||||
|
color = if (isDarkTheme) Color(0xFF2C2C2E) else Color.White,
|
||||||
|
shape = RoundedCornerShape(12.dp)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// Delete Chat
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Delete,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = PrimaryBlue,
|
||||||
|
modifier = Modifier.size(20.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
|
Text(
|
||||||
|
"Delete Chat",
|
||||||
|
color = textColor,
|
||||||
|
fontSize = 16.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
showMenu = false
|
||||||
|
showDeleteConfirm = true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Block User (не показываем для Saved Messages)
|
||||||
|
if (!isSavedMessages) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Block,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = Color(0xFFFF3B30),
|
||||||
|
modifier = Modifier.size(20.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
|
Text(
|
||||||
|
"Block User",
|
||||||
|
color = Color(0xFFFF3B30),
|
||||||
|
fontSize = 16.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
showMenu = false
|
||||||
|
showBlockConfirm = true
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
) {
|
}
|
||||||
Icon(
|
|
||||||
Icons.Default.MoreVert,
|
|
||||||
contentDescription = "More",
|
|
||||||
tint = textColor
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Нижняя линия для разделения
|
// Нижняя линия для разделения
|
||||||
@@ -725,6 +798,79 @@ fun ChatDetailScreen(
|
|||||||
confirmButton = { TextButton(onClick = { showLogs = false }) { Text("Close") } }
|
confirmButton = { TextButton(onClick = { showLogs = false }) { Text("Close") } }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Диалог подтверждения удаления чата
|
||||||
|
if (showDeleteConfirm) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { showDeleteConfirm = false },
|
||||||
|
containerColor = if (isDarkTheme) Color(0xFF2C2C2E) else Color.White,
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
"Delete Chat",
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = textColor
|
||||||
|
)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(
|
||||||
|
"Are you sure you want to delete this chat? This action cannot be undone.",
|
||||||
|
color = secondaryTextColor
|
||||||
|
)
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
showDeleteConfirm = false
|
||||||
|
// TODO: Implement delete chat
|
||||||
|
onBack()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text("Delete", color = Color(0xFFFF3B30))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = { showDeleteConfirm = false }) {
|
||||||
|
Text("Cancel", color = PrimaryBlue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Диалог подтверждения блокировки
|
||||||
|
if (showBlockConfirm) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { showBlockConfirm = false },
|
||||||
|
containerColor = if (isDarkTheme) Color(0xFF2C2C2E) else Color.White,
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
"Block ${user.title.ifEmpty { "User" }}",
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = textColor
|
||||||
|
)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(
|
||||||
|
"Are you sure you want to block this user? They won't be able to send you messages.",
|
||||||
|
color = secondaryTextColor
|
||||||
|
)
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
showBlockConfirm = false
|
||||||
|
// TODO: Implement block user
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text("Block", color = Color(0xFFFF3B30))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = { showBlockConfirm = false }) {
|
||||||
|
Text("Cancel", color = PrimaryBlue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 🚀 Анимация появления сообщения Telegram-style */
|
/** 🚀 Анимация появления сообщения Telegram-style */
|
||||||
@@ -900,26 +1046,53 @@ private fun MessageInputBar(
|
|||||||
placeholderColor: Color
|
placeholderColor: Color
|
||||||
) {
|
) {
|
||||||
var showEmojiPicker by remember { mutableStateOf(false) }
|
var showEmojiPicker by remember { mutableStateOf(false) }
|
||||||
|
// Флаг для запуска закрытия клавиатуры перед открытием emoji picker
|
||||||
|
var pendingEmojiPicker by remember { mutableStateOf(false) }
|
||||||
val keyboardController = LocalSoftwareKeyboardController.current
|
val keyboardController = LocalSoftwareKeyboardController.current
|
||||||
val focusManager = LocalFocusManager.current
|
val focusManager = LocalFocusManager.current
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
// Получаем context и view для гарантированного закрытия клавиатуры
|
||||||
|
val context = LocalContext.current
|
||||||
|
val view = LocalView.current
|
||||||
|
|
||||||
// Состояние отправки
|
// Состояние отправки
|
||||||
val canSend = remember(value) { value.isNotBlank() }
|
val canSend = remember(value) { value.isNotBlank() }
|
||||||
|
|
||||||
// Easing анимации
|
// Easing анимации
|
||||||
val backEasing = CubicBezierEasing(0.34f, 1.56f, 0.64f, 1f)
|
val backEasing = CubicBezierEasing(0.34f, 1.56f, 0.64f, 1f)
|
||||||
|
|
||||||
|
// Функция для гарантированного закрытия клавиатуры через InputMethodManager
|
||||||
|
fun hideKeyboardCompletely() {
|
||||||
|
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
imm.hideSoftInputFromWindow(view.windowToken, 0)
|
||||||
|
focusManager.clearFocus(force = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Эффект для закрытия клавиатуры и открытия emoji picker
|
||||||
|
LaunchedEffect(pendingEmojiPicker) {
|
||||||
|
if (pendingEmojiPicker) {
|
||||||
|
// Гарантированно закрываем клавиатуру через InputMethodManager
|
||||||
|
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
imm.hideSoftInputFromWindow(view.windowToken, 0)
|
||||||
|
focusManager.clearFocus(force = true)
|
||||||
|
// Ждём пока клавиатура закроется
|
||||||
|
delay(200)
|
||||||
|
// Теперь открываем emoji picker
|
||||||
|
showEmojiPicker = true
|
||||||
|
pendingEmojiPicker = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Функция переключения emoji picker
|
// Функция переключения emoji picker
|
||||||
fun toggleEmojiPicker() {
|
fun toggleEmojiPicker() {
|
||||||
if (showEmojiPicker) {
|
if (showEmojiPicker) {
|
||||||
|
// Закрываем emoji picker
|
||||||
showEmojiPicker = false
|
showEmojiPicker = false
|
||||||
} else {
|
} else {
|
||||||
// Сначала скрываем клавиатуру, затем показываем emoji picker
|
// Запускаем процесс: сначала закрыть клавиатуру, потом открыть picker
|
||||||
focusManager.clearFocus(force = true)
|
pendingEmojiPicker = true
|
||||||
keyboardController?.hide()
|
|
||||||
// Небольшая задержка чтобы клавиатура успела закрыться
|
|
||||||
showEmojiPicker = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user