feat: Update dialog deletion logic to use sorted dialog keys for message removal
This commit is contained in:
@@ -212,6 +212,9 @@ fun ChatsListScreen(
|
|||||||
var dialogToBlock by remember { mutableStateOf<DialogUiModel?>(null) }
|
var dialogToBlock by remember { mutableStateOf<DialogUiModel?>(null) }
|
||||||
var dialogToUnblock by remember { mutableStateOf<DialogUiModel?>(null) }
|
var dialogToUnblock by remember { mutableStateOf<DialogUiModel?>(null) }
|
||||||
|
|
||||||
|
// Trigger для обновления статуса блокировки
|
||||||
|
var blocklistUpdateTrigger by remember { mutableStateOf(0) }
|
||||||
|
|
||||||
// Dev console dialog - commented out for now
|
// Dev console dialog - commented out for now
|
||||||
/*
|
/*
|
||||||
if (showDevConsole) {
|
if (showDevConsole) {
|
||||||
@@ -520,7 +523,7 @@ fun ChatsListScreen(
|
|||||||
val isSavedMessages = dialog.opponentKey == accountPublicKey
|
val isSavedMessages = dialog.opponentKey == accountPublicKey
|
||||||
// Check if user is blocked
|
// Check if user is blocked
|
||||||
var isBlocked by remember { mutableStateOf(false) }
|
var isBlocked by remember { mutableStateOf(false) }
|
||||||
LaunchedEffect(dialog.opponentKey) {
|
LaunchedEffect(dialog.opponentKey, blocklistUpdateTrigger) {
|
||||||
isBlocked = chatsViewModel.isUserBlocked(dialog.opponentKey)
|
isBlocked = chatsViewModel.isUserBlocked(dialog.opponentKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -577,37 +580,54 @@ fun ChatsListScreen(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
val displayName = dialog.opponentTitle.ifEmpty { dialog.opponentKey.take(8) }
|
Column(
|
||||||
Text(
|
modifier = Modifier.fillMaxWidth(),
|
||||||
text = "All messages with $displayName will be permanently deleted. This action cannot be undone.",
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
fontSize = 15.sp,
|
) {
|
||||||
color = if (isDarkTheme) Color(0xFFAAAAAA) else Color(0xFF666666),
|
val displayName = dialog.opponentTitle.ifEmpty { dialog.opponentKey.take(8) }
|
||||||
textAlign = TextAlign.Center,
|
Text(
|
||||||
modifier = Modifier.fillMaxWidth()
|
text = "All messages with $displayName will be permanently deleted. This action cannot be undone.",
|
||||||
)
|
fontSize = 15.sp,
|
||||||
},
|
color = if (isDarkTheme) Color(0xFFAAAAAA) else Color(0xFF666666),
|
||||||
confirmButton = {
|
textAlign = TextAlign.Center,
|
||||||
TextButton(
|
modifier = Modifier.fillMaxWidth()
|
||||||
onClick = {
|
|
||||||
scope.launch {
|
|
||||||
chatsViewModel.deleteDialog(dialog.opponentKey)
|
|
||||||
dialogToDelete = null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
colors = ButtonDefaults.textButtonColors(
|
|
||||||
contentColor = Color(0xFFFF3B30)
|
|
||||||
)
|
)
|
||||||
) {
|
|
||||||
Text("Delete", fontWeight = FontWeight.SemiBold)
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
}
|
|
||||||
},
|
Row(
|
||||||
dismissButton = {
|
modifier = Modifier.fillMaxWidth(),
|
||||||
TextButton(
|
horizontalArrangement = Arrangement.Center,
|
||||||
onClick = { dialogToDelete = null }
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Text("Cancel", fontWeight = FontWeight.Medium)
|
TextButton(
|
||||||
|
onClick = { dialogToDelete = null },
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
Text("Cancel", fontWeight = FontWeight.Medium)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
chatsViewModel.deleteDialog(dialog.opponentKey)
|
||||||
|
dialogToDelete = null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors = ButtonDefaults.textButtonColors(
|
||||||
|
contentColor = Color(0xFFFF3B30)
|
||||||
|
),
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
Text("Delete", fontWeight = FontWeight.SemiBold)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
confirmButton = {},
|
||||||
|
dismissButton = {},
|
||||||
containerColor = if (isDarkTheme) Color(0xFF2C2C2E) else Color.White,
|
containerColor = if (isDarkTheme) Color(0xFF2C2C2E) else Color.White,
|
||||||
shape = RoundedCornerShape(16.dp)
|
shape = RoundedCornerShape(16.dp)
|
||||||
)
|
)
|
||||||
@@ -635,37 +655,55 @@ fun ChatsListScreen(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
val displayName = dialog.opponentTitle.ifEmpty { dialog.opponentKey.take(8) }
|
Column(
|
||||||
Text(
|
modifier = Modifier.fillMaxWidth(),
|
||||||
text = "$displayName will no longer be able to send you messages. You can unblock them later.",
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
fontSize = 15.sp,
|
) {
|
||||||
color = if (isDarkTheme) Color(0xFFAAAAAA) else Color(0xFF666666),
|
val displayName = dialog.opponentTitle.ifEmpty { dialog.opponentKey.take(8) }
|
||||||
textAlign = TextAlign.Center,
|
Text(
|
||||||
modifier = Modifier.fillMaxWidth()
|
text = "$displayName will no longer be able to send you messages. You can unblock them later.",
|
||||||
)
|
fontSize = 15.sp,
|
||||||
},
|
color = if (isDarkTheme) Color(0xFFAAAAAA) else Color(0xFF666666),
|
||||||
confirmButton = {
|
textAlign = TextAlign.Center,
|
||||||
TextButton(
|
modifier = Modifier.fillMaxWidth()
|
||||||
onClick = {
|
|
||||||
scope.launch {
|
|
||||||
chatsViewModel.blockUser(dialog.opponentKey)
|
|
||||||
dialogToBlock = null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
colors = ButtonDefaults.textButtonColors(
|
|
||||||
contentColor = Color(0xFFFF3B30)
|
|
||||||
)
|
)
|
||||||
) {
|
|
||||||
Text("Block", fontWeight = FontWeight.SemiBold)
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
}
|
|
||||||
},
|
Row(
|
||||||
dismissButton = {
|
modifier = Modifier.fillMaxWidth(),
|
||||||
TextButton(
|
horizontalArrangement = Arrangement.Center,
|
||||||
onClick = { dialogToBlock = null }
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Text("Cancel", fontWeight = FontWeight.Medium)
|
TextButton(
|
||||||
|
onClick = { dialogToBlock = null },
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
Text("Cancel", fontWeight = FontWeight.Medium)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
chatsViewModel.blockUser(dialog.opponentKey)
|
||||||
|
dialogToBlock = null
|
||||||
|
blocklistUpdateTrigger++
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors = ButtonDefaults.textButtonColors(
|
||||||
|
contentColor = Color(0xFFFF3B30)
|
||||||
|
),
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
Text("Block", fontWeight = FontWeight.SemiBold)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
confirmButton = {},
|
||||||
|
dismissButton = {},
|
||||||
containerColor = if (isDarkTheme) Color(0xFF2C2C2E) else Color.White,
|
containerColor = if (isDarkTheme) Color(0xFF2C2C2E) else Color.White,
|
||||||
shape = RoundedCornerShape(16.dp)
|
shape = RoundedCornerShape(16.dp)
|
||||||
)
|
)
|
||||||
@@ -693,37 +731,55 @@ fun ChatsListScreen(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
val displayName = dialog.opponentTitle.ifEmpty { dialog.opponentKey.take(8) }
|
Column(
|
||||||
Text(
|
modifier = Modifier.fillMaxWidth(),
|
||||||
text = "$displayName will be able to send you messages again.",
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
fontSize = 15.sp,
|
) {
|
||||||
color = if (isDarkTheme) Color(0xFFAAAAAA) else Color(0xFF666666),
|
val displayName = dialog.opponentTitle.ifEmpty { dialog.opponentKey.take(8) }
|
||||||
textAlign = TextAlign.Center,
|
Text(
|
||||||
modifier = Modifier.fillMaxWidth()
|
text = "$displayName will be able to send you messages again.",
|
||||||
)
|
fontSize = 15.sp,
|
||||||
},
|
color = if (isDarkTheme) Color(0xFFAAAAAA) else Color(0xFF666666),
|
||||||
confirmButton = {
|
textAlign = TextAlign.Center,
|
||||||
TextButton(
|
modifier = Modifier.fillMaxWidth()
|
||||||
onClick = {
|
|
||||||
scope.launch {
|
|
||||||
chatsViewModel.unblockUser(dialog.opponentKey)
|
|
||||||
dialogToUnblock = null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
colors = ButtonDefaults.textButtonColors(
|
|
||||||
contentColor = Color(0xFF4CAF50)
|
|
||||||
)
|
)
|
||||||
) {
|
|
||||||
Text("Unblock", fontWeight = FontWeight.SemiBold)
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
}
|
|
||||||
},
|
Row(
|
||||||
dismissButton = {
|
modifier = Modifier.fillMaxWidth(),
|
||||||
TextButton(
|
horizontalArrangement = Arrangement.Center,
|
||||||
onClick = { dialogToUnblock = null }
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Text("Cancel", fontWeight = FontWeight.Medium)
|
TextButton(
|
||||||
|
onClick = { dialogToUnblock = null },
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
Text("Cancel", fontWeight = FontWeight.Medium)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
chatsViewModel.unblockUser(dialog.opponentKey)
|
||||||
|
dialogToUnblock = null
|
||||||
|
blocklistUpdateTrigger++
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors = ButtonDefaults.textButtonColors(
|
||||||
|
contentColor = Color(0xFF4CAF50)
|
||||||
|
),
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
Text("Unblock", fontWeight = FontWeight.SemiBold)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
confirmButton = {},
|
||||||
|
dismissButton = {},
|
||||||
containerColor = if (isDarkTheme) Color(0xFF2C2C2E) else Color.White,
|
containerColor = if (isDarkTheme) Color(0xFF2C2C2E) else Color.White,
|
||||||
shape = RoundedCornerShape(16.dp)
|
shape = RoundedCornerShape(16.dp)
|
||||||
)
|
)
|
||||||
@@ -1106,7 +1162,7 @@ fun SwipeableDialogItem(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. КОНТЕНТ - поверх кнопок, сдвигается при свайпе
|
// 2. КОНТЕНТ - поверх кнопок, сдвигается при свайпе
|
||||||
Box(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.offset { IntOffset(animatedOffsetX.toInt(), 0) }
|
.offset { IntOffset(animatedOffsetX.toInt(), 0) }
|
||||||
@@ -1132,23 +1188,30 @@ fun SwipeableDialogItem(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
DialogItem(
|
DialogItemContent(
|
||||||
dialog = dialog,
|
dialog = dialog,
|
||||||
isDarkTheme = isDarkTheme,
|
isDarkTheme = isDarkTheme,
|
||||||
isTyping = isTyping,
|
isTyping = isTyping,
|
||||||
onClick = onClick
|
onClick = onClick
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Сепаратор внутри контента
|
||||||
|
val dividerColor = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE8E8E8)
|
||||||
|
Divider(
|
||||||
|
modifier = Modifier.padding(start = 84.dp),
|
||||||
|
color = dividerColor,
|
||||||
|
thickness = 0.5.dp
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Элемент диалога из базы данных - ОПТИМИЗИРОВАННЫЙ */
|
/** Элемент диалога из базы данных - ОПТИМИЗИРОВАННЫЙ (без сепаратора для SwipeableDialogItem) */
|
||||||
@Composable
|
@Composable
|
||||||
fun DialogItem(dialog: DialogUiModel, isDarkTheme: Boolean, isTyping: Boolean = false, onClick: () -> Unit) {
|
fun DialogItemContent(dialog: DialogUiModel, isDarkTheme: Boolean, isTyping: Boolean = false, onClick: () -> Unit) {
|
||||||
// 🔥 ОПТИМИЗАЦИЯ: Кешируем цвета и строки
|
// 🔥 ОПТИМИЗАЦИЯ: Кешируем цвета и строки
|
||||||
val textColor = remember(isDarkTheme) { if (isDarkTheme) Color.White else Color.Black }
|
val textColor = remember(isDarkTheme) { if (isDarkTheme) Color.White else Color.Black }
|
||||||
val secondaryTextColor = remember(isDarkTheme) { if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666) }
|
val secondaryTextColor = remember(isDarkTheme) { if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666) }
|
||||||
val dividerColor = remember(isDarkTheme) { if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE8E8E8) }
|
|
||||||
|
|
||||||
val avatarColors = remember(dialog.opponentKey, isDarkTheme) { getAvatarColor(dialog.opponentKey, isDarkTheme) }
|
val avatarColors = remember(dialog.opponentKey, isDarkTheme) { getAvatarColor(dialog.opponentKey, isDarkTheme) }
|
||||||
val displayName = remember(dialog.opponentTitle, dialog.opponentKey) {
|
val displayName = remember(dialog.opponentTitle, dialog.opponentKey) {
|
||||||
@@ -1166,24 +1229,23 @@ fun DialogItem(dialog: DialogUiModel, isDarkTheme: Boolean, isTyping: Boolean =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Row(
|
||||||
Row(
|
modifier =
|
||||||
modifier =
|
Modifier.fillMaxWidth()
|
||||||
Modifier.fillMaxWidth()
|
.clickable(onClick = onClick)
|
||||||
.clickable(onClick = onClick)
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
verticalAlignment = Alignment.CenterVertically
|
||||||
verticalAlignment = Alignment.CenterVertically
|
) {
|
||||||
) {
|
// Avatar container with online indicator
|
||||||
// Avatar container with online indicator
|
Box(modifier = Modifier.size(56.dp)) {
|
||||||
Box(modifier = Modifier.size(56.dp)) {
|
// Avatar
|
||||||
// Avatar
|
Box(
|
||||||
Box(
|
modifier =
|
||||||
modifier =
|
Modifier.fillMaxSize()
|
||||||
Modifier.fillMaxSize()
|
.clip(CircleShape)
|
||||||
.clip(CircleShape)
|
.background(avatarColors.backgroundColor),
|
||||||
.background(avatarColors.backgroundColor),
|
contentAlignment = Alignment.Center
|
||||||
contentAlignment = Alignment.Center
|
) {
|
||||||
) {
|
|
||||||
Text(
|
Text(
|
||||||
text = initials,
|
text = initials,
|
||||||
color = avatarColors.textColor,
|
color = avatarColors.textColor,
|
||||||
@@ -1290,13 +1352,6 @@ fun DialogItem(dialog: DialogUiModel, isDarkTheme: Boolean, isTyping: Boolean =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Divider(
|
|
||||||
modifier = Modifier.padding(start = 84.dp),
|
|
||||||
color = dividerColor,
|
|
||||||
thickness = 0.5.dp
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -176,10 +176,19 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
|||||||
if (currentAccount.isEmpty()) return
|
if (currentAccount.isEmpty()) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Вычисляем правильный dialog_key (отсортированная комбинация ключей)
|
||||||
|
val dialogKey = if (currentAccount < opponentKey) {
|
||||||
|
"$currentAccount:$opponentKey"
|
||||||
|
} else {
|
||||||
|
"$opponentKey:$currentAccount"
|
||||||
|
}
|
||||||
|
|
||||||
|
android.util.Log.d("ChatsListViewModel", "Deleting dialog with key: $dialogKey")
|
||||||
|
|
||||||
// Удаляем все сообщения из диалога по dialog_key
|
// Удаляем все сообщения из диалога по dialog_key
|
||||||
database.messageDao().deleteDialog(
|
database.messageDao().deleteDialog(
|
||||||
account = currentAccount,
|
account = currentAccount,
|
||||||
dialogKey = opponentKey
|
dialogKey = dialogKey
|
||||||
)
|
)
|
||||||
// Также удаляем по from/to ключам (на всякий случай)
|
// Также удаляем по from/to ключам (на всякий случай)
|
||||||
database.messageDao().deleteMessagesBetweenUsers(
|
database.messageDao().deleteMessagesBetweenUsers(
|
||||||
|
|||||||
Reference in New Issue
Block a user