fix: update status bar color management for improved visibility in ChatDetailScreen and OtherProfileScreen
This commit is contained in:
@@ -194,22 +194,24 @@ fun ChatDetailScreen(
|
|||||||
var imageViewerInitialIndex by remember { mutableStateOf(0) }
|
var imageViewerInitialIndex by remember { mutableStateOf(0) }
|
||||||
var imageViewerSourceBounds by remember { mutableStateOf<ImageSourceBounds?>(null) }
|
var imageViewerSourceBounds by remember { mutableStateOf<ImageSourceBounds?>(null) }
|
||||||
|
|
||||||
// 🎨 Управление статус баром - чёрный при просмотре фото
|
// 🎨 Управление статус баром
|
||||||
DisposableEffect(isDarkTheme, showImageViewer) {
|
DisposableEffect(isDarkTheme, showImageViewer) {
|
||||||
val insetsController = window?.let { WindowCompat.getInsetsController(it, view) }
|
val insetsController = window?.let { WindowCompat.getInsetsController(it, view) }
|
||||||
|
|
||||||
if (showImageViewer) {
|
if (showImageViewer) {
|
||||||
// 📸 При просмотре фото - чёрный статус бар
|
// 📸 При просмотре фото - чёрный статус бар
|
||||||
window?.statusBarColor = 0xFF000000.toInt()
|
window?.statusBarColor = android.graphics.Color.BLACK
|
||||||
insetsController?.isAppearanceLightStatusBars = false
|
insetsController?.isAppearanceLightStatusBars = false
|
||||||
} else {
|
} else {
|
||||||
// Обычный режим - цвет хедера
|
// Обычный режим - прозрачный статус бар, иконки по теме
|
||||||
val headerColor = if (isDarkTheme) 0xFF212121.toInt() else 0xFFFFFFFF.toInt()
|
window?.statusBarColor = android.graphics.Color.TRANSPARENT
|
||||||
window?.statusBarColor = headerColor
|
|
||||||
insetsController?.isAppearanceLightStatusBars = !isDarkTheme
|
insetsController?.isAppearanceLightStatusBars = !isDarkTheme
|
||||||
}
|
}
|
||||||
|
|
||||||
onDispose { }
|
onDispose {
|
||||||
|
// Восстанавливаем прозрачный статус бар при выходе
|
||||||
|
window?.statusBarColor = android.graphics.Color.TRANSPARENT
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 📷 Camera: URI для сохранения фото
|
// 📷 Camera: URI для сохранения фото
|
||||||
@@ -342,8 +344,14 @@ fun ChatDetailScreen(
|
|||||||
Pair<ChatMessage, Boolean>>() // message, showDateHeader
|
Pair<ChatMessage, Boolean>>() // message, showDateHeader
|
||||||
var lastDateString = ""
|
var lastDateString = ""
|
||||||
|
|
||||||
|
// 🔥 КРИТИЧНО: Дедупликация по ID перед сортировкой!
|
||||||
|
val uniqueMessages = messages.distinctBy { it.id }
|
||||||
|
if (uniqueMessages.size != messages.size) {
|
||||||
|
android.util.Log.e("ChatDetailScreen", "🚨 DEDUPLICATED ${messages.size - uniqueMessages.size} messages in UI! Original: ${messages.map { it.id }}")
|
||||||
|
}
|
||||||
|
|
||||||
// Сортируем по времени (новые -> старые) для reversed layout
|
// Сортируем по времени (новые -> старые) для reversed layout
|
||||||
val sortedMessages = messages.sortedByDescending { it.timestamp.time }
|
val sortedMessages = uniqueMessages.sortedByDescending { it.timestamp.time }
|
||||||
|
|
||||||
for (i in sortedMessages.indices) {
|
for (i in sortedMessages.indices) {
|
||||||
val message = sortedMessages[i]
|
val message = sortedMessages[i]
|
||||||
@@ -892,7 +900,8 @@ fun ChatDetailScreen(
|
|||||||
publicKey = user.publicKey,
|
publicKey = user.publicKey,
|
||||||
avatarRepository = avatarRepository,
|
avatarRepository = avatarRepository,
|
||||||
size = 40.dp,
|
size = 40.dp,
|
||||||
isDarkTheme = isDarkTheme
|
isDarkTheme = isDarkTheme,
|
||||||
|
displayName = user.title.ifEmpty { user.username } // 🔥 Для инициалов
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -258,7 +258,18 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
// Добавляем все сразу
|
// Добавляем все сразу
|
||||||
kotlinx.coroutines.withContext(Dispatchers.Main.immediate) {
|
kotlinx.coroutines.withContext(Dispatchers.Main.immediate) {
|
||||||
val currentList = _messages.value
|
val currentList = _messages.value
|
||||||
_messages.value = (currentList + newMessages).sortedBy { it.timestamp }
|
val newList = (currentList + newMessages).sortedBy { it.timestamp }
|
||||||
|
|
||||||
|
// 🔍 DEBUG: Проверка на дублирующиеся ID
|
||||||
|
val allIds = newList.map { it.id }
|
||||||
|
val duplicates = allIds.groupBy { it }.filter { it.value.size > 1 }.keys
|
||||||
|
if (duplicates.isNotEmpty()) {
|
||||||
|
android.util.Log.e("ChatViewModel", "🚨 DUPLICATE IDS FOUND in pollLatestMessages: $duplicates")
|
||||||
|
android.util.Log.e("ChatViewModel", " currentList ids: ${currentList.map { it.id }}")
|
||||||
|
android.util.Log.e("ChatViewModel", " newMessages ids: ${newMessages.map { it.id }}")
|
||||||
|
}
|
||||||
|
|
||||||
|
_messages.value = newList
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновляем кэш
|
// Обновляем кэш
|
||||||
@@ -359,6 +370,23 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
// ✅ handleIncomingMessage удалён - обработка входящих сообщений теперь ТОЛЬКО в MessageRepository
|
// ✅ handleIncomingMessage удалён - обработка входящих сообщений теперь ТОЛЬКО в MessageRepository
|
||||||
// Это предотвращает дублирование сообщений
|
// Это предотвращает дублирование сообщений
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 🔥 Безопасное добавление сообщения - предотвращает дубликаты
|
||||||
|
* Возвращает true если сообщение было добавлено
|
||||||
|
*/
|
||||||
|
private fun addMessageSafely(message: ChatMessage): Boolean {
|
||||||
|
val currentMessages = _messages.value
|
||||||
|
val currentIds = currentMessages.map { it.id }.toSet()
|
||||||
|
android.util.Log.d("ChatViewModel", "🔍 addMessageSafely: id=${message.id}, currentCount=${currentMessages.size}, ids=${currentIds.take(5)}...")
|
||||||
|
if (message.id in currentIds) {
|
||||||
|
android.util.Log.e("ChatViewModel", "🚨 BLOCKED DUPLICATE: id=${message.id} already exists in ${currentIds.size} messages!")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_messages.value = currentMessages + message
|
||||||
|
android.util.Log.d("ChatViewModel", "✅ Added message: id=${message.id}, newCount=${_messages.value.size}")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
private fun updateMessageStatus(messageId: String, status: MessageStatus) {
|
private fun updateMessageStatus(messageId: String, status: MessageStatus) {
|
||||||
_messages.value = _messages.value.map { msg ->
|
_messages.value = _messages.value.map { msg ->
|
||||||
if (msg.id == messageId) msg.copy(status = status) else msg
|
if (msg.id == messageId) msg.copy(status = status) else msg
|
||||||
@@ -565,10 +593,27 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
// 🔥 СРАЗУ обновляем UI - пользователь видит сообщения мгновенно
|
// 🔥 СРАЗУ обновляем UI - пользователь видит сообщения мгновенно
|
||||||
// НО сохраняем оптимистичные сообщения (SENDING), которые ещё не в БД
|
// НО сохраняем оптимистичные сообщения (SENDING), которые ещё не в БД
|
||||||
withContext(Dispatchers.Main.immediate) {
|
withContext(Dispatchers.Main.immediate) {
|
||||||
val optimisticMessages = _messages.value.filter { msg ->
|
val dbIds = messages.map { it.id }.toSet()
|
||||||
msg.status == MessageStatus.SENDING && messages.none { it.id == msg.id }
|
val currentMsgs = _messages.value
|
||||||
|
android.util.Log.d("ChatViewModel", "📥 loadMessages: dbCount=${messages.size}, currentCount=${currentMsgs.size}")
|
||||||
|
android.util.Log.d("ChatViewModel", " DB ids: ${dbIds.take(5)}...")
|
||||||
|
android.util.Log.d("ChatViewModel", " Current ids: ${currentMsgs.map { it.id }.take(5)}...")
|
||||||
|
|
||||||
|
val optimisticMessages = currentMsgs.filter { msg ->
|
||||||
|
msg.status == MessageStatus.SENDING && msg.id !in dbIds
|
||||||
}
|
}
|
||||||
_messages.value = messages + optimisticMessages
|
android.util.Log.d("ChatViewModel", " Optimistic (SENDING, not in DB): ${optimisticMessages.size} - ${optimisticMessages.map { it.id }}")
|
||||||
|
|
||||||
|
val newList = messages + optimisticMessages
|
||||||
|
|
||||||
|
// 🔍 Финальная дедупликация по ID (на всякий случай)
|
||||||
|
val deduplicatedList = newList.distinctBy { it.id }
|
||||||
|
|
||||||
|
if (deduplicatedList.size != newList.size) {
|
||||||
|
android.util.Log.e("ChatViewModel", "🚨 DEDUPLICATED ${newList.size - deduplicatedList.size} messages!")
|
||||||
|
}
|
||||||
|
|
||||||
|
_messages.value = deduplicatedList
|
||||||
_isLoading.value = false
|
_isLoading.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1287,7 +1332,9 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
status = MessageStatus.SENDING,
|
status = MessageStatus.SENDING,
|
||||||
replyData = replyData // Данные для reply bubble
|
replyData = replyData // Данные для reply bubble
|
||||||
)
|
)
|
||||||
_messages.value = _messages.value + optimisticMessage
|
|
||||||
|
// <20> Безопасное добавление с проверкой дубликатов
|
||||||
|
addMessageSafely(optimisticMessage)
|
||||||
_inputText.value = ""
|
_inputText.value = ""
|
||||||
|
|
||||||
// 🔥 Очищаем reply после отправки - данные сохраняются в displayReplyMessages для анимации
|
// 🔥 Очищаем reply после отправки - данные сохраняются в displayReplyMessages для анимации
|
||||||
@@ -1475,7 +1522,8 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
_messages.value = _messages.value + optimisticMessage
|
// 🔥 Безопасное добавление с проверкой дубликатов
|
||||||
|
addMessageSafely(optimisticMessage)
|
||||||
_inputText.value = ""
|
_inputText.value = ""
|
||||||
|
|
||||||
|
|
||||||
@@ -1531,10 +1579,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
ProtocolManager.send(packet)
|
ProtocolManager.send(packet)
|
||||||
}
|
}
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
updateMessageStatus(messageId, MessageStatus.SENT)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 💾 Сохраняем изображение в файл локально (как в desktop)
|
// 💾 Сохраняем изображение в файл локально (как в desktop)
|
||||||
AttachmentFileManager.saveAttachment(
|
AttachmentFileManager.saveAttachment(
|
||||||
context = getApplication(),
|
context = getApplication(),
|
||||||
@@ -1564,10 +1608,19 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
encryptedKey = encryptedKey,
|
encryptedKey = encryptedKey,
|
||||||
timestamp = timestamp,
|
timestamp = timestamp,
|
||||||
isFromMe = true,
|
isFromMe = true,
|
||||||
delivered = if (isSavedMessages) 2 else 0,
|
delivered = if (isSavedMessages) 2 else 0, // SENDING для обычных
|
||||||
attachmentsJson = attachmentsJson
|
attachmentsJson = attachmentsJson
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 🔥 После успешной отправки обновляем статус на SENT (2) в БД и UI
|
||||||
|
if (!isSavedMessages) {
|
||||||
|
updateMessageStatusInDb(messageId, 2) // SENT
|
||||||
|
}
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
updateMessageStatus(messageId, MessageStatus.SENT)
|
||||||
|
}
|
||||||
|
|
||||||
saveDialog(if (text.isNotEmpty()) text else "photo", timestamp)
|
saveDialog(if (text.isNotEmpty()) text else "photo", timestamp)
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -1634,7 +1687,8 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
status = MessageStatus.SENDING,
|
status = MessageStatus.SENDING,
|
||||||
attachments = attachmentsList
|
attachments = attachmentsList
|
||||||
)
|
)
|
||||||
_messages.value = _messages.value + optimisticMessage
|
// 🔥 Безопасное добавление с проверкой дубликатов
|
||||||
|
addMessageSafely(optimisticMessage)
|
||||||
_inputText.value = ""
|
_inputText.value = ""
|
||||||
|
|
||||||
|
|
||||||
@@ -1715,10 +1769,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
ProtocolManager.send(packet)
|
ProtocolManager.send(packet)
|
||||||
}
|
}
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
updateMessageStatus(messageId, MessageStatus.SENT)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Сохраняем в БД
|
// Сохраняем в БД
|
||||||
saveMessageToDatabase(
|
saveMessageToDatabase(
|
||||||
messageId = messageId,
|
messageId = messageId,
|
||||||
@@ -1731,6 +1781,16 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
attachmentsJson = attachmentsJsonArray.toString()
|
attachmentsJson = attachmentsJsonArray.toString()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 🔥 Обновляем статус в БД после отправки
|
||||||
|
if (!isSavedMessages) {
|
||||||
|
updateMessageStatusInDb(messageId, 2) // SENT
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем UI
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
updateMessageStatus(messageId, MessageStatus.SENT)
|
||||||
|
}
|
||||||
|
|
||||||
saveDialog(if (text.isNotEmpty()) text else "📷 ${images.size} photos", timestamp)
|
saveDialog(if (text.isNotEmpty()) text else "📷 ${images.size} photos", timestamp)
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -1785,7 +1845,8 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
_messages.value = _messages.value + optimisticMessage
|
// 🔥 Безопасное добавление с проверкой дубликатов
|
||||||
|
addMessageSafely(optimisticMessage)
|
||||||
_inputText.value = ""
|
_inputText.value = ""
|
||||||
|
|
||||||
|
|
||||||
@@ -1838,10 +1899,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
ProtocolManager.send(packet)
|
ProtocolManager.send(packet)
|
||||||
}
|
}
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
updateMessageStatus(messageId, MessageStatus.SENT)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ⚠️ НЕ сохраняем файл локально - они слишком большие
|
// ⚠️ НЕ сохраняем файл локально - они слишком большие
|
||||||
// Файлы загружаются с Transport Server при необходимости
|
// Файлы загружаются с Transport Server при необходимости
|
||||||
val attachmentsJson = JSONArray().apply {
|
val attachmentsJson = JSONArray().apply {
|
||||||
@@ -1853,6 +1910,7 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
})
|
})
|
||||||
}.toString()
|
}.toString()
|
||||||
|
|
||||||
|
// 🔥 Сохраняем сначала с SENDING, потом обновляем на SENT
|
||||||
saveMessageToDatabase(
|
saveMessageToDatabase(
|
||||||
messageId = messageId,
|
messageId = messageId,
|
||||||
text = text,
|
text = text,
|
||||||
@@ -1860,10 +1918,19 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
encryptedKey = encryptedKey,
|
encryptedKey = encryptedKey,
|
||||||
timestamp = timestamp,
|
timestamp = timestamp,
|
||||||
isFromMe = true,
|
isFromMe = true,
|
||||||
delivered = if (isSavedMessages) 2 else 0,
|
delivered = if (isSavedMessages) 2 else 0, // SENDING для обычных, SENT для saved
|
||||||
attachmentsJson = attachmentsJson
|
attachmentsJson = attachmentsJson
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 🔥 После успешной отправки обновляем статус на SENT (2) в БД и UI
|
||||||
|
if (!isSavedMessages) {
|
||||||
|
updateMessageStatusInDb(messageId, 2) // SENT
|
||||||
|
}
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
updateMessageStatus(messageId, MessageStatus.SENT)
|
||||||
|
}
|
||||||
|
|
||||||
saveDialog(if (text.isNotEmpty()) text else "file", timestamp)
|
saveDialog(if (text.isNotEmpty()) text else "file", timestamp)
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -1973,7 +2040,7 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
_messages.value = _messages.value + optimisticMessage
|
addMessageSafely(optimisticMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Шифрование текста (пустой текст для аватарки)
|
// 2. Шифрование текста (пустой текст для аватарки)
|
||||||
@@ -2029,10 +2096,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
ProtocolManager.send(packet)
|
ProtocolManager.send(packet)
|
||||||
}
|
}
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
updateMessageStatus(messageId, MessageStatus.SENT)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 💾 Сохраняем аватар в файл локально (как IMAGE - с приватным ключом)
|
// 💾 Сохраняем аватар в файл локально (как IMAGE - с приватным ключом)
|
||||||
AttachmentFileManager.saveAttachment(
|
AttachmentFileManager.saveAttachment(
|
||||||
context = getApplication(),
|
context = getApplication(),
|
||||||
@@ -2063,6 +2126,16 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
attachmentsJson = attachmentsJson
|
attachmentsJson = attachmentsJson
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 🔥 Обновляем статус в БД после отправки
|
||||||
|
if (!isSavedMessages) {
|
||||||
|
updateMessageStatusInDb(messageId, 2) // SENT
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем UI
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
updateMessageStatus(messageId, MessageStatus.SENT)
|
||||||
|
}
|
||||||
|
|
||||||
saveDialog("\$a=Avatar", timestamp)
|
saveDialog("\$a=Avatar", timestamp)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -448,7 +448,8 @@ fun ChatsListScreen(
|
|||||||
publicKey = accountPublicKey,
|
publicKey = accountPublicKey,
|
||||||
avatarRepository = avatarRepository,
|
avatarRepository = avatarRepository,
|
||||||
size = 66.dp,
|
size = 66.dp,
|
||||||
isDarkTheme = isDarkTheme
|
isDarkTheme = isDarkTheme,
|
||||||
|
displayName = accountName.ifEmpty { accountUsername } // 🔥 Для инициалов
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1247,7 +1248,8 @@ fun ChatItem(
|
|||||||
size = 56.dp,
|
size = 56.dp,
|
||||||
isDarkTheme = isDarkTheme,
|
isDarkTheme = isDarkTheme,
|
||||||
showOnlineIndicator = true,
|
showOnlineIndicator = true,
|
||||||
isOnline = chat.isOnline
|
isOnline = chat.isOnline,
|
||||||
|
displayName = chat.name // 🔥 Для инициалов
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(12.dp))
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
@@ -1724,11 +1726,20 @@ fun DialogItemContent(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// 🔥 Формируем displayName для инициалов в placeholder
|
||||||
|
val avatarDisplayName = when {
|
||||||
|
dialog.opponentTitle.isNotEmpty() &&
|
||||||
|
dialog.opponentTitle != dialog.opponentKey &&
|
||||||
|
!dialog.opponentTitle.startsWith(dialog.opponentKey.take(7)) -> dialog.opponentTitle
|
||||||
|
dialog.opponentUsername.isNotEmpty() -> dialog.opponentUsername
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
com.rosetta.messenger.ui.components.AvatarImage(
|
com.rosetta.messenger.ui.components.AvatarImage(
|
||||||
publicKey = dialog.opponentKey,
|
publicKey = dialog.opponentKey,
|
||||||
avatarRepository = avatarRepository,
|
avatarRepository = avatarRepository,
|
||||||
size = 56.dp,
|
size = 56.dp,
|
||||||
isDarkTheme = isDarkTheme
|
isDarkTheme = isDarkTheme,
|
||||||
|
displayName = avatarDisplayName
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -193,12 +193,22 @@ private fun SearchResultItem(
|
|||||||
modifier = Modifier.size(20.dp)
|
modifier = Modifier.size(20.dp)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
// Приоритет: title -> username -> publicKey
|
||||||
|
val initials = when {
|
||||||
|
user.title.isNotEmpty() &&
|
||||||
|
user.title != user.publicKey &&
|
||||||
|
!user.title.startsWith(user.publicKey.take(7)) -> {
|
||||||
|
getInitials(user.title)
|
||||||
|
}
|
||||||
|
user.username.isNotEmpty() -> {
|
||||||
|
user.username.take(2).uppercase()
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
user.publicKey.take(2).uppercase()
|
||||||
|
}
|
||||||
|
}
|
||||||
Text(
|
Text(
|
||||||
text = if (user.title.isNotEmpty()) {
|
text = initials,
|
||||||
getInitials(user.title)
|
|
||||||
} else {
|
|
||||||
user.publicKey.take(2).uppercase()
|
|
||||||
},
|
|
||||||
fontSize = 15.sp,
|
fontSize = 15.sp,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
color = avatarColors.textColor
|
color = avatarColors.textColor
|
||||||
|
|||||||
@@ -369,7 +369,8 @@ private fun RecentUserItem(
|
|||||||
size = 48.dp,
|
size = 48.dp,
|
||||||
isDarkTheme = isDarkTheme,
|
isDarkTheme = isDarkTheme,
|
||||||
showOnlineIndicator = false,
|
showOnlineIndicator = false,
|
||||||
isOnline = false
|
isOnline = false,
|
||||||
|
displayName = user.title.ifEmpty { user.username } // 🔥 Для инициалов
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(12.dp))
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
|
|||||||
@@ -197,8 +197,19 @@ class AppleEmojiEditTextView @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Восстанавливаем курсор, убедившись что он в допустимых пределах
|
// Восстанавливаем курсор, убедившись что он в допустимых пределах
|
||||||
if (cursorPosition >= 0 && cursorPosition <= editable.length) {
|
// 🔥 Захватываем длину ДО post, т.к. text может измениться
|
||||||
post { setSelection(cursorPosition.coerceIn(0, editable.length)) }
|
val safePosition = cursorPosition.coerceIn(0, editable.length)
|
||||||
|
if (safePosition >= 0) {
|
||||||
|
post {
|
||||||
|
try {
|
||||||
|
val currentLength = text?.length ?: 0
|
||||||
|
if (safePosition <= currentLength) {
|
||||||
|
setSelection(safePosition.coerceIn(0, currentLength))
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// Игнорируем - текст мог измениться
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
isUpdating = false
|
isUpdating = false
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import com.rosetta.messenger.repository.AvatarRepository
|
|||||||
import com.rosetta.messenger.ui.chats.AvatarColors
|
import com.rosetta.messenger.ui.chats.AvatarColors
|
||||||
import com.rosetta.messenger.ui.chats.getAvatarColor
|
import com.rosetta.messenger.ui.chats.getAvatarColor
|
||||||
import com.rosetta.messenger.ui.chats.getAvatarText
|
import com.rosetta.messenger.ui.chats.getAvatarText
|
||||||
|
import com.rosetta.messenger.ui.chats.getInitials
|
||||||
import com.rosetta.messenger.utils.AvatarFileManager
|
import com.rosetta.messenger.utils.AvatarFileManager
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@@ -57,7 +58,8 @@ fun AvatarImage(
|
|||||||
onClick: (() -> Unit)? = null,
|
onClick: (() -> Unit)? = null,
|
||||||
showOnlineIndicator: Boolean = false,
|
showOnlineIndicator: Boolean = false,
|
||||||
isOnline: Boolean = false,
|
isOnline: Boolean = false,
|
||||||
shape: Shape = CircleShape
|
shape: Shape = CircleShape,
|
||||||
|
displayName: String? = null // 🔥 Имя для инициалов (title/username)
|
||||||
) {
|
) {
|
||||||
// Получаем аватары из репозитория
|
// Получаем аватары из репозитория
|
||||||
val avatars by avatarRepository?.getAvatars(publicKey, allDecode = false)?.collectAsState()
|
val avatars by avatarRepository?.getAvatars(publicKey, allDecode = false)?.collectAsState()
|
||||||
@@ -113,7 +115,8 @@ fun AvatarImage(
|
|||||||
publicKey = publicKey,
|
publicKey = publicKey,
|
||||||
size = size,
|
size = size,
|
||||||
isDarkTheme = isDarkTheme,
|
isDarkTheme = isDarkTheme,
|
||||||
shape = shape
|
shape = shape,
|
||||||
|
displayName = displayName
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,10 +141,16 @@ fun AvatarPlaceholder(
|
|||||||
size: Dp = 40.dp,
|
size: Dp = 40.dp,
|
||||||
isDarkTheme: Boolean,
|
isDarkTheme: Boolean,
|
||||||
fontSize: TextUnit? = null,
|
fontSize: TextUnit? = null,
|
||||||
shape: Shape = CircleShape
|
shape: Shape = CircleShape,
|
||||||
|
displayName: String? = null // 🔥 Имя для инициалов
|
||||||
) {
|
) {
|
||||||
val avatarColors = getAvatarColor(publicKey, isDarkTheme)
|
val avatarColors = getAvatarColor(publicKey, isDarkTheme)
|
||||||
val avatarText = getAvatarText(publicKey)
|
// 🔥 Используем displayName для инициалов, если оно есть
|
||||||
|
val avatarText = if (!displayName.isNullOrEmpty() && displayName != publicKey && !displayName.startsWith(publicKey.take(7))) {
|
||||||
|
getInitials(displayName)
|
||||||
|
} else {
|
||||||
|
getAvatarText(publicKey)
|
||||||
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package com.rosetta.messenger.ui.settings
|
package com.rosetta.messenger.ui.settings
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.compose.animation.core.LinearOutSlowInEasing
|
import androidx.compose.animation.core.LinearOutSlowInEasing
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
@@ -36,6 +38,7 @@ import androidx.compose.ui.platform.LocalDensity
|
|||||||
import androidx.compose.ui.platform.LocalFocusManager
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||||
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
@@ -82,6 +85,26 @@ fun OtherProfileScreen(
|
|||||||
val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
|
val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
|
||||||
val avatarColors = getAvatarColor(user.publicKey, isDarkTheme)
|
val avatarColors = getAvatarColor(user.publicKey, isDarkTheme)
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val view = LocalView.current
|
||||||
|
val window = remember { (view.context as? Activity)?.window }
|
||||||
|
|
||||||
|
// 🎨 Статус бар - прозрачный с белыми иконками (поверх аватара)
|
||||||
|
DisposableEffect(Unit) {
|
||||||
|
val insetsController = window?.let { WindowCompat.getInsetsController(it, view) }
|
||||||
|
// Сохраняем оригинальные значения
|
||||||
|
val originalStatusBarColor = window?.statusBarColor ?: 0
|
||||||
|
val originalLightStatusBars = insetsController?.isAppearanceLightStatusBars ?: false
|
||||||
|
|
||||||
|
// Прозрачный статус бар с белыми иконками
|
||||||
|
window?.statusBarColor = android.graphics.Color.TRANSPARENT
|
||||||
|
insetsController?.isAppearanceLightStatusBars = false
|
||||||
|
|
||||||
|
onDispose {
|
||||||
|
// Восстанавливаем при выходе
|
||||||
|
window?.statusBarColor = originalStatusBarColor
|
||||||
|
insetsController?.isAppearanceLightStatusBars = originalLightStatusBars
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 🔥 Получаем тот же ChatViewModel что и в ChatDetailScreen для очистки истории
|
// 🔥 Получаем тот же ChatViewModel что и в ChatDetailScreen для очистки истории
|
||||||
val viewModel: ChatViewModel = viewModel(key = "chat_${user.publicKey}")
|
val viewModel: ChatViewModel = viewModel(key = "chat_${user.publicKey}")
|
||||||
|
|||||||
Reference in New Issue
Block a user