Refactor code structure for improved readability and maintainability

This commit is contained in:
2026-01-17 01:50:10 +05:00
parent 97eac22879
commit 14ea9e6996
6 changed files with 139 additions and 78 deletions

File diff suppressed because one or more lines are too long

View File

@@ -151,8 +151,10 @@ class MessageRepository private constructor(private val context: Context) {
attachments: List<MessageAttachment> = emptyList(), attachments: List<MessageAttachment> = emptyList(),
replyToMessageId: String? = null replyToMessageId: String? = null
): Message { ): Message {
android.util.Log.d("MessageRepo", "📤 sendMessage START: to=${toPublicKey.take(16)}...")
val account = currentAccount ?: throw IllegalStateException("Not initialized") val account = currentAccount ?: throw IllegalStateException("Not initialized")
val privateKey = currentPrivateKey ?: throw IllegalStateException("Not initialized") val privateKey = currentPrivateKey ?: throw IllegalStateException("Not initialized")
android.util.Log.d("MessageRepo", "📤 sendMessage: account=${account.take(16)}...")
val messageId = UUID.randomUUID().toString().replace("-", "").take(32) val messageId = UUID.randomUUID().toString().replace("-", "").take(32)
val timestamp = System.currentTimeMillis() val timestamp = System.currentTimeMillis()
@@ -217,7 +219,8 @@ class MessageRepository private constructor(private val context: Context) {
updateDialog(toPublicKey, text.trim(), timestamp) updateDialog(toPublicKey, text.trim(), timestamp)
// 🔥 Отмечаем что я отправлял сообщения в этот диалог (перемещает из requests в chats) // 🔥 Отмечаем что я отправлял сообщения в этот диалог (перемещает из requests в chats)
dialogDao.markIHaveSent(account, toPublicKey) val updatedRows = dialogDao.markIHaveSent(account, toPublicKey)
android.util.Log.d("MessageRepo", "📤 MARKED i_have_sent=1 for opponent=${toPublicKey.take(16)}..., updatedRows=$updatedRows")
// Отправляем пакет // Отправляем пакет
val packet = PacketMessage().apply { val packet = PacketMessage().apply {
@@ -536,10 +539,15 @@ class MessageRepository private constructor(private val context: Context) {
suspend fun updateDialogUserInfo(publicKey: String, title: String, username: String, verified: Int) { suspend fun updateDialogUserInfo(publicKey: String, title: String, username: String, verified: Int) {
val account = currentAccount ?: return val account = currentAccount ?: return
android.util.Log.d("MessageRepo", "📋 updateDialogUserInfo: publicKey=${publicKey.take(16)}..., title=$title, username=$username")
// Проверяем существует ли диалог с этим пользователем // Проверяем существует ли диалог с этим пользователем
val existing = dialogDao.getDialog(account, publicKey) val existing = dialogDao.getDialog(account, publicKey)
if (existing != null) { if (existing != null) {
android.util.Log.d("MessageRepo", "📋 Updating existing dialog info for ${publicKey.take(16)}...")
dialogDao.updateOpponentInfo(account, publicKey, title, username, verified) dialogDao.updateOpponentInfo(account, publicKey, title, username, verified)
} else {
android.util.Log.d("MessageRepo", "📋 Dialog not found for ${publicKey.take(16)}..., skipping")
} }
} }

View File

@@ -361,9 +361,10 @@ interface DialogDao {
/** /**
* Отметить что я отправлял сообщения в этот диалог * Отметить что я отправлял сообщения в этот диалог
* Возвращает количество обновлённых строк
*/ */
@Query("UPDATE dialogs SET i_have_sent = 1 WHERE account = :account AND opponent_key = :opponentKey") @Query("UPDATE dialogs SET i_have_sent = 1 WHERE account = :account AND opponent_key = :opponentKey")
suspend fun markIHaveSent(account: String, opponentKey: String) suspend fun markIHaveSent(account: String, opponentKey: String): Int
/** /**
* Обновить онлайн статус * Обновить онлайн статус
@@ -417,7 +418,8 @@ interface DialogDao {
* Логика: * Логика:
* 1. Берем последнее сообщение (по timestamp DESC) * 1. Берем последнее сообщение (по timestamp DESC)
* 2. Считаем количество непрочитанных сообщений (from_me = 0 AND read = 0) * 2. Считаем количество непрочитанных сообщений (from_me = 0 AND read = 0)
* 3. Обновляем диалог или создаем новый * 3. Вычисляем i_have_sent = 1 если есть исходящие сообщения (from_me = 1) - как sended в Архиве
* 4. Обновляем диалог или создаем новый
*/ */
@Query(""" @Query("""
INSERT OR REPLACE INTO dialogs ( INSERT OR REPLACE INTO dialogs (
@@ -430,7 +432,8 @@ interface DialogDao {
unread_count, unread_count,
is_online, is_online,
last_seen, last_seen,
verified verified,
i_have_sent
) )
SELECT SELECT
:account AS account, :account AS account,
@@ -478,7 +481,19 @@ interface DialogDao {
COALESCE( COALESCE(
(SELECT verified FROM dialogs WHERE account = :account AND opponent_key = :opponentKey), (SELECT verified FROM dialogs WHERE account = :account AND opponent_key = :opponentKey),
0 0
) AS verified ) AS verified,
CASE
WHEN (SELECT COUNT(*) FROM messages
WHERE account = :account
AND from_public_key = :account
AND to_public_key = :opponentKey
AND from_me = 1) > 0
THEN 1
ELSE COALESCE(
(SELECT i_have_sent FROM dialogs WHERE account = :account AND opponent_key = :opponentKey),
0
)
END AS i_have_sent
""") """)
suspend fun updateDialogFromMessages(account: String, opponentKey: String) suspend fun updateDialogFromMessages(account: String, opponentKey: String)
} }

View File

@@ -810,6 +810,9 @@ fun ChatDetailScreen(
// Кнопка меню - открывает bottom sheet // Кнопка меню - открывает bottom sheet
IconButton( IconButton(
onClick = { onClick = {
// Закрываем клавиатуру перед открытием меню
keyboardController?.hide()
focusManager.clearFocus()
showMenu = true showMenu = true
}, },
modifier = Modifier modifier = Modifier
@@ -1087,11 +1090,15 @@ fun ChatDetailScreen(
verticalArrangement = Arrangement.Center verticalArrangement = Arrangement.Center
) { ) {
if (isSavedMessages) { if (isSavedMessages) {
Icon( val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.saved))
Icons.Default.Bookmark, val progress by animateLottieCompositionAsState(
contentDescription = null, composition = composition,
tint = secondaryTextColor.copy(alpha = 0.5f), iterations = LottieConstants.IterateForever
modifier = Modifier.size(64.dp) )
LottieAnimation(
composition = composition,
progress = { progress },
modifier = Modifier.size(120.dp)
) )
} else { } else {
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.speech)) val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.speech))

View File

@@ -704,84 +704,113 @@ fun ChatsListScreen(
val requestsCount by chatsViewModel.requestsCount.collectAsState() val requestsCount by chatsViewModel.requestsCount.collectAsState()
val requests by chatsViewModel.requests.collectAsState() val requests by chatsViewModel.requests.collectAsState()
if (showRequestsScreen) { // 🎬 Animated content transition between main list and requests
// 📬 Show Requests Screen AnimatedContent(
RequestsScreen( targetState = showRequestsScreen,
requests = requests, transitionSpec = {
isDarkTheme = isDarkTheme, if (targetState) {
onBack = { showRequestsScreen = false }, // Переход на Requests: slide in from right + fade
onRequestClick = { request -> (slideInHorizontally(
showRequestsScreen = false animationSpec = tween(300, easing = FastOutSlowInEasing),
val user = chatsViewModel.dialogToSearchUser(request) initialOffsetX = { it }
onUserSelect(user) ) + fadeIn(tween(300))) togetherWith
(slideOutHorizontally(
animationSpec = tween(300, easing = FastOutSlowInEasing),
targetOffsetX = { -it / 3 }
) + fadeOut(tween(200)))
} else {
// Возврат из Requests: slide out to right
(slideInHorizontally(
animationSpec = tween(300, easing = FastOutSlowInEasing),
initialOffsetX = { -it / 3 }
) + fadeIn(tween(300))) togetherWith
(slideOutHorizontally(
animationSpec = tween(300, easing = FastOutSlowInEasing),
targetOffsetX = { it }
) + fadeOut(tween(200)))
} }
) },
} else if (dialogsList.isEmpty() && requestsCount == 0) { label = "RequestsTransition"
// Empty state with Lottie animation ) { isRequestsScreen ->
EmptyChatsState( if (isRequestsScreen) {
// 📬 Show Requests Screen
RequestsScreen(
requests = requests,
isDarkTheme = isDarkTheme, isDarkTheme = isDarkTheme,
modifier = Modifier.fillMaxSize() onBack = { showRequestsScreen = false },
) onRequestClick = { request ->
} else { showRequestsScreen = false
// Show dialogs list val user = chatsViewModel.dialogToSearchUser(request)
val dividerColor = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE8E8E8) onUserSelect(user)
LazyColumn(modifier = Modifier.fillMaxSize()) {
// 📬 Requests Section
if (requestsCount > 0) {
item(key = "requests_section") {
RequestsSection(
count = requestsCount,
isDarkTheme = isDarkTheme,
onClick = { showRequestsScreen = true }
)
Divider(
color = dividerColor,
thickness = 0.5.dp
)
} }
} )
} else if (dialogsList.isEmpty() && requestsCount == 0) {
// Empty state with Lottie animation
EmptyChatsState(
isDarkTheme = isDarkTheme,
modifier = Modifier.fillMaxSize()
)
} else {
// Show dialogs list
val dividerColor = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE8E8E8)
items(dialogsList, key = { it.opponentKey }) { dialog -> LazyColumn(modifier = Modifier.fillMaxSize()) {
val isSavedMessages = dialog.opponentKey == accountPublicKey // 📬 Requests Section
// Check if user is blocked if (requestsCount > 0) {
var isBlocked by remember { mutableStateOf(false) } item(key = "requests_section") {
LaunchedEffect(dialog.opponentKey, blocklistUpdateTrigger) { RequestsSection(
isBlocked = chatsViewModel.isUserBlocked(dialog.opponentKey) count = requestsCount,
isDarkTheme = isDarkTheme,
onClick = { showRequestsScreen = true }
)
Divider(
color = dividerColor,
thickness = 0.5.dp
)
}
} }
Column { items(dialogsList, key = { it.opponentKey }) { dialog ->
SwipeableDialogItem( val isSavedMessages = dialog.opponentKey == accountPublicKey
dialog = dialog, // Check if user is blocked
isDarkTheme = isDarkTheme, var isBlocked by remember { mutableStateOf(false) }
isTyping = typingUsers.contains(dialog.opponentKey), LaunchedEffect(dialog.opponentKey, blocklistUpdateTrigger) {
isBlocked = isBlocked, isBlocked = chatsViewModel.isUserBlocked(dialog.opponentKey)
isSavedMessages = isSavedMessages, }
onClick = {
val user = chatsViewModel.dialogToSearchUser(dialog)
onUserSelect(user)
},
onDelete = {
dialogToDelete = dialog
},
onBlock = {
dialogToBlock = dialog
},
onUnblock = {
dialogToUnblock = dialog
}
)
// 🔥 СЕПАРАТОР - линия разделения между диалогами Column {
Divider( SwipeableDialogItem(
modifier = Modifier.padding(start = 84.dp), dialog = dialog,
color = dividerColor, isDarkTheme = isDarkTheme,
thickness = 0.5.dp isTyping = typingUsers.contains(dialog.opponentKey),
) isBlocked = isBlocked,
isSavedMessages = isSavedMessages,
onClick = {
val user = chatsViewModel.dialogToSearchUser(dialog)
onUserSelect(user)
},
onDelete = {
dialogToDelete = dialog
},
onBlock = {
dialogToBlock = dialog
},
onUnblock = {
dialogToUnblock = dialog
}
)
// 🔥 СЕПАРАТОР - линия разделения между диалогами
Divider(
modifier = Modifier.padding(start = 84.dp),
color = dividerColor,
thickness = 0.5.dp
)
}
} }
} }
} }
} } // Close AnimatedContent
// Console button removed // Console button removed
} }

File diff suppressed because one or more lines are too long