Восстановил поломанную отправку и печать
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -33,6 +33,8 @@ class SignalRNotificationObserver @Inject constructor(
|
||||
private val chatRepository: chats.domain.repository.ChatRepository,
|
||||
private val serverConfig: ServerConfig,
|
||||
private val networkManager: NetworkManager,
|
||||
private val messageDao: core.database.data.MessageDao,
|
||||
private val api: chats.data.remote.api.ChatApi,
|
||||
@ApplicationContext private val context: Context
|
||||
) {
|
||||
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
||||
@@ -195,6 +197,7 @@ class SignalRNotificationObserver @Inject constructor(
|
||||
/**
|
||||
* Синхронизирует пропущенные сообщения после восстановления соединения
|
||||
* Запрашивает только НОВЫЕ сообщения с последнего известного sequenceId
|
||||
* Показывает уведомления для непрочитанных сообщений
|
||||
*/
|
||||
private fun syncMissedMessages() {
|
||||
scope.launch {
|
||||
@@ -212,11 +215,21 @@ class SignalRNotificationObserver @Inject constructor(
|
||||
if (lastSequenceId != null) {
|
||||
// Запрашиваем сообщения ПОСЛЕ lastSequenceId (только новые)
|
||||
android.util.Log.d("SignalRNtfObserver", "Chat ${chat.id}: last known seqId=$lastSequenceId, fetching newer...")
|
||||
chatRepository.getMessages(
|
||||
val newMessages = chatRepository.getMessages(
|
||||
chatId = chat.id,
|
||||
afterSequenceId = lastSequenceId.toLong(),
|
||||
limit = 100
|
||||
)
|
||||
|
||||
android.util.Log.d("SignalRNtfObserver", "Chat ${chat.id}: fetched ${newMessages.size} messages from API")
|
||||
|
||||
// Показываем уведомление если есть новые сообщения и чат не открыт
|
||||
if (newMessages.isNotEmpty() && activeChatTracker.currentChatId.value != chat.id) {
|
||||
android.util.Log.d("SignalRNtfObserver", "Chat ${chat.id}: ${newMessages.size} new messages, showing notification")
|
||||
showMissedMessagesNotification(chat, newMessages)
|
||||
} else {
|
||||
android.util.Log.d("SignalRNtfObserver", "Chat ${chat.id}: skipping notification (messages=${newMessages.size}, chatOpen=${activeChatTracker.currentChatId.value == chat.id})")
|
||||
}
|
||||
} else {
|
||||
// Нет локальных сообщений - загружаем последние 50
|
||||
android.util.Log.d("SignalRNtfObserver", "Chat ${chat.id}: no local messages, fetching last 50")
|
||||
@@ -232,12 +245,81 @@ class SignalRNotificationObserver @Inject constructor(
|
||||
}
|
||||
|
||||
android.util.Log.d("SignalRNtfObserver", "Missed messages sync completed")
|
||||
|
||||
// Обновляем счетчик непрочитанных после синхронизации
|
||||
refresh()
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("SignalRNtfObserver", "Sync failed", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Показывает уведомление о пропущенных сообщениях
|
||||
*/
|
||||
private fun showMissedMessagesNotification(chat: chats.domain.model.Chat, newMessages: List<chats.domain.model.Message>) {
|
||||
scope.launch {
|
||||
try {
|
||||
android.util.Log.d("SignalRNtfObserver", "showMissedMessagesNotification called for chat ${chat.id} with ${newMessages.size} messages")
|
||||
|
||||
// Фильтруем сообщения не от текущего пользователя
|
||||
val currentUserId = tokenManager.getUserId()
|
||||
android.util.Log.d("SignalRNtfObserver", "Current user ID: $currentUserId")
|
||||
|
||||
val messagesFromOthers = newMessages.filter { it.senderId != currentUserId }
|
||||
android.util.Log.d("SignalRNtfObserver", "Messages from others: ${messagesFromOthers.size}")
|
||||
|
||||
if (messagesFromOthers.isEmpty()) {
|
||||
android.util.Log.d("SignalRNtfObserver", "No new messages from others in chat ${chat.id} - all messages are from current user")
|
||||
return@launch
|
||||
}
|
||||
|
||||
// Группируем сообщения по отправителям
|
||||
val messagesBySender = messagesFromOthers.groupBy { it.senderId }
|
||||
android.util.Log.d("SignalRNtfObserver", "Messages grouped by ${messagesBySender.size} sender(s)")
|
||||
|
||||
// Для каждого отправителя показываем уведомление
|
||||
messagesBySender.forEach { (senderId, messages) ->
|
||||
val senderName = messages.firstOrNull()?.senderName ?: "Контакт"
|
||||
val messageCount = messages.size
|
||||
|
||||
android.util.Log.d("SignalRNtfObserver", "Processing sender $senderName with $messageCount messages")
|
||||
|
||||
// Формируем текст уведомления
|
||||
val notificationText = when {
|
||||
messageCount == 1 -> {
|
||||
messages.firstOrNull()?.content ?: "Новое сообщение"
|
||||
}
|
||||
messageCount <= 3 -> {
|
||||
messages.take(3).mapNotNull { it.content }.joinToString(", ")
|
||||
}
|
||||
else -> {
|
||||
"$messageCount новых сообщений"
|
||||
}
|
||||
}
|
||||
|
||||
android.util.Log.d("SignalRNtfObserver", "Showing notification for chat ${chat.id}: $senderName - $notificationText")
|
||||
|
||||
// Небольшая задержка перед показом уведомления
|
||||
kotlinx.coroutines.delay(500)
|
||||
|
||||
// Показываем уведомление
|
||||
NotificationHelper.showNotification(
|
||||
context = context,
|
||||
title = senderName,
|
||||
body = notificationText,
|
||||
type = "chat",
|
||||
chatId = chat.id,
|
||||
notificationId = chat.id.hashCode(),
|
||||
totalCount = activeChatTracker.totalUnreadCount.value
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("SignalRNtfObserver", "Failed to show missed messages notification", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает последний известный sequenceId для чата из локальной базы
|
||||
*/
|
||||
@@ -277,13 +359,101 @@ class SignalRNotificationObserver @Inject constructor(
|
||||
android.util.Log.d("SignalRNtfObserver", "SignalR reconnected, syncing missed messages...")
|
||||
// 5. Синхронизируем пропущенные сообщения
|
||||
syncMissedMessages()
|
||||
|
||||
// 6. Пробуем отправить отложенные сообщения НЕМЕДЛЕННО (не через WorkManager)
|
||||
sendPendingMessagesImmediately()
|
||||
|
||||
// 7. Также планируем WorkManager на всякий случай
|
||||
MessageSyncWorker.scheduleSync(context)
|
||||
android.util.Log.d("SignalRNtfObserver", "Outgoing sync worker scheduled")
|
||||
} else {
|
||||
android.util.Log.w("SignalRNtfObserver", "SignalR failed to reconnect within timeout")
|
||||
}
|
||||
|
||||
// 6. Запускаем синхронизацию отложенных сообщений
|
||||
MessageSyncWorker.scheduleSync(context)
|
||||
android.util.Log.d("SignalRNtfObserver", "Outgoing sync worker scheduled")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Немедленно отправляет отложенные сообщения без ожидания WorkManager
|
||||
* Вызывается сразу после восстановления соединения
|
||||
*/
|
||||
private fun sendPendingMessagesImmediately() {
|
||||
scope.launch {
|
||||
try {
|
||||
android.util.Log.d("SignalRNtfObserver", "Checking for pending messages to send...")
|
||||
|
||||
// Получаем все сообщения со статусом SYNCING
|
||||
val pendingMessages = messageDao.getPendingSyncMessages()
|
||||
android.util.Log.d("SignalRNtfObserver", "Found ${pendingMessages.size} pending messages")
|
||||
|
||||
if (pendingMessages.isEmpty()) {
|
||||
android.util.Log.d("SignalRNtfObserver", "No pending messages to send")
|
||||
return@launch
|
||||
}
|
||||
|
||||
var successCount = 0
|
||||
var failureCount = 0
|
||||
|
||||
for (message in pendingMessages) {
|
||||
try {
|
||||
android.util.Log.d("SignalRNtfObserver", "Sending pending message: ${message.id}")
|
||||
|
||||
val attachments = parseAttachments(message.mediaJson)
|
||||
val request = chats.data.remote.api.SendMessageRequest(
|
||||
content = message.content,
|
||||
type = message.mediaType.lowercase(),
|
||||
attachments = attachments,
|
||||
replyToId = message.replyToId
|
||||
)
|
||||
|
||||
val response = api.sendMessage(message.chatId, request)
|
||||
|
||||
// Обновляем сообщение в базе
|
||||
val syncedMessage = message.copy(
|
||||
id = response.id,
|
||||
sequenceId = response.sequenceId ?: message.sequenceId,
|
||||
createdAt = response.createdAt ?: message.createdAt,
|
||||
syncStatus = core.database.data.SyncStatus.SYNCED,
|
||||
isDeletedLocally = false,
|
||||
isEditedLocally = false,
|
||||
editedContent = null,
|
||||
lastUpdated = System.currentTimeMillis()
|
||||
)
|
||||
|
||||
messageDao.insertMessage(syncedMessage)
|
||||
android.util.Log.d("SignalRNtfObserver", "Message sent successfully: ${response.id}")
|
||||
successCount++
|
||||
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("SignalRNtfObserver", "Failed to send message ${message.id}", e)
|
||||
failureCount++
|
||||
}
|
||||
}
|
||||
|
||||
android.util.Log.d("SignalRNtfObserver", "Pending messages sync completed. Success: $successCount, Failed: $failureCount")
|
||||
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("SignalRNtfObserver", "sendPendingMessagesImmediately failed", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseAttachments(mediaJson: String): List<chats.data.remote.api.AttachmentRequest>? {
|
||||
return try {
|
||||
val gson = com.google.gson.Gson()
|
||||
val mediaList = gson.fromJson(mediaJson, Array::class.java)
|
||||
?.map { elem ->
|
||||
val map = elem as Map<*, *>
|
||||
chats.data.remote.api.AttachmentRequest(
|
||||
type = map["type"] as? String ?: "file",
|
||||
url = map["url"] as? String ?: "",
|
||||
fileName = map["filename"] as? String ?: "file",
|
||||
fileSize = (map["size"] as? Number)?.toLong() ?: 0L
|
||||
)
|
||||
}
|
||||
mediaList?.takeIf { it.isNotEmpty() }
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("SignalRNtfObserver", "Failed to parse attachments", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -194,7 +194,35 @@ class ChatRepositoryImpl @Inject constructor(
|
||||
|
||||
messageDao.insertMessage(localMessage)
|
||||
Log.d(TAG, "Saved local message: $localId")
|
||||
MessageSyncWorker.scheduleSync(context)
|
||||
|
||||
// Пробуем отправить НЕМЕДЛЕННО через API
|
||||
try {
|
||||
Log.d(TAG, "Sending message immediately via API: $localId")
|
||||
val request = SendMessageRequest(
|
||||
content = content,
|
||||
type = type,
|
||||
attachments = attachments,
|
||||
replyToId = replyToId
|
||||
)
|
||||
val response = api.sendMessage(chatId, request)
|
||||
Log.d(TAG, "Message sent successfully: ${response.id}")
|
||||
|
||||
// Обновляем сообщение в базе с серверными данными
|
||||
val syncedMessage = localMessage.copy(
|
||||
id = response.id,
|
||||
sequenceId = response.sequenceId ?: 0,
|
||||
createdAt = response.createdAt ?: localMessage.createdAt,
|
||||
syncStatus = SyncStatus.SYNCED
|
||||
)
|
||||
messageDao.insertMessage(syncedMessage)
|
||||
|
||||
// Возвращаем доменную модель с серверными данными
|
||||
return response.toDomain(userId, baseUrl)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to send message immediately, scheduling sync: ${e.message}")
|
||||
// Ошибка - планируем синхронизацию через WorkManager
|
||||
MessageSyncWorker.scheduleSync(context)
|
||||
}
|
||||
|
||||
// Создаём доменную модель вручную для локального сообщения
|
||||
return Message(
|
||||
|
||||
@@ -329,6 +329,11 @@ class ChatDetailViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
is ChatEvent.UserTyping -> {
|
||||
// Игнорируем свои же typing события
|
||||
if (event.userId == getCurrentUserId()) {
|
||||
android.util.Log.d("ChatDetailVM", "Ignoring own typing event")
|
||||
return@onEach
|
||||
}
|
||||
_state.update { it.copy(isTyping = true) }
|
||||
typingTimerJob?.cancel()
|
||||
typingTimerJob = viewModelScope.launch {
|
||||
@@ -337,6 +342,11 @@ class ChatDetailViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
is ChatEvent.UserStoppedTyping -> {
|
||||
// Игнорируем свои же события
|
||||
if (event.userId == getCurrentUserId()) {
|
||||
android.util.Log.d("ChatDetailVM", "Ignoring own stopped typing event")
|
||||
return@onEach
|
||||
}
|
||||
_state.update { it.copy(isTyping = false) }
|
||||
}
|
||||
is ChatEvent.MessagesRead -> {
|
||||
|
||||
Reference in New Issue
Block a user