Remove unnecessary logging statements across various components to clean up code and improve readability. This includes removing debug, error, and warning logs from attachment handling, image processing, media loading, and profile management functionalities. Additionally, a script has been added to automate the removal of log statements from Kotlin files.
This commit is contained in:
@@ -110,7 +110,6 @@ private suspend fun performUnlock(
|
||||
name = account.name
|
||||
)
|
||||
|
||||
android.util.Log.d("UnlockScreen", "🔐 Starting connection and authentication...")
|
||||
|
||||
// Connect to server and authenticate
|
||||
ProtocolManager.connect()
|
||||
@@ -130,12 +129,10 @@ private suspend fun performUnlock(
|
||||
|
||||
kotlinx.coroutines.delay(300)
|
||||
|
||||
android.util.Log.d("UnlockScreen", "🔐 Starting authentication...")
|
||||
ProtocolManager.authenticate(account.publicKey, privateKeyHash)
|
||||
|
||||
accountManager.setCurrentAccount(account.publicKey)
|
||||
|
||||
android.util.Log.d("UnlockScreen", "✅ Unlock complete")
|
||||
onSuccess(decryptedAccount)
|
||||
} catch (e: Exception) {
|
||||
onError("Failed to unlock: ${e.message}")
|
||||
@@ -181,7 +178,6 @@ fun UnlockScreen(
|
||||
val biometricPrefs = remember { BiometricPreferences(context) }
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
android.util.Log.d("UnlockScreen", "🎬 SCREEN INITIALIZED - Activity: $activity")
|
||||
|
||||
var password by remember { mutableStateOf("") }
|
||||
var passwordVisible by remember { mutableStateOf(false) }
|
||||
@@ -203,7 +199,6 @@ fun UnlockScreen(
|
||||
|
||||
// Load accounts and pre-select last used account
|
||||
LaunchedEffect(Unit) {
|
||||
android.util.Log.d("UnlockScreen", "📦 LOADING DATA...")
|
||||
// ⚡ СИНХРОННОЕ чтение последнего аккаунта ДО загрузки списка
|
||||
val lastLoggedKey = accountManager.getLastLoggedPublicKey()
|
||||
|
||||
@@ -243,38 +238,27 @@ fun UnlockScreen(
|
||||
}
|
||||
savedPasswordsMap = passwords
|
||||
|
||||
android.util.Log.d("UnlockScreen", "🔐 Biometric available: $biometricAvailable")
|
||||
android.util.Log.d("UnlockScreen", "🔐 Biometric enabled: $isBiometricEnabled")
|
||||
android.util.Log.d("UnlockScreen", "🔐 Activity: $activity")
|
||||
android.util.Log.d("UnlockScreen", "🔐 Selected account: ${selectedAccount?.publicKey}")
|
||||
android.util.Log.d("UnlockScreen", "🔐 Saved passwords count: ${passwords.size}")
|
||||
android.util.Log.d("UnlockScreen", "🔐 Has password for selected: ${selectedAccount?.let { passwords.containsKey(it.publicKey) }}")
|
||||
|
||||
dataLoaded = true
|
||||
|
||||
android.util.Log.d("UnlockScreen", "✅ DATA LOADED")
|
||||
}
|
||||
|
||||
// Функция для запуска биометрической разблокировки
|
||||
fun tryBiometricUnlock() {
|
||||
if (selectedAccount == null || activity == null) {
|
||||
android.util.Log.d("UnlockScreen", "❌ Cannot start biometric: no account or activity")
|
||||
return
|
||||
}
|
||||
|
||||
val encryptedPassword = savedPasswordsMap[selectedAccount!!.publicKey]
|
||||
if (encryptedPassword == null) {
|
||||
android.util.Log.d("UnlockScreen", "❌ No saved password for account")
|
||||
return
|
||||
}
|
||||
|
||||
android.util.Log.d("UnlockScreen", "🔐 Starting biometric unlock...")
|
||||
|
||||
biometricManager.decryptPassword(
|
||||
activity = activity,
|
||||
encryptedData = encryptedPassword,
|
||||
onSuccess = { decryptedPassword ->
|
||||
android.util.Log.d("UnlockScreen", "✅ Biometric success, unlocking...")
|
||||
scope.launch {
|
||||
performUnlock(
|
||||
selectedAccount = selectedAccount,
|
||||
@@ -289,11 +273,9 @@ fun UnlockScreen(
|
||||
}
|
||||
},
|
||||
onError = { errorMessage ->
|
||||
android.util.Log.e("UnlockScreen", "❌ Biometric error: $errorMessage")
|
||||
error = errorMessage
|
||||
},
|
||||
onCancel = {
|
||||
android.util.Log.d("UnlockScreen", "🚫 Biometric cancelled")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -198,7 +198,6 @@ fun ChatDetailScreen(
|
||||
contract = ActivityResultContracts.TakePicture()
|
||||
) { success ->
|
||||
if (success && cameraImageUri != null) {
|
||||
android.util.Log.d("ChatDetailScreen", "📷 Photo captured: $cameraImageUri")
|
||||
// Открываем редактор вместо прямой отправки
|
||||
pendingCameraPhotoUri = cameraImageUri
|
||||
}
|
||||
@@ -210,13 +209,11 @@ fun ChatDetailScreen(
|
||||
) { uri ->
|
||||
if (uri != null) {
|
||||
scope.launch {
|
||||
android.util.Log.d("ChatDetailScreen", "📄 File selected: $uri")
|
||||
val fileName = MediaUtils.getFileName(context, uri)
|
||||
val fileSize = MediaUtils.getFileSize(context, uri)
|
||||
|
||||
// Проверяем размер файла
|
||||
if (fileSize > MediaUtils.MAX_FILE_SIZE_MB * 1024 * 1024) {
|
||||
android.util.Log.w("ChatDetailScreen", "📄 File too large: ${fileSize / 1024 / 1024}MB")
|
||||
// TODO: Показать ошибку
|
||||
return@launch
|
||||
}
|
||||
@@ -1994,7 +1991,6 @@ fun ChatDetailScreen(
|
||||
currentUserPublicKey = currentUserPublicKey,
|
||||
onMediaSelected = { selectedMedia ->
|
||||
// 📸 Открываем edit screen для выбранных изображений
|
||||
android.util.Log.d("ChatDetailScreen", "📸 Opening editor for ${selectedMedia.size} media items")
|
||||
|
||||
// Собираем URI изображений (пока без видео)
|
||||
val imageUris = selectedMedia
|
||||
@@ -2020,7 +2016,6 @@ fun ChatDetailScreen(
|
||||
)
|
||||
cameraLauncher.launch(cameraImageUri!!)
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("ChatDetailScreen", "📷 Failed to create camera file", e)
|
||||
}
|
||||
},
|
||||
onOpenFilePicker = {
|
||||
@@ -2029,7 +2024,6 @@ fun ChatDetailScreen(
|
||||
},
|
||||
onAvatarClick = {
|
||||
// 👤 Отправляем свой аватар (как в desktop)
|
||||
android.util.Log.d("ChatDetailScreen", "👤 Sending avatar...")
|
||||
viewModel.sendAvatarMessage()
|
||||
}
|
||||
)
|
||||
|
||||
@@ -4,7 +4,6 @@ import android.app.Application
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.rosetta.messenger.crypto.CryptoManager
|
||||
@@ -334,8 +333,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
* Открыть диалог
|
||||
*/
|
||||
fun openDialog(publicKey: String, title: String = "", username: String = "") {
|
||||
Log.d(TAG, "🚪 openDialog START: publicKey=$publicKey, title=$title")
|
||||
Log.d(TAG, "🔍 Current state: opponentKey=$opponentKey, myPublicKey=$myPublicKey")
|
||||
|
||||
// 🔥 ВСЕГДА перезагружаем данные - не кешируем, т.к. диалог мог быть удалён
|
||||
// if (opponentKey == publicKey) {
|
||||
@@ -354,11 +351,9 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
val forwardMessages = ForwardManager.getForwardMessagesForChat(publicKey)
|
||||
val hasForward = forwardMessages.isNotEmpty()
|
||||
if (hasForward) {
|
||||
Log.d(TAG, "📨 Has forward messages: ${forwardMessages.size}")
|
||||
}
|
||||
|
||||
// Сбрасываем состояние
|
||||
Log.d(TAG, "🔄 Resetting state, setting messages to empty")
|
||||
_messages.value = emptyList()
|
||||
_opponentOnline.value = false
|
||||
_opponentTyping.value = false
|
||||
@@ -370,7 +365,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
subscribedToOnlineStatus = false // 🔥 Сбрасываем флаг подписки при смене диалога
|
||||
isDialogActive = true // 🔥 Диалог активен!
|
||||
|
||||
Log.d(TAG, "📊 State after reset: messagesCount=${_messages.value.size}, isDialogActive=$isDialogActive")
|
||||
|
||||
// 📨 Применяем Forward сообщения СРАЗУ после сброса
|
||||
if (hasForward) {
|
||||
@@ -427,13 +421,11 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
val opponent = opponentKey ?: return
|
||||
val dialogKey = getDialogKey(account, opponent)
|
||||
|
||||
Log.d(TAG, "📥 loadMessagesFromDatabase START: account=$account, opponent=$opponent, dialogKey=$dialogKey")
|
||||
|
||||
// 📁 Проверяем является ли это Saved Messages
|
||||
val isSavedMessages = (opponent == account)
|
||||
|
||||
if (isLoadingMessages) {
|
||||
Log.d(TAG, "⚠️ loadMessagesFromDatabase SKIP - already loading")
|
||||
return
|
||||
}
|
||||
isLoadingMessages = true
|
||||
@@ -442,10 +434,8 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
try {
|
||||
// 🔥 МГНОВЕННАЯ загрузка из кэша если есть!
|
||||
val cachedMessages = dialogMessagesCache[dialogKey]
|
||||
Log.d(TAG, "📦 Cache check: dialogKey=$dialogKey, cachedCount=${cachedMessages?.size ?: 0}")
|
||||
|
||||
if (cachedMessages != null && cachedMessages.isNotEmpty()) {
|
||||
Log.d(TAG, "✅ Using cached messages: ${cachedMessages.size}")
|
||||
withContext(Dispatchers.Main.immediate) {
|
||||
_messages.value = cachedMessages
|
||||
_isLoading.value = false
|
||||
@@ -466,11 +456,9 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
messageDao.getMessageCount(account, dialogKey)
|
||||
}
|
||||
|
||||
Log.d(TAG, "📊 DB message count: totalCount=$totalCount, isSavedMessages=$isSavedMessages")
|
||||
|
||||
if (totalCount == 0) {
|
||||
// Пустой диалог - сразу показываем пустое состояние без скелетона
|
||||
Log.d(TAG, "📭 Empty dialog - showing empty state")
|
||||
withContext(Dispatchers.Main.immediate) {
|
||||
_messages.value = emptyList()
|
||||
_isLoading.value = false
|
||||
@@ -481,24 +469,20 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
// 🔥 Есть сообщения - показываем скелетон и загружаем с задержкой для анимации
|
||||
if (delayMs > 0) {
|
||||
Log.d(TAG, "⏳ Delaying load for ${delayMs}ms")
|
||||
withContext(Dispatchers.Main.immediate) {
|
||||
_isLoading.value = true // Показываем скелетон
|
||||
}
|
||||
delay(delayMs)
|
||||
Log.d(TAG, "✅ Delay finished, starting DB load")
|
||||
}
|
||||
|
||||
|
||||
// 🔥 Получаем первую страницу - используем специальный метод для saved messages
|
||||
Log.d(TAG, "🔍 Querying DB for messages...")
|
||||
val entities = if (isSavedMessages) {
|
||||
messageDao.getMessagesForSavedDialog(account, limit = PAGE_SIZE, offset = 0)
|
||||
} else {
|
||||
messageDao.getMessages(account, dialogKey, limit = PAGE_SIZE, offset = 0)
|
||||
}
|
||||
|
||||
Log.d(TAG, "📋 Loaded entities from DB: count=${entities.size}")
|
||||
|
||||
hasMoreMessages = entities.size >= PAGE_SIZE
|
||||
currentOffset = entities.size
|
||||
@@ -517,11 +501,9 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(TAG, "✅ Decrypted messages: count=${messages.size}")
|
||||
|
||||
// 🔥 Сохраняем в кэш для мгновенной повторной загрузки!
|
||||
dialogMessagesCache[dialogKey] = messages.toList()
|
||||
Log.d(TAG, "💾 Saved to cache: dialogKey=$dialogKey, count=${messages.size}")
|
||||
|
||||
// 🔥 СРАЗУ обновляем UI - пользователь видит сообщения мгновенно
|
||||
// НО сохраняем оптимистичные сообщения (SENDING), которые ещё не в БД
|
||||
@@ -558,9 +540,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
isLoadingMessages = false
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "❌ Error loading messages from DB", e)
|
||||
Log.e(TAG, "❌ Exception details: ${e.message}")
|
||||
Log.e(TAG, "❌ Stack trace: ${e.stackTraceToString()}")
|
||||
withContext(Dispatchers.Main.immediate) {
|
||||
_isLoading.value = false
|
||||
}
|
||||
@@ -828,7 +807,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
result
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("ChatViewModel", "Failed to parse attachments", e)
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
@@ -985,7 +963,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "📸 Failed to parse reply attachments from JSON: ${e.message}")
|
||||
}
|
||||
|
||||
|
||||
@@ -1256,13 +1233,11 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
val isFirstMessage = messageDao.getMessageCount(sender, sender, recipient) == 0
|
||||
if (isFirstMessage) {
|
||||
try {
|
||||
Log.d(TAG, "📸 First message to user - checking for avatar...")
|
||||
// Получаем свой аватар из AvatarRepository
|
||||
val avatarDao = database.avatarDao()
|
||||
val myAvatar = avatarDao.getLatestAvatar(sender)
|
||||
|
||||
if (myAvatar != null) {
|
||||
Log.d(TAG, "📸 Found my avatar, path: ${myAvatar.avatar}")
|
||||
// Читаем и расшифровываем аватар
|
||||
val avatarBlob = com.rosetta.messenger.utils.AvatarFileManager.readAvatar(
|
||||
getApplication(),
|
||||
@@ -1270,10 +1245,8 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
)
|
||||
|
||||
if (avatarBlob != null && avatarBlob.isNotEmpty()) {
|
||||
Log.d(TAG, "📸 Avatar blob read successfully, length: ${avatarBlob.length}")
|
||||
// Шифруем аватар с ChaCha ключом для отправки
|
||||
val encryptedAvatarBlob = MessageCrypto.encryptReplyBlob(avatarBlob, plainKeyAndNonce)
|
||||
Log.d(TAG, "📸 Avatar encrypted, length: ${encryptedAvatarBlob.length}")
|
||||
|
||||
val avatarAttachmentId = "avatar_${timestamp}"
|
||||
messageAttachments.add(MessageAttachment(
|
||||
@@ -1282,15 +1255,11 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
type = AttachmentType.AVATAR,
|
||||
preview = ""
|
||||
))
|
||||
Log.d(TAG, "📸 ✅ Avatar attached to first message!")
|
||||
} else {
|
||||
Log.w(TAG, "📸 Avatar blob is null or empty!")
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "📸 No avatar found for current user")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "📸 Failed to attach avatar to first message", e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1427,11 +1396,9 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
val privateKey = myPrivateKey
|
||||
|
||||
if (recipient == null || sender == null || privateKey == null) {
|
||||
Log.e(TAG, "📸 Cannot send image: missing keys")
|
||||
return
|
||||
}
|
||||
if (isSending) {
|
||||
Log.w(TAG, "📸 Already sending message")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1462,7 +1429,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
_messages.value = _messages.value + optimisticMessage
|
||||
_inputText.value = ""
|
||||
|
||||
Log.d(TAG, "📸 Sending image message: id=$messageId")
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
@@ -1485,9 +1451,7 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
var uploadTag = ""
|
||||
|
||||
if (!isSavedMessages) {
|
||||
Log.d(TAG, "📤 Uploading image to Transport Server...")
|
||||
uploadTag = TransportManager.uploadFile(attachmentId, encryptedImageBlob)
|
||||
Log.d(TAG, "📤 Upload complete, tag: $uploadTag")
|
||||
}
|
||||
|
||||
// Preview содержит tag::blurhash (как в desktop)
|
||||
@@ -1556,10 +1520,8 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
)
|
||||
|
||||
saveDialog(if (text.isNotEmpty()) text else "photo", timestamp)
|
||||
Log.d(TAG, "📸 ✅ Image message sent successfully")
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "📸 ❌ Failed to send image message", e)
|
||||
withContext(Dispatchers.Main) {
|
||||
updateMessageStatus(messageId, MessageStatus.SENT)
|
||||
}
|
||||
@@ -1590,11 +1552,9 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
val privateKey = myPrivateKey
|
||||
|
||||
if (recipient == null || sender == null || privateKey == null) {
|
||||
Log.e(TAG, "🖼️ Cannot send image group: missing keys")
|
||||
return
|
||||
}
|
||||
if (isSending) {
|
||||
Log.w(TAG, "🖼️ Already sending message")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1628,7 +1588,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
_messages.value = _messages.value + optimisticMessage
|
||||
_inputText.value = ""
|
||||
|
||||
Log.d(TAG, "🖼️ Sending image group: ${images.size} images, id=$messageId")
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
@@ -1687,7 +1646,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
put("height", imageData.height)
|
||||
})
|
||||
|
||||
Log.d(TAG, "🖼️ Image $index uploaded: tag=${uploadTag?.take(20) ?: "null"}")
|
||||
}
|
||||
|
||||
// Создаём пакет
|
||||
@@ -1725,10 +1683,8 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
)
|
||||
|
||||
saveDialog(if (text.isNotEmpty()) text else "📷 ${images.size} photos", timestamp)
|
||||
Log.d(TAG, "🖼️ ✅ Image group sent successfully")
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "🖼️ ❌ Failed to send image group", e)
|
||||
withContext(Dispatchers.Main) {
|
||||
updateMessageStatus(messageId, MessageStatus.SENT)
|
||||
}
|
||||
@@ -1751,11 +1707,9 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
val privateKey = myPrivateKey
|
||||
|
||||
if (recipient == null || sender == null || privateKey == null) {
|
||||
Log.e(TAG, "📄 Cannot send file: missing keys")
|
||||
return
|
||||
}
|
||||
if (isSending) {
|
||||
Log.w(TAG, "📄 Already sending message")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1785,7 +1739,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
_messages.value = _messages.value + optimisticMessage
|
||||
_inputText.value = ""
|
||||
|
||||
Log.d(TAG, "📄 Sending file message: $fileName ($fileSize bytes)")
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
@@ -1807,9 +1760,7 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
var uploadTag = ""
|
||||
|
||||
if (!isSavedMessages) {
|
||||
Log.d(TAG, "📤 Uploading file to Transport Server...")
|
||||
uploadTag = TransportManager.uploadFile(attachmentId, encryptedFileBlob)
|
||||
Log.d(TAG, "📤 Upload complete, tag: $uploadTag")
|
||||
}
|
||||
|
||||
// Preview содержит tag::size::name (как в desktop)
|
||||
@@ -1865,10 +1816,8 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
)
|
||||
|
||||
saveDialog(if (text.isNotEmpty()) text else "file", timestamp)
|
||||
Log.d(TAG, "📄 ✅ File message sent successfully")
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "📄 ❌ Failed to send file message", e)
|
||||
withContext(Dispatchers.Main) {
|
||||
updateMessageStatus(messageId, MessageStatus.SENT)
|
||||
}
|
||||
@@ -1888,11 +1837,9 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
val userPrivateKey = myPrivateKey
|
||||
|
||||
if (recipient == null || sender == null || userPrivateKey == null) {
|
||||
Log.e(TAG, "👤 Cannot send avatar: missing keys")
|
||||
return
|
||||
}
|
||||
if (isSending) {
|
||||
Log.w(TAG, "👤 Already sending message")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1903,13 +1850,11 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
Log.d(TAG, "👤 Fetching current user avatar...")
|
||||
// Получаем свой аватар из AvatarRepository
|
||||
val avatarDao = database.avatarDao()
|
||||
val myAvatar = avatarDao.getLatestAvatar(sender)
|
||||
|
||||
if (myAvatar == null) {
|
||||
Log.w(TAG, "👤 No avatar found for current user")
|
||||
withContext(Dispatchers.Main) {
|
||||
android.widget.Toast.makeText(
|
||||
getApplication(),
|
||||
@@ -1921,7 +1866,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
return@launch
|
||||
}
|
||||
|
||||
Log.d(TAG, "👤 Found avatar, path: ${myAvatar.avatar}")
|
||||
// Читаем и расшифровываем аватар
|
||||
val avatarBlob = com.rosetta.messenger.utils.AvatarFileManager.readAvatar(
|
||||
getApplication(),
|
||||
@@ -1929,7 +1873,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
)
|
||||
|
||||
if (avatarBlob == null || avatarBlob.isEmpty()) {
|
||||
Log.w(TAG, "👤 Avatar blob is null or empty!")
|
||||
withContext(Dispatchers.Main) {
|
||||
android.widget.Toast.makeText(
|
||||
getApplication(),
|
||||
@@ -1941,8 +1884,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
return@launch
|
||||
}
|
||||
|
||||
Log.d(TAG, "👤 Avatar blob read successfully, length: ${avatarBlob.length}")
|
||||
Log.d(TAG, "👤 Avatar blob first 100 chars: ${avatarBlob.take(100)}")
|
||||
|
||||
// 🔥 КРИТИЧНО: Desktop ожидает полный data URL, а не просто Base64!
|
||||
// Добавляем префикс если его нет
|
||||
@@ -1951,7 +1892,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
} else {
|
||||
"data:image/png;base64,$avatarBlob"
|
||||
}
|
||||
Log.d(TAG, "👤 Avatar data URL length: ${avatarDataUrl.length}")
|
||||
|
||||
// Генерируем blurhash для preview (как на desktop)
|
||||
val avatarBlurhash = withContext(Dispatchers.IO) {
|
||||
@@ -1963,11 +1903,9 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
""
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to generate blurhash for avatar: ${e.message}")
|
||||
""
|
||||
}
|
||||
}
|
||||
Log.d(TAG, "👤 Avatar blurhash generated: ${avatarBlurhash.take(20)}...")
|
||||
|
||||
// 1. 🚀 Optimistic UI
|
||||
val optimisticMessage = ChatMessage(
|
||||
@@ -2001,7 +1939,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
// НЕ с AVATAR_PASSWORD! AVATAR_PASSWORD используется только для локального хранения
|
||||
// Используем avatarDataUrl (с префиксом data:image/...) а не avatarBlob!
|
||||
val encryptedAvatarBlob = MessageCrypto.encryptReplyBlob(avatarDataUrl, plainKeyAndNonce)
|
||||
Log.d(TAG, "👤 Avatar encrypted with ChaCha key, length: ${encryptedAvatarBlob.length}")
|
||||
|
||||
val avatarAttachmentId = "avatar_$timestamp"
|
||||
|
||||
@@ -2011,18 +1948,14 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
if (!isSavedMessages) {
|
||||
try {
|
||||
Log.d(TAG, "👤 📤 Uploading avatar to Transport Server...")
|
||||
uploadTag = TransportManager.uploadFile(avatarAttachmentId, encryptedAvatarBlob)
|
||||
Log.d(TAG, "👤 📤 Upload complete, tag: $uploadTag")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "👤 ❌ Failed to upload avatar to Transport Server", e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
// Preview содержит tag::blurhash (как в desktop)
|
||||
val previewWithTag = if (uploadTag.isNotEmpty()) "$uploadTag::$avatarBlurhash" else avatarBlurhash
|
||||
Log.d(TAG, "👤 Preview with tag: ${previewWithTag.take(50)}...")
|
||||
|
||||
val avatarAttachment = MessageAttachment(
|
||||
id = avatarAttachmentId,
|
||||
@@ -2083,12 +2016,9 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
saveDialog("\$a=Avatar", timestamp)
|
||||
|
||||
Log.d(TAG, "👤 💾 Avatar message saved to database: messageId=$messageId")
|
||||
|
||||
Log.d(TAG, "👤 ✅ Avatar message sent successfully!")
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "👤 ❌ Failed to send avatar message", e)
|
||||
withContext(Dispatchers.Main) {
|
||||
updateMessageStatus(messageId, MessageStatus.SENT)
|
||||
android.widget.Toast.makeText(
|
||||
@@ -2419,7 +2349,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e(TAG, "Failed to clear chat history", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2437,7 +2366,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
val bytes = Base64.decode(cleanBase64, Base64.DEFAULT)
|
||||
BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to decode base64 to bitmap: ${e.message}")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,7 +127,6 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
||||
// 📁 НЕ загружаем для Saved Messages
|
||||
val isSavedMessages = (dialog.account == dialog.opponentKey)
|
||||
if (!isSavedMessages && (dialog.opponentTitle.isEmpty() || dialog.opponentTitle == dialog.opponentKey || dialog.opponentTitle == dialog.opponentKey.take(7))) {
|
||||
android.util.Log.d("ChatsListVM", "🔍 Dialog needs user info: ${dialog.opponentKey.take(16)}... title='${dialog.opponentTitle}'")
|
||||
loadUserInfoForDialog(dialog.opponentKey)
|
||||
}
|
||||
|
||||
@@ -166,12 +165,10 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
||||
} else null
|
||||
} else null
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("ChatsListVM", "Failed to parse attachments", e)
|
||||
null
|
||||
}
|
||||
|
||||
// 🔥 Лог для отладки - показываем и старые и новые значения
|
||||
android.util.Log.d("ChatsListVM", "📊 Dialog ${dialog.opponentKey.take(16)}... | OLD: fromMe=${dialog.lastMessageFromMe}, del=${dialog.lastMessageDelivered}, read=${dialog.lastMessageRead} | NEW: fromMe=$actualFromMe, del=$actualDelivered, read=$actualRead | attachment=$attachmentType")
|
||||
|
||||
DialogUiModel(
|
||||
id = dialog.id,
|
||||
@@ -212,13 +209,11 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
||||
dialogDao.getRequestsFlow(publicKey)
|
||||
.flowOn(Dispatchers.IO)
|
||||
.map { requestsList ->
|
||||
android.util.Log.d("ChatsListVM", "📬 getRequestsFlow emitted: ${requestsList.size} requests")
|
||||
requestsList.map { dialog ->
|
||||
// 🔥 Загружаем информацию о пользователе если её нет
|
||||
// 📁 НЕ загружаем для Saved Messages
|
||||
val isSavedMessages = (dialog.account == dialog.opponentKey)
|
||||
if (!isSavedMessages && (dialog.opponentTitle.isEmpty() || dialog.opponentTitle == dialog.opponentKey)) {
|
||||
android.util.Log.d("ChatsListVM", "📬 Request needs user info: ${dialog.opponentKey.take(16)}... title='${dialog.opponentTitle}'")
|
||||
loadUserInfoForRequest(dialog.opponentKey)
|
||||
}
|
||||
|
||||
@@ -250,7 +245,6 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
||||
} else null
|
||||
} else null
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("ChatsListVM", "Failed to parse attachments in request", e)
|
||||
null
|
||||
}
|
||||
|
||||
@@ -476,18 +470,15 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
||||
private fun loadUserInfoForDialog(publicKey: String) {
|
||||
// 📁 Не запрашиваем информацию о самом себе (Saved Messages)
|
||||
if (publicKey == currentAccount) {
|
||||
android.util.Log.d("ChatsListVM", "📁 Skipping loadUserInfoForDialog for Saved Messages")
|
||||
return
|
||||
}
|
||||
|
||||
// 🔥 Не запрашиваем если уже запрашивали
|
||||
if (requestedUserInfoKeys.contains(publicKey)) {
|
||||
android.util.Log.d("ChatsListVM", "⏭️ Skipping loadUserInfoForDialog - already requested for ${publicKey.take(16)}...")
|
||||
return
|
||||
}
|
||||
requestedUserInfoKeys.add(publicKey)
|
||||
|
||||
android.util.Log.d("ChatsListVM", "🔍 loadUserInfoForDialog: ${publicKey.take(16)}...")
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
@@ -499,7 +490,6 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
||||
// 🔥 ВАЖНО: Используем хеш ключа, как в MessageRepository.requestUserInfo
|
||||
val privateKeyHash = CryptoManager.generatePrivateKeyHash(currentUserPrivateKey)
|
||||
|
||||
android.util.Log.d("ChatsListVM", "📤 Sending PacketSearch for user info: ${publicKey.take(16)}...")
|
||||
|
||||
// Запрашиваем информацию о пользователе с сервера
|
||||
val packet = PacketSearch().apply {
|
||||
@@ -508,7 +498,6 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
||||
}
|
||||
ProtocolManager.send(packet)
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("ChatsListVM", "❌ loadUserInfoForRequest error: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.rosetta.messenger.ui.chats
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.rosetta.messenger.network.PacketSearch
|
||||
@@ -48,43 +47,35 @@ class SearchUsersViewModel : ViewModel() {
|
||||
val currentQuery = lastSearchedText
|
||||
val responseSearch = packet.search
|
||||
|
||||
Log.d(TAG, "📥 PacketSearch received: ${packet.users.size} users, search='$responseSearch', ourQuery='$currentQuery'")
|
||||
|
||||
// Принимаем ответ только если:
|
||||
// 1. search в ответе совпадает с нашим запросом, ИЛИ
|
||||
// 2. search пустой но мы ждём ответ (lastSearchedText не пустой)
|
||||
// НО: если search пустой и мы НЕ ждём ответ - игнорируем
|
||||
if (responseSearch.isEmpty() && currentQuery.isEmpty()) {
|
||||
Log.d(TAG, "📥 Ignoring empty search response - no active search")
|
||||
return@handler
|
||||
}
|
||||
|
||||
// Если search не пустой и не совпадает с нашим запросом - игнорируем
|
||||
if (responseSearch.isNotEmpty() && responseSearch != currentQuery) {
|
||||
Log.d(TAG, "📥 Ignoring search response - search mismatch: '$responseSearch' != '$currentQuery'")
|
||||
return@handler
|
||||
}
|
||||
|
||||
Log.d(TAG, "📥 ACCEPTED PacketSearch response: ${packet.users.size} users")
|
||||
packet.users.forEachIndexed { index, user ->
|
||||
Log.d(TAG, " [$index] publicKey=${user.publicKey.take(16)}... title=${user.title} username=${user.username}")
|
||||
}
|
||||
_searchResults.value = packet.users
|
||||
_isSearching.value = false
|
||||
Log.d(TAG, "📥 Updated searchResults, isSearching=false")
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
// Регистрируем обработчик пакетов поиска
|
||||
Log.d(TAG, "🟢 INIT: Registering searchPacketHandler for 0x03")
|
||||
ProtocolManager.waitPacket(0x03, searchPacketHandler)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
// Отписываемся от пакетов при уничтожении ViewModel
|
||||
Log.d(TAG, "🔴 onCleared: Unregistering searchPacketHandler")
|
||||
ProtocolManager.unwaitPacket(0x03, searchPacketHandler)
|
||||
searchJob?.cancel()
|
||||
}
|
||||
@@ -101,7 +92,6 @@ class SearchUsersViewModel : ViewModel() {
|
||||
* Аналогично handleSearch в React Native
|
||||
*/
|
||||
fun onSearchQueryChange(query: String) {
|
||||
Log.d(TAG, "🔍 onSearchQueryChange: query='$query' lastSearchedText='$lastSearchedText'")
|
||||
_searchQuery.value = query
|
||||
|
||||
// Отменяем предыдущий поиск
|
||||
@@ -109,7 +99,6 @@ class SearchUsersViewModel : ViewModel() {
|
||||
|
||||
// Если пустой запрос - очищаем результаты
|
||||
if (query.trim().isEmpty()) {
|
||||
Log.d(TAG, "🔍 Empty query, clearing results")
|
||||
_searchResults.value = emptyList()
|
||||
_isSearching.value = false
|
||||
lastSearchedText = ""
|
||||
@@ -118,36 +107,30 @@ class SearchUsersViewModel : ViewModel() {
|
||||
|
||||
// Если текст уже был найден - не повторяем поиск
|
||||
if (query == lastSearchedText) {
|
||||
Log.d(TAG, "🔍 Query same as lastSearchedText, skipping")
|
||||
return
|
||||
}
|
||||
|
||||
// Показываем индикатор загрузки
|
||||
_isSearching.value = true
|
||||
Log.d(TAG, "🔍 Starting search job with 1s debounce")
|
||||
|
||||
// Запускаем поиск с задержкой 1 секунда (как в React Native)
|
||||
searchJob = viewModelScope.launch {
|
||||
delay(1000) // debounce
|
||||
|
||||
Log.d(TAG, "🔍 After debounce: protocolState=${ProtocolManager.state.value}")
|
||||
|
||||
// Проверяем состояние протокола
|
||||
if (ProtocolManager.state.value != ProtocolState.AUTHENTICATED) {
|
||||
Log.d(TAG, "🔍 Protocol not authenticated, aborting")
|
||||
_isSearching.value = false
|
||||
return@launch
|
||||
}
|
||||
|
||||
// Проверяем, не изменился ли запрос
|
||||
if (query != _searchQuery.value) {
|
||||
Log.d(TAG, "🔍 Query changed during debounce, aborting")
|
||||
return@launch
|
||||
}
|
||||
|
||||
lastSearchedText = query
|
||||
|
||||
Log.d(TAG, "📤 SENDING PacketSearch: query='$query' privateKeyHash=${privateKeyHash.take(16)}...")
|
||||
|
||||
// Создаем и отправляем пакет поиска
|
||||
val packetSearch = PacketSearch().apply {
|
||||
@@ -156,7 +139,6 @@ class SearchUsersViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
ProtocolManager.sendPacket(packetSearch)
|
||||
Log.d(TAG, "📤 PacketSearch sent!")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.rosetta.messenger.ui.chats.components
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.Image
|
||||
@@ -461,10 +460,6 @@ fun ImageAttachment(
|
||||
fillMaxSize: Boolean = false,
|
||||
onImageClick: (attachmentId: String) -> Unit = {}
|
||||
) {
|
||||
Log.d(
|
||||
TAG,
|
||||
"🖼️ ImageAttachment: id=${attachment.id}, isOutgoing=$isOutgoing, messageStatus=$messageStatus, showTimeOverlay=$showTimeOverlay"
|
||||
)
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
@@ -492,12 +487,10 @@ fun ImageAttachment(
|
||||
when {
|
||||
// 1. Если blob уже есть в памяти → DOWNLOADED
|
||||
attachment.blob.isNotEmpty() -> {
|
||||
Log.d(TAG, "📦 Blob already in memory for ${attachment.id}")
|
||||
DownloadStatus.DOWNLOADED
|
||||
}
|
||||
// 2. Если preview НЕ содержит UUID → это наш локальный файл → DOWNLOADED
|
||||
!isDownloadTag(attachment.preview) -> {
|
||||
Log.d(TAG, "📦 No download tag, local file for ${attachment.id}")
|
||||
DownloadStatus.DOWNLOADED
|
||||
}
|
||||
// 3. Есть UUID (download tag) → проверяем файловую систему
|
||||
@@ -511,10 +504,8 @@ fun ImageAttachment(
|
||||
senderPublicKey
|
||||
)
|
||||
if (hasLocal) {
|
||||
Log.d(TAG, "📦 Found local file for ${attachment.id}")
|
||||
DownloadStatus.DOWNLOADED
|
||||
} else {
|
||||
Log.d(TAG, "📥 Need to download ${attachment.id}")
|
||||
DownloadStatus.NOT_DOWNLOADED
|
||||
}
|
||||
}
|
||||
@@ -525,25 +516,14 @@ fun ImageAttachment(
|
||||
if (preview.isNotEmpty() && !isDownloadTag(preview)) {
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
Log.d(
|
||||
TAG,
|
||||
"🎨 Decoding blurhash: ${preview.take(30)}... (length: ${preview.length})"
|
||||
)
|
||||
blurhashBitmap = BlurHash.decode(preview, 200, 200)
|
||||
if (blurhashBitmap != null) {
|
||||
Log.d(TAG, "✅ Blurhash decoded successfully")
|
||||
} else {
|
||||
Log.w(TAG, "⚠️ Blurhash decode returned null")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "❌ Failed to decode blurhash: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.d(
|
||||
TAG,
|
||||
"⚠️ No valid blurhash preview (preview='${preview.take(20)}...', isDownloadTag=${isDownloadTag(preview)})"
|
||||
)
|
||||
}
|
||||
|
||||
// Загружаем изображение если статус DOWNLOADED
|
||||
@@ -551,11 +531,9 @@ fun ImageAttachment(
|
||||
withContext(Dispatchers.IO) {
|
||||
// 1. Сначала пробуем blob из памяти
|
||||
if (attachment.blob.isNotEmpty()) {
|
||||
Log.d(TAG, "🖼️ Loading image from blob")
|
||||
imageBitmap = base64ToBitmap(attachment.blob)
|
||||
} else {
|
||||
// 2. Читаем из файловой системы (как в Desktop getBlob)
|
||||
Log.d(TAG, "🖼️ Loading image from local file")
|
||||
val localBlob =
|
||||
AttachmentFileManager.readAttachment(
|
||||
context,
|
||||
@@ -565,9 +543,7 @@ fun ImageAttachment(
|
||||
)
|
||||
if (localBlob != null) {
|
||||
imageBitmap = base64ToBitmap(localBlob)
|
||||
Log.d(TAG, "✅ Image loaded from local file")
|
||||
} else {
|
||||
Log.w(TAG, "⚠️ Failed to read local file, need to download")
|
||||
downloadStatus = DownloadStatus.NOT_DOWNLOADED
|
||||
}
|
||||
}
|
||||
@@ -580,34 +556,22 @@ fun ImageAttachment(
|
||||
scope.launch {
|
||||
try {
|
||||
downloadStatus = DownloadStatus.DOWNLOADING
|
||||
Log.d(TAG, "=====================================")
|
||||
Log.d(TAG, "📥 Starting IMAGE download")
|
||||
Log.d(TAG, "📝 Attachment ID: ${attachment.id}")
|
||||
Log.d(TAG, "🏷️ Download tag: $downloadTag")
|
||||
Log.d(TAG, "👤 Sender public key: ${senderPublicKey.take(16)}...")
|
||||
Log.d(TAG, "=====================================")
|
||||
|
||||
// Скачиваем зашифрованный контент
|
||||
Log.d(TAG, "⬇️ Downloading encrypted content from CDN...")
|
||||
val startTime = System.currentTimeMillis()
|
||||
val encryptedContent = TransportManager.downloadFile(attachment.id, downloadTag)
|
||||
val downloadTime = System.currentTimeMillis() - startTime
|
||||
Log.d(TAG, "✅ Downloaded ${encryptedContent.length} chars in ${downloadTime}ms")
|
||||
downloadProgress = 0.5f
|
||||
|
||||
downloadStatus = DownloadStatus.DECRYPTING
|
||||
Log.d(TAG, "🔓 Starting decryption...")
|
||||
|
||||
// КРИТИЧНО: chachaKey ЗАШИФРОВАН в БД (как в Desktop)
|
||||
// Сначала расшифровываем его, получаем raw bytes
|
||||
Log.d(TAG, "🔑 Decrypting ChaCha key from sender...")
|
||||
val decryptedKeyAndNonce =
|
||||
MessageCrypto.decryptKeyFromSender(chachaKey, privateKey)
|
||||
Log.d(TAG, "🔑 ChaCha key decrypted: ${decryptedKeyAndNonce.size} bytes")
|
||||
|
||||
// Используем decryptAttachmentBlobWithPlainKey который правильно конвертирует
|
||||
// bytes в password
|
||||
Log.d(TAG, "🔓 Decrypting image blob with PBKDF2...")
|
||||
val decryptStartTime = System.currentTimeMillis()
|
||||
val decrypted =
|
||||
MessageCrypto.decryptAttachmentBlobWithPlainKey(
|
||||
@@ -615,21 +579,13 @@ fun ImageAttachment(
|
||||
decryptedKeyAndNonce
|
||||
)
|
||||
val decryptTime = System.currentTimeMillis() - decryptStartTime
|
||||
Log.d(TAG, "🔓 Decryption completed in ${decryptTime}ms")
|
||||
downloadProgress = 0.8f
|
||||
|
||||
if (decrypted != null) {
|
||||
Log.d(TAG, "✅ Decrypted blob: ${decrypted.length} chars")
|
||||
withContext(Dispatchers.IO) {
|
||||
Log.d(TAG, "🖼️ Converting to bitmap...")
|
||||
imageBitmap = base64ToBitmap(decrypted)
|
||||
Log.d(
|
||||
TAG,
|
||||
"✅ Bitmap created: ${imageBitmap?.width}x${imageBitmap?.height}"
|
||||
)
|
||||
|
||||
// 💾 Сохраняем в файловую систему (как в Desktop)
|
||||
Log.d(TAG, "💾 Saving to local storage...")
|
||||
val saved =
|
||||
AttachmentFileManager.saveAttachment(
|
||||
context = context,
|
||||
@@ -638,32 +594,18 @@ fun ImageAttachment(
|
||||
publicKey = senderPublicKey,
|
||||
privateKey = privateKey
|
||||
)
|
||||
Log.d(TAG, "💾 Image saved to local storage: $saved")
|
||||
}
|
||||
downloadProgress = 1f
|
||||
downloadStatus = DownloadStatus.DOWNLOADED
|
||||
Log.d(TAG, "=====================================")
|
||||
Log.d(TAG, "✅ IMAGE DOWNLOAD COMPLETE")
|
||||
Log.d(TAG, "=====================================")
|
||||
} else {
|
||||
Log.e(TAG, "=====================================")
|
||||
Log.e(TAG, "❌ DECRYPTION RETURNED NULL")
|
||||
Log.e(TAG, "=====================================")
|
||||
downloadStatus = DownloadStatus.ERROR
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "=====================================")
|
||||
Log.e(TAG, "❌ IMAGE DOWNLOAD FAILED")
|
||||
Log.e(TAG, "📝 Attachment ID: ${attachment.id}")
|
||||
Log.e(TAG, "🏷️ Download tag: $downloadTag")
|
||||
Log.e(TAG, "❌ Error: ${e.message}")
|
||||
Log.e(TAG, "=====================================")
|
||||
e.printStackTrace()
|
||||
downloadStatus = DownloadStatus.ERROR
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "⚠️ Cannot download image: empty download tag for ${attachment.id}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -982,7 +924,6 @@ fun FileAttachment(
|
||||
scope.launch {
|
||||
try {
|
||||
downloadStatus = DownloadStatus.DOWNLOADING
|
||||
Log.d(TAG, "📥 Downloading file: $fileName")
|
||||
|
||||
val encryptedContent = TransportManager.downloadFile(attachment.id, downloadTag)
|
||||
downloadProgress = 0.6f
|
||||
@@ -1005,12 +946,10 @@ fun FileAttachment(
|
||||
// TODO: Save to Downloads folder
|
||||
downloadProgress = 1f
|
||||
downloadStatus = DownloadStatus.DOWNLOADED
|
||||
Log.d(TAG, "✅ File downloaded: $fileName")
|
||||
} else {
|
||||
downloadStatus = DownloadStatus.ERROR
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "❌ File download failed", e)
|
||||
downloadStatus = DownloadStatus.ERROR
|
||||
}
|
||||
}
|
||||
@@ -1221,12 +1160,10 @@ fun AvatarAttachment(
|
||||
when {
|
||||
// 1. Если blob уже есть в памяти → DOWNLOADED
|
||||
attachment.blob.isNotEmpty() -> {
|
||||
Log.d(TAG, "📦 Avatar blob in memory for ${attachment.id}")
|
||||
DownloadStatus.DOWNLOADED
|
||||
}
|
||||
// 2. Если preview НЕ содержит UUID → локальный файл → DOWNLOADED
|
||||
!isDownloadTag(attachment.preview) -> {
|
||||
Log.d(TAG, "📦 No download tag for avatar ${attachment.id}")
|
||||
DownloadStatus.DOWNLOADED
|
||||
}
|
||||
// 3. Есть UUID (download tag) → проверяем файловую систему
|
||||
@@ -1239,10 +1176,8 @@ fun AvatarAttachment(
|
||||
senderPublicKey
|
||||
)
|
||||
if (hasLocal) {
|
||||
Log.d(TAG, "📦 Found local avatar file for ${attachment.id}")
|
||||
DownloadStatus.DOWNLOADED
|
||||
} else {
|
||||
Log.d(TAG, "📥 Need to download avatar ${attachment.id}")
|
||||
DownloadStatus.NOT_DOWNLOADED
|
||||
}
|
||||
}
|
||||
@@ -1255,7 +1190,6 @@ fun AvatarAttachment(
|
||||
try {
|
||||
blurhashBitmap = BlurHash.decode(preview, 100, 100)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to decode avatar blurhash: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1265,11 +1199,9 @@ fun AvatarAttachment(
|
||||
withContext(Dispatchers.IO) {
|
||||
// 1. Сначала пробуем blob из памяти
|
||||
if (attachment.blob.isNotEmpty()) {
|
||||
Log.d(TAG, "🖼️ Loading avatar from blob")
|
||||
avatarBitmap = base64ToBitmap(attachment.blob)
|
||||
} else {
|
||||
// 2. Читаем из файловой системы (как в Desktop getBlob)
|
||||
Log.d(TAG, "🖼️ Loading avatar from local file")
|
||||
val localBlob =
|
||||
AvatarFileManager.readAvatarByAttachmentId(
|
||||
context,
|
||||
@@ -1278,9 +1210,7 @@ fun AvatarAttachment(
|
||||
)
|
||||
if (localBlob != null) {
|
||||
avatarBitmap = base64ToBitmap(localBlob)
|
||||
Log.d(TAG, "✅ Avatar loaded from local file")
|
||||
} else {
|
||||
Log.w(TAG, "⚠️ Failed to read local avatar file")
|
||||
downloadStatus = DownloadStatus.NOT_DOWNLOADED
|
||||
}
|
||||
}
|
||||
@@ -1293,32 +1223,20 @@ fun AvatarAttachment(
|
||||
scope.launch {
|
||||
try {
|
||||
downloadStatus = DownloadStatus.DOWNLOADING
|
||||
Log.d(TAG, "=====================================")
|
||||
Log.d(TAG, "👤 Starting AVATAR download")
|
||||
Log.d(TAG, "📝 Attachment ID: ${attachment.id}")
|
||||
Log.d(TAG, "🏷️ Download tag: $downloadTag")
|
||||
Log.d(TAG, "👤 Sender public key: ${senderPublicKey.take(16)}...")
|
||||
Log.d(TAG, "=====================================")
|
||||
|
||||
Log.d(TAG, "⬇️ Downloading encrypted avatar from CDN...")
|
||||
val startTime = System.currentTimeMillis()
|
||||
val encryptedContent = TransportManager.downloadFile(attachment.id, downloadTag)
|
||||
val downloadTime = System.currentTimeMillis() - startTime
|
||||
Log.d(TAG, "✅ Downloaded ${encryptedContent.length} chars in ${downloadTime}ms")
|
||||
|
||||
downloadStatus = DownloadStatus.DECRYPTING
|
||||
Log.d(TAG, "🔓 Starting decryption...")
|
||||
|
||||
// КРИТИЧНО: chachaKey ЗАШИФРОВАН в БД (как в Desktop)
|
||||
// Сначала расшифровываем его, получаем raw bytes
|
||||
Log.d(TAG, "🔑 Decrypting ChaCha key from sender...")
|
||||
val decryptedKeyAndNonce =
|
||||
MessageCrypto.decryptKeyFromSender(chachaKey, privateKey)
|
||||
Log.d(TAG, "🔑 ChaCha key decrypted: ${decryptedKeyAndNonce.size} bytes")
|
||||
|
||||
// Используем decryptAttachmentBlobWithPlainKey который правильно конвертирует
|
||||
// bytes в password
|
||||
Log.d(TAG, "🔓 Decrypting avatar blob with PBKDF2...")
|
||||
val decryptStartTime = System.currentTimeMillis()
|
||||
val decrypted =
|
||||
MessageCrypto.decryptAttachmentBlobWithPlainKey(
|
||||
@@ -1326,21 +1244,13 @@ fun AvatarAttachment(
|
||||
decryptedKeyAndNonce
|
||||
)
|
||||
val decryptTime = System.currentTimeMillis() - decryptStartTime
|
||||
Log.d(TAG, "🔓 Decryption completed in ${decryptTime}ms")
|
||||
|
||||
if (decrypted != null) {
|
||||
Log.d(TAG, "✅ Decrypted blob: ${decrypted.length} chars")
|
||||
withContext(Dispatchers.IO) {
|
||||
Log.d(TAG, "🖼️ Converting to bitmap...")
|
||||
avatarBitmap = base64ToBitmap(decrypted)
|
||||
Log.d(
|
||||
TAG,
|
||||
"✅ Bitmap created: ${avatarBitmap?.width}x${avatarBitmap?.height}"
|
||||
)
|
||||
|
||||
// 💾 Сохраняем в файловую систему по attachment.id (как в Desktop)
|
||||
// Desktop: writeFile(`a/${md5(attachment.id + publicKey)}`, encrypted)
|
||||
Log.d(TAG, "💾 Saving avatar to file system by attachment ID...")
|
||||
val path =
|
||||
AvatarFileManager.saveAvatarByAttachmentId(
|
||||
context = context,
|
||||
@@ -1348,69 +1258,36 @@ fun AvatarAttachment(
|
||||
attachmentId = attachment.id,
|
||||
publicKey = senderPublicKey
|
||||
)
|
||||
Log.d(TAG, "💾 Avatar saved to: $path")
|
||||
}
|
||||
// Сохраняем аватар в репозиторий (для UI обновления)
|
||||
// Если это исходящее сообщение с аватаром, сохраняем для текущего
|
||||
// пользователя
|
||||
val targetPublicKey =
|
||||
if (isOutgoing && currentUserPublicKey.isNotEmpty()) {
|
||||
Log.d(
|
||||
TAG,
|
||||
"💾 Saving avatar to repository for CURRENT user ${currentUserPublicKey.take(16)}..."
|
||||
)
|
||||
currentUserPublicKey
|
||||
} else {
|
||||
Log.d(
|
||||
TAG,
|
||||
"💾 Saving avatar to repository for SENDER ${senderPublicKey.take(16)}..."
|
||||
)
|
||||
senderPublicKey
|
||||
}
|
||||
|
||||
// ВАЖНО: ждем завершения сохранения в репозиторий
|
||||
if (avatarRepository != null) {
|
||||
try {
|
||||
Log.d(TAG, "📤 Calling avatarRepository.saveAvatar()...")
|
||||
avatarRepository.saveAvatar(targetPublicKey, decrypted)
|
||||
Log.d(
|
||||
TAG,
|
||||
"✅ Avatar saved to repository for ${targetPublicKey.take(16)}"
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "❌ Failed to save avatar to repository: ${e.message}", e)
|
||||
}
|
||||
} else {
|
||||
Log.e(
|
||||
TAG,
|
||||
"❌ avatarRepository is NULL! Cannot save avatar for ${targetPublicKey.take(16)}"
|
||||
)
|
||||
}
|
||||
|
||||
downloadStatus = DownloadStatus.DOWNLOADED
|
||||
Log.d(TAG, "=====================================")
|
||||
Log.d(TAG, "✅ AVATAR DOWNLOAD COMPLETE")
|
||||
Log.d(TAG, "=====================================")
|
||||
} else {
|
||||
Log.e(TAG, "=====================================")
|
||||
Log.e(TAG, "❌ AVATAR DECRYPTION RETURNED NULL")
|
||||
Log.e(TAG, "=====================================")
|
||||
downloadStatus = DownloadStatus.ERROR
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "=====================================")
|
||||
Log.e(TAG, "❌ AVATAR DOWNLOAD FAILED")
|
||||
Log.e(TAG, "📝 Attachment ID: ${attachment.id}")
|
||||
Log.e(TAG, "🏷️ Download tag: $downloadTag")
|
||||
Log.e(TAG, "👤 Sender: ${senderPublicKey.take(16)}...")
|
||||
Log.e(TAG, "❌ Error: ${e.message}")
|
||||
Log.e(TAG, "=====================================")
|
||||
e.printStackTrace()
|
||||
downloadStatus = DownloadStatus.ERROR
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "⚠️ Cannot download avatar: empty download tag for ${attachment.id}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1690,7 +1567,6 @@ private fun base64ToBitmap(base64: String): Bitmap? {
|
||||
val bytes = Base64.decode(cleanBase64, Base64.DEFAULT)
|
||||
BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to decode base64 to bitmap: ${e.message}")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.rosetta.messenger.ui.chats.components
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.*
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
@@ -817,7 +816,6 @@ fun ReplyBubble(
|
||||
imageBitmap = decoded
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("ReplyBubble", "Failed to load image: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1173,7 +1171,6 @@ fun ReplyImagePreview(
|
||||
fullImageBitmap = decoded
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("ReplyImagePreview", "Failed to load full image: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import android.widget.ImageView
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
@@ -692,18 +691,15 @@ private suspend fun saveEditedImage(
|
||||
saveSettings,
|
||||
object : PhotoEditor.OnSaveListener {
|
||||
override fun onSuccess(imagePath: String) {
|
||||
Log.d(TAG, "Image saved to: $imagePath")
|
||||
onResult(Uri.fromFile(File(imagePath)))
|
||||
}
|
||||
|
||||
override fun onFailure(exception: Exception) {
|
||||
Log.e(TAG, "Failed to save image", exception)
|
||||
onResult(null)
|
||||
}
|
||||
}
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error saving image", e)
|
||||
withContext(Dispatchers.Main) { onResult(null) }
|
||||
}
|
||||
}
|
||||
@@ -733,19 +729,16 @@ private suspend fun saveEditedImageSync(context: Context, photoEditor: PhotoEdit
|
||||
saveSettings,
|
||||
object : PhotoEditor.OnSaveListener {
|
||||
override fun onSuccess(imagePath: String) {
|
||||
Log.d(TAG, "Image saved sync to: $imagePath")
|
||||
continuation.resume(Uri.fromFile(File(imagePath)))
|
||||
}
|
||||
|
||||
override fun onFailure(exception: Exception) {
|
||||
Log.e(TAG, "Failed to save image sync", exception)
|
||||
continuation.resume(null)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error saving image sync", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
@@ -829,7 +822,6 @@ private fun launchCrop(
|
||||
|
||||
launcher.launch(intent)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error launching crop", e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -952,7 +944,6 @@ fun MultiImageEditorScreen(
|
||||
photoEditors[page] = editor
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error loading image for page $page", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -973,7 +964,6 @@ fun MultiImageEditorScreen(
|
||||
view.source.setImageBitmap(bitmap)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error reloading image", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1297,7 +1287,6 @@ private fun AsyncImageLoader(uri: Uri, modifier: Modifier = Modifier) {
|
||||
bitmap = BitmapFactory.decodeStream(inputStream)
|
||||
inputStream?.close()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error loading image", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.rosetta.messenger.ui.chats.components
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.*
|
||||
@@ -341,7 +340,6 @@ private fun ZoomableImage(
|
||||
previewBitmap = base64ToBitmapSafe(blurPart)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to load preview: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -377,7 +375,6 @@ private fun ZoomableImage(
|
||||
// 3. Скачиваем с CDN
|
||||
val downloadTag = getDownloadTag(image.preview)
|
||||
if (downloadTag.isNotEmpty()) {
|
||||
Log.d(TAG, "📥 Downloading image from CDN: ${image.attachmentId}")
|
||||
|
||||
val encryptedContent = TransportManager.downloadFile(image.attachmentId, downloadTag)
|
||||
val decryptedKeyAndNonce = MessageCrypto.decryptKeyFromSender(image.chachaKey, privateKey)
|
||||
@@ -405,7 +402,6 @@ private fun ZoomableImage(
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to load image: ${e.message}", e)
|
||||
loadError = e.message
|
||||
}
|
||||
|
||||
@@ -598,7 +594,6 @@ private fun base64ToBitmapSafe(base64String: String): Bitmap? {
|
||||
val decodedBytes = Base64.decode(cleanBase64, Base64.DEFAULT)
|
||||
BitmapFactory.decodeByteArray(decodedBytes, 0, decodedBytes.size)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to decode base64 to bitmap: ${e.message}")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.provider.MediaStore
|
||||
import android.util.Log
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.animation.*
|
||||
@@ -918,9 +917,7 @@ private suspend fun loadMediaItems(context: Context): List<MediaItem> = withCont
|
||||
// Sort all items by date
|
||||
items.sortByDescending { it.dateModified }
|
||||
|
||||
Log.d(TAG, "📸 Loaded ${items.size} media items")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "❌ Failed to load media items", e)
|
||||
}
|
||||
|
||||
items
|
||||
|
||||
@@ -68,20 +68,16 @@ fun AvatarImage(
|
||||
|
||||
// Логируем для отладки
|
||||
LaunchedEffect(publicKey, avatars) {
|
||||
android.util.Log.d("AvatarImage", "📸 publicKey=${publicKey.take(16)}... avatars=${avatars.size} repository=${avatarRepository != null}")
|
||||
}
|
||||
|
||||
// Декодируем первый аватар
|
||||
LaunchedEffect(avatars) {
|
||||
bitmap = if (avatars.isNotEmpty()) {
|
||||
android.util.Log.d("AvatarImage", "🔄 Decoding avatar for ${publicKey.take(16)}... base64 length=${avatars.first().base64Data?.length ?: 0}")
|
||||
withContext(Dispatchers.IO) {
|
||||
val result = AvatarFileManager.base64ToBitmap(avatars.first().base64Data)
|
||||
android.util.Log.d("AvatarImage", "✅ Decoded bitmap for ${publicKey.take(16)}... result=${result != null} size=${result?.width}x${result?.height}")
|
||||
result
|
||||
}
|
||||
} else {
|
||||
android.util.Log.d("AvatarImage", "⚠️ No avatars for ${publicKey.take(16)}...")
|
||||
null
|
||||
}
|
||||
}
|
||||
@@ -101,7 +97,6 @@ fun AvatarImage(
|
||||
) {
|
||||
// Log what we're showing
|
||||
LaunchedEffect(bitmap) {
|
||||
android.util.Log.d("AvatarImage", "🖼️ Showing for ${publicKey.take(16)}... bitmap=${bitmap != null}")
|
||||
}
|
||||
|
||||
if (bitmap != null) {
|
||||
|
||||
@@ -232,7 +232,6 @@ fun OtherProfileScreen(
|
||||
val account = viewModel.myPublicKey ?: return@launch
|
||||
database.dialogDao().deleteDialog(account, user.publicKey)
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("OtherProfileScreen", "Failed to delete dialog", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
@@ -191,13 +190,11 @@ fun ProfileScreen(
|
||||
rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
Log.d(TAG, "✂️ Crop result: resultCode=${result.resultCode}")
|
||||
|
||||
val croppedUri = ImageCropHelper.getCroppedImageUri(result)
|
||||
val error = ImageCropHelper.getCropError(result)
|
||||
|
||||
if (croppedUri != null) {
|
||||
Log.d(TAG, "✅ Cropped image URI: $croppedUri")
|
||||
scope.launch {
|
||||
try {
|
||||
// Читаем обрезанное изображение
|
||||
@@ -205,10 +202,8 @@ fun ProfileScreen(
|
||||
val imageBytes = inputStream?.readBytes()
|
||||
inputStream?.close()
|
||||
|
||||
Log.d(TAG, "📊 Cropped image bytes: ${imageBytes?.size ?: 0} bytes")
|
||||
|
||||
if (imageBytes != null) {
|
||||
Log.d(TAG, "🔄 Converting cropped image to PNG Base64...")
|
||||
val base64Png =
|
||||
withContext(Dispatchers.IO) {
|
||||
AvatarFileManager.imagePrepareForNetworkTransfer(
|
||||
@@ -217,12 +212,10 @@ fun ProfileScreen(
|
||||
)
|
||||
}
|
||||
|
||||
Log.d(TAG, "✅ Converted to Base64: ${base64Png.length} chars")
|
||||
|
||||
// Сохраняем аватар через репозиторий
|
||||
avatarRepository?.changeMyAvatar(base64Png)
|
||||
|
||||
Log.d(TAG, "🎉 Avatar update completed")
|
||||
|
||||
android.widget.Toast.makeText(
|
||||
context,
|
||||
@@ -232,7 +225,6 @@ fun ProfileScreen(
|
||||
.show()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "❌ Failed to process cropped avatar", e)
|
||||
android.widget.Toast.makeText(
|
||||
context,
|
||||
"Failed to update avatar: ${e.message}",
|
||||
@@ -242,7 +234,6 @@ fun ProfileScreen(
|
||||
}
|
||||
}
|
||||
} else if (error != null) {
|
||||
Log.e(TAG, "❌ Crop error", error)
|
||||
android.widget.Toast.makeText(
|
||||
context,
|
||||
"Failed to crop image: ${error.message}",
|
||||
@@ -250,7 +241,6 @@ fun ProfileScreen(
|
||||
)
|
||||
.show()
|
||||
} else {
|
||||
Log.w(TAG, "⚠️ Crop cancelled")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,13 +248,11 @@ fun ProfileScreen(
|
||||
val imagePickerLauncher =
|
||||
rememberLauncherForActivityResult(contract = ActivityResultContracts.GetContent()) {
|
||||
uri: Uri? ->
|
||||
Log.d(TAG, "🖼️ Image picker result: uri=$uri")
|
||||
uri?.let {
|
||||
// Запускаем uCrop для обрезки
|
||||
val cropIntent = ImageCropHelper.createCropIntent(context, it, isDarkTheme)
|
||||
cropLauncher.launch(cropIntent)
|
||||
}
|
||||
?: Log.w(TAG, "⚠️ URI is null, image picker cancelled")
|
||||
}
|
||||
|
||||
// Цвета в зависимости от темы
|
||||
@@ -749,20 +737,16 @@ private fun CollapsingProfileHeader(
|
||||
var avatarBitmap by remember(avatars) { mutableStateOf<android.graphics.Bitmap?>(null) }
|
||||
var dominantColor by remember { mutableStateOf<Color?>(null) }
|
||||
|
||||
Log.d(TAG, "🎨 CollapsingProfileHeader: hasAvatar=$hasAvatar, avatars.size=${avatars.size}, dominantColor=$dominantColor")
|
||||
|
||||
LaunchedEffect(avatars, publicKey) {
|
||||
Log.d(TAG, "🎨 LaunchedEffect avatars: size=${avatars.size}, publicKey=${publicKey.take(8)}")
|
||||
if (avatars.isNotEmpty()) {
|
||||
val loadedBitmap = kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.IO) {
|
||||
AvatarFileManager.base64ToBitmap(avatars.first().base64Data)
|
||||
}
|
||||
avatarBitmap = loadedBitmap
|
||||
Log.d(TAG, "🎨 Bitmap loaded: ${loadedBitmap?.width}x${loadedBitmap?.height}")
|
||||
// Извлекаем доминантный цвет из нижней части аватарки (где будет текст)
|
||||
loadedBitmap?.let { bitmap ->
|
||||
try {
|
||||
Log.d(TAG, "🎨 Extracting color from bitmap ${bitmap.width}x${bitmap.height}")
|
||||
// Берем нижнюю треть изображения для более точного определения
|
||||
val bottomThird =
|
||||
android.graphics.Bitmap.createBitmap(
|
||||
@@ -772,20 +756,15 @@ private fun CollapsingProfileHeader(
|
||||
bitmap.width,
|
||||
(bitmap.height / 3).coerceAtLeast(1)
|
||||
)
|
||||
Log.d(TAG, "🎨 Bottom third: ${bottomThird.width}x${bottomThird.height}")
|
||||
val palette = AndroidPalette.from(bottomThird).generate()
|
||||
Log.d(TAG, "🎨 Palette generated, dominantSwatch=${palette.dominantSwatch}, mutedSwatch=${palette.mutedSwatch}")
|
||||
// Используем доминантный цвет или muted swatch
|
||||
val swatch = palette.dominantSwatch ?: palette.mutedSwatch
|
||||
if (swatch != null) {
|
||||
val extractedColor = Color(swatch.rgb)
|
||||
Log.d(TAG, "🎨 Extracted dominant color: R=${extractedColor.red}, G=${extractedColor.green}, B=${extractedColor.blue}, isLight=${isColorLight(extractedColor)}")
|
||||
dominantColor = extractedColor
|
||||
} else {
|
||||
Log.w(TAG, "🎨 No swatch found in palette!")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to extract dominant color: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -799,7 +778,6 @@ private fun CollapsingProfileHeader(
|
||||
derivedStateOf {
|
||||
if (hasAvatar && dominantColor != null) {
|
||||
val isLight = isColorLight(dominantColor!!)
|
||||
Log.d(TAG, "🎨 Text color: hasAvatar=$hasAvatar, dominantColor=$dominantColor, isLight=$isLight")
|
||||
if (isLight) Color.Black else Color.White
|
||||
} else {
|
||||
if (isColorLight(avatarColors.backgroundColor)) Color.Black else Color.White
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.rosetta.messenger.ui.settings
|
||||
|
||||
import android.app.Application
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.rosetta.messenger.data.AccountManager
|
||||
@@ -52,7 +51,6 @@ class ProfileViewModel(application: Application) : AndroidViewModel(application)
|
||||
val timestamp = java.text.SimpleDateFormat("HH:mm:ss.SSS", java.util.Locale.getDefault()).format(java.util.Date())
|
||||
val logMessage = "[$timestamp] $message"
|
||||
_state.value = _state.value.copy(logs = _state.value.logs + logMessage)
|
||||
Log.d("ProfileViewModel", logMessage)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,15 +97,10 @@ class ProfileViewModel(application: Application) : AndroidViewModel(application)
|
||||
addLog(" - privateKey: ${privateKeyHash.take(16)}... (length: ${privateKeyHash.length})")
|
||||
|
||||
// CRITICAL: Log full packet data for debugging
|
||||
Log.d("ProfileViewModel", "📤 SENDING PacketUserInfo:")
|
||||
Log.d("ProfileViewModel", " username='$username' (${username.length} chars)")
|
||||
Log.d("ProfileViewModel", " title='$actualTitle' (${actualTitle.length} chars)")
|
||||
Log.d("ProfileViewModel", " privateKey='${privateKeyHash.take(32)}...' (${privateKeyHash.length} chars)")
|
||||
|
||||
addLog("Sending packet to server...")
|
||||
ProtocolManager.send(packet)
|
||||
addLog("Packet sent successfully")
|
||||
Log.d("ProfileViewModel", "✅ Packet sent to ProtocolManager")
|
||||
addLog("Waiting for PacketResult (0x02) from server...")
|
||||
|
||||
// Set timeout for server response
|
||||
@@ -135,7 +128,6 @@ class ProfileViewModel(application: Application) : AndroidViewModel(application)
|
||||
// Wait for response (handled in handlePacketResult)
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e("ProfileViewModel", "Error saving profile", e)
|
||||
addLog("❌ ERROR: ${e.message}")
|
||||
_state.value = _state.value.copy(
|
||||
isSaving = false,
|
||||
@@ -191,9 +183,7 @@ class ProfileViewModel(application: Application) : AndroidViewModel(application)
|
||||
try {
|
||||
accountManager.updateAccountName(publicKey, name)
|
||||
accountManager.updateAccountUsername(publicKey, username)
|
||||
Log.d("ProfileViewModel", "Local profile updated: name=$name, username=$username")
|
||||
} catch (e: Exception) {
|
||||
Log.e("ProfileViewModel", "Error updating local profile", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user