feat: Implement message forwarding feature with chat selection and re-encryption logic

This commit is contained in:
k1ngsterr1
2026-01-16 03:29:32 +05:00
parent 4b2f5785ae
commit 81d2b744ba
6 changed files with 530 additions and 21 deletions

View File

@@ -0,0 +1,108 @@
package com.rosetta.messenger.data
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
/**
* 📨 Менеджер для пересылки сообщений (Forward)
*
* Логика как в десктопе:
* 1. Пользователь выбирает сообщения в чате
* 2. Нажимает Forward
* 3. Открывается список чатов
* 4. Выбирает чат куда переслать
* 5. Переходит в выбранный чат с сообщениями в Reply панели (как Forward)
*
* Singleton для передачи данных между экранами
*/
object ForwardManager {
/**
* Сообщение для пересылки
*/
data class ForwardMessage(
val messageId: String,
val text: String,
val timestamp: Long,
val isOutgoing: Boolean,
val senderPublicKey: String, // publicKey отправителя сообщения
val originalChatPublicKey: String // publicKey чата откуда пересылается
)
// Сообщения для пересылки
private val _forwardMessages = MutableStateFlow<List<ForwardMessage>>(emptyList())
val forwardMessages: StateFlow<List<ForwardMessage>> = _forwardMessages.asStateFlow()
// Флаг показа выбора чата
private val _showChatPicker = MutableStateFlow(false)
val showChatPicker: StateFlow<Boolean> = _showChatPicker.asStateFlow()
// Выбранный чат (publicKey собеседника)
private val _selectedChatPublicKey = MutableStateFlow<String?>(null)
val selectedChatPublicKey: StateFlow<String?> = _selectedChatPublicKey.asStateFlow()
/**
* Установить сообщения для пересылки и показать выбор чата
*/
fun setForwardMessages(
messages: List<ForwardMessage>,
showPicker: Boolean = true
) {
android.util.Log.d("ForwardManager", "📨 Setting forward messages: ${messages.size}")
_forwardMessages.value = messages
if (showPicker) {
_showChatPicker.value = true
}
}
/**
* Выбрать чат для пересылки
*/
fun selectChat(publicKey: String) {
android.util.Log.d("ForwardManager", "📨 Selected chat: $publicKey")
_selectedChatPublicKey.value = publicKey
_showChatPicker.value = false
}
/**
* Скрыть выбор чата (отмена)
*/
fun hideChatPicker() {
android.util.Log.d("ForwardManager", "📨 Hide chat picker")
_showChatPicker.value = false
}
/**
* Получить сообщения и очистить состояние
* Вызывается при открытии выбранного чата
*/
fun consumeForwardMessages(): List<ForwardMessage> {
val messages = _forwardMessages.value
android.util.Log.d("ForwardManager", "📨 Consuming forward messages: ${messages.size}")
return messages
}
/**
* Очистить все данные (после применения или отмены)
*/
fun clear() {
android.util.Log.d("ForwardManager", "📨 Clearing forward state")
_forwardMessages.value = emptyList()
_showChatPicker.value = false
_selectedChatPublicKey.value = null
}
/**
* Проверить есть ли сообщения для пересылки
*/
fun hasForwardMessages(): Boolean = _forwardMessages.value.isNotEmpty()
/**
* Проверить есть ли сообщения для конкретного чата
*/
fun hasForwardMessagesForChat(publicKey: String): Boolean {
val selectedKey = _selectedChatPublicKey.value
return selectedKey == publicKey && _forwardMessages.value.isNotEmpty()
}
}

View File

@@ -600,8 +600,11 @@ class MessageRepository private constructor(private val context: Context) {
}
/**
* Сериализация attachments в JSON с расшифровкой MESSAGES blob
* Для MESSAGES типа blob расшифровывается и сохраняется в preview (как в RN)
* Сериализация attachments в JSON с RE-ENCRYPTION для хранения в БД
* Для MESSAGES типа:
* 1. Расшифровываем blob с ChaCha ключом сообщения
* 2. Re-encrypt с приватным ключом (как в Desktop Архиве)
* 3. Сохраняем зашифрованный blob в БД
*/
private fun serializeAttachmentsWithDecryption(
attachments: List<MessageAttachment>,
@@ -614,9 +617,10 @@ class MessageRepository private constructor(private val context: Context) {
for (attachment in attachments) {
val jsonObj = JSONObject()
// Для MESSAGES типа расшифровываем blob
// Для MESSAGES типа расшифровываем и re-encrypt
if (attachment.type == AttachmentType.MESSAGES && attachment.blob.isNotEmpty()) {
try {
// 1. Расшифровываем с ChaCha ключом сообщения
val decryptedBlob = MessageCrypto.decryptAttachmentBlob(
attachment.blob,
encryptedKey,
@@ -624,11 +628,14 @@ class MessageRepository private constructor(private val context: Context) {
)
if (decryptedBlob != null) {
// Сохраняем расшифрованный JSON в preview (как в RN)
// 2. Re-encrypt с приватным ключом для хранения (как в Desktop Архиве)
val reEncryptedBlob = CryptoManager.encryptWithPassword(decryptedBlob, privateKey)
// 3. Сохраняем ЗАШИФРОВАННЫЙ blob в БД
jsonObj.put("id", attachment.id)
jsonObj.put("blob", decryptedBlob) // Расшифрованный JSON
jsonObj.put("blob", reEncryptedBlob) // 🔒 Зашифрован приватным ключом!
jsonObj.put("type", attachment.type.value)
jsonObj.put("preview", decryptedBlob) // Для совместимости
jsonObj.put("preview", attachment.preview)
jsonObj.put("width", attachment.width)
jsonObj.put("height", attachment.height)
} else {