Refactor code structure for improved readability and maintainability
This commit is contained in:
1
app/src/main/assets/lottie/saved.json
Normal file
1
app/src/main/assets/lottie/saved.json
Normal file
File diff suppressed because one or more lines are too long
@@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
1
app/src/main/res/raw/saved.json
Normal file
1
app/src/main/res/raw/saved.json
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user