feat: add avatar presence handling in OtherProfileScreen for improved scroll behavior
This commit is contained in:
@@ -285,6 +285,31 @@ class MessageRepository private constructor(private val context: Context) {
|
|||||||
// 🔥 КРИТИЧНО: Обновляем диалог через updateDialogFromMessages
|
// 🔥 КРИТИЧНО: Обновляем диалог через updateDialogFromMessages
|
||||||
dialogDao.updateDialogFromMessages(account, toPublicKey)
|
dialogDao.updateDialogFromMessages(account, toPublicKey)
|
||||||
|
|
||||||
|
// 📁 Для saved messages - гарантируем создание/обновление dialog
|
||||||
|
if (isSavedMessages) {
|
||||||
|
val existing = dialogDao.getDialog(account, account)
|
||||||
|
dialogDao.insertDialog(
|
||||||
|
com.rosetta.messenger.database.DialogEntity(
|
||||||
|
id = existing?.id ?: 0,
|
||||||
|
account = account,
|
||||||
|
opponentKey = account,
|
||||||
|
opponentTitle = existing?.opponentTitle ?: "Saved Messages",
|
||||||
|
opponentUsername = existing?.opponentUsername ?: "",
|
||||||
|
lastMessage = encryptedPlainMessage,
|
||||||
|
lastMessageTimestamp = timestamp,
|
||||||
|
unreadCount = 0,
|
||||||
|
isOnline = 0,
|
||||||
|
lastSeen = existing?.lastSeen ?: 0,
|
||||||
|
verified = existing?.verified ?: 0,
|
||||||
|
iHaveSent = 1,
|
||||||
|
lastMessageFromMe = 1,
|
||||||
|
lastMessageDelivered = 1,
|
||||||
|
lastMessageRead = 1,
|
||||||
|
lastMessageAttachments = serializeAttachments(attachments)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// 🔥 Логируем что записалось в диалог
|
// 🔥 Логируем что записалось в диалог
|
||||||
val dialog = dialogDao.getDialog(account, toPublicKey)
|
val dialog = dialogDao.getDialog(account, toPublicKey)
|
||||||
MessageLogger.logDialogUpdate(
|
MessageLogger.logDialogUpdate(
|
||||||
|
|||||||
@@ -579,8 +579,11 @@ interface DialogDao {
|
|||||||
*/
|
*/
|
||||||
@Transaction
|
@Transaction
|
||||||
suspend fun updateDialogFromMessages(account: String, opponentKey: String) {
|
suspend fun updateDialogFromMessages(account: String, opponentKey: String) {
|
||||||
|
// 📁 Для saved messages dialogKey = account (не "$account:$account")
|
||||||
val dialogKey =
|
val dialogKey =
|
||||||
if (account < opponentKey) "$account:$opponentKey" else "$opponentKey:$account"
|
if (account == opponentKey) account
|
||||||
|
else if (account < opponentKey) "$account:$opponentKey"
|
||||||
|
else "$opponentKey:$account"
|
||||||
|
|
||||||
// 1. Получаем последнее сообщение — O(1) по индексу (account, dialog_key, timestamp)
|
// 1. Получаем последнее сообщение — O(1) по индексу (account, dialog_key, timestamp)
|
||||||
val lastMsg = getLastMessageByDialogKey(account, dialogKey) ?: return
|
val lastMsg = getLastMessageByDialogKey(account, dialogKey) ?: return
|
||||||
@@ -624,7 +627,8 @@ interface DialogDao {
|
|||||||
*/
|
*/
|
||||||
@Transaction
|
@Transaction
|
||||||
suspend fun updateSavedMessagesDialogFromMessages(account: String) {
|
suspend fun updateSavedMessagesDialogFromMessages(account: String) {
|
||||||
val dialogKey = "$account:$account"
|
// 📁 dialogKey для saved messages = account (не "$account:$account")
|
||||||
|
val dialogKey = account
|
||||||
|
|
||||||
val lastMsg = getLastMessageByDialogKey(account, dialogKey) ?: return
|
val lastMsg = getLastMessageByDialogKey(account, dialogKey) ?: return
|
||||||
val existing = getDialog(account, account)
|
val existing = getDialog(account, account)
|
||||||
|
|||||||
@@ -536,9 +536,11 @@ class ChatsListViewModel(application: Application) : AndroidViewModel(applicatio
|
|||||||
// 🔥 Обновляем счетчик requests
|
// 🔥 Обновляем счетчик requests
|
||||||
_requestsCount.value = _requests.value.size
|
_requestsCount.value = _requests.value.size
|
||||||
|
|
||||||
// Вычисляем правильный dialog_key (отсортированная комбинация ключей)
|
// Вычисляем правильный dialog_key
|
||||||
val dialogKey =
|
val dialogKey =
|
||||||
if (currentAccount < opponentKey) {
|
if (currentAccount == opponentKey) {
|
||||||
|
currentAccount // 📁 Saved Messages
|
||||||
|
} else if (currentAccount < opponentKey) {
|
||||||
"$currentAccount:$opponentKey"
|
"$currentAccount:$opponentKey"
|
||||||
} else {
|
} else {
|
||||||
"$opponentKey:$currentAccount"
|
"$opponentKey:$currentAccount"
|
||||||
|
|||||||
@@ -62,11 +62,15 @@ fun SearchScreen(
|
|||||||
val view = LocalView.current
|
val view = LocalView.current
|
||||||
val focusManager = LocalFocusManager.current
|
val focusManager = LocalFocusManager.current
|
||||||
if (!view.isInEditMode) {
|
if (!view.isInEditMode) {
|
||||||
SideEffect {
|
DisposableEffect(isDarkTheme) {
|
||||||
val window = (view.context as android.app.Activity).window
|
val window = (view.context as android.app.Activity).window
|
||||||
val insetsController = androidx.core.view.WindowCompat.getInsetsController(window, view)
|
val insetsController = androidx.core.view.WindowCompat.getInsetsController(window, view)
|
||||||
insetsController.isAppearanceLightStatusBars = !isDarkTheme
|
insetsController.isAppearanceLightStatusBars = !isDarkTheme
|
||||||
window.statusBarColor = android.graphics.Color.TRANSPARENT
|
window.statusBarColor = android.graphics.Color.TRANSPARENT
|
||||||
|
onDispose {
|
||||||
|
// Restore white status bar icons for chat list header
|
||||||
|
insetsController.isAppearanceLightStatusBars = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -227,12 +227,28 @@ fun MessageInputBar(
|
|||||||
},
|
},
|
||||||
hideEmoji = { onToggleEmojiPicker(false) }
|
hideEmoji = { onToggleEmojiPicker(false) }
|
||||||
)
|
)
|
||||||
} else {
|
} else if (coordinator.isKeyboardVisible || coordinator.keyboardHeight > 0.dp) {
|
||||||
// KEYBOARD → EMOJI
|
// KEYBOARD → EMOJI (клавиатура открыта)
|
||||||
coordinator.requestShowEmoji(
|
coordinator.requestShowEmoji(
|
||||||
hideKeyboard = { imm.hideSoftInputFromWindow(view.windowToken, 0) },
|
hideKeyboard = { imm.hideSoftInputFromWindow(view.windowToken, 0) },
|
||||||
showEmoji = { onToggleEmojiPicker(true) }
|
showEmoji = { onToggleEmojiPicker(true) }
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
// Клавиатура НЕ открыта → открываем emoji напрямую
|
||||||
|
// Устанавливаем высоту из сохранённой или дефолт
|
||||||
|
if (coordinator.emojiHeight == 0.dp) {
|
||||||
|
val savedPx = com.rosetta.messenger.ui.components.KeyboardHeightProvider
|
||||||
|
.getSavedKeyboardHeight(context)
|
||||||
|
if (savedPx > 0) {
|
||||||
|
coordinator.emojiHeight = with(density) { savedPx.toDp() }
|
||||||
|
} else {
|
||||||
|
coordinator.emojiHeight = 300.dp // разумный дефолт
|
||||||
|
}
|
||||||
|
}
|
||||||
|
coordinator.isEmojiBoxVisible = true
|
||||||
|
coordinator.openEmojiOnly(
|
||||||
|
showEmoji = { onToggleEmojiPicker(true) }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -420,11 +420,18 @@ fun OtherProfileScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DEBUG LOGS
|
// Коллапс при отсутствии аватара
|
||||||
|
LaunchedEffect(hasAvatar) {
|
||||||
|
if (!hasAvatar) {
|
||||||
|
isPulledDown = false
|
||||||
|
overscrollOffset = 0f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════
|
||||||
// NESTED SCROLL - Telegram style with overscroll support
|
// NESTED SCROLL - Telegram style with overscroll support
|
||||||
// ═══════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════
|
||||||
val nestedScrollConnection = remember {
|
val nestedScrollConnection = remember(hasAvatar) {
|
||||||
object : NestedScrollConnection {
|
object : NestedScrollConnection {
|
||||||
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
|
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
|
||||||
val delta = available.y
|
val delta = available.y
|
||||||
@@ -474,8 +481,8 @@ fun OtherProfileScreen(
|
|||||||
available: Offset,
|
available: Offset,
|
||||||
source: NestedScrollSource
|
source: NestedScrollSource
|
||||||
): Offset {
|
): Offset {
|
||||||
// Overscroll при свайпе вниз от верха
|
// Overscroll при свайпе вниз от верха (только если есть аватар)
|
||||||
if (available.y > 0 && scrollOffset == 0f) {
|
if (available.y > 0 && scrollOffset == 0f && hasAvatar) {
|
||||||
// Telegram: сопротивление если ещё не isPulledDown
|
// Telegram: сопротивление если ещё не isPulledDown
|
||||||
val resistance = if (isPulledDown) 1f else 0.5f
|
val resistance = if (isPulledDown) 1f else 0.5f
|
||||||
val delta = available.y * resistance
|
val delta = available.y * resistance
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ fun RosettaAndroidTheme(
|
|||||||
val insetsController = WindowCompat.getInsetsController(window, view)
|
val insetsController = WindowCompat.getInsetsController(window, view)
|
||||||
// Make status bar transparent for wave animation overlay
|
// Make status bar transparent for wave animation overlay
|
||||||
window.statusBarColor = AndroidColor.TRANSPARENT
|
window.statusBarColor = AndroidColor.TRANSPARENT
|
||||||
insetsController.isAppearanceLightStatusBars = !darkTheme
|
// Note: isAppearanceLightStatusBars is managed per-screen, not globally
|
||||||
|
|
||||||
// Navigation bar: показываем только если есть нативные кнопки
|
// Navigation bar: показываем только если есть нативные кнопки
|
||||||
NavigationModeUtils.applyNavigationBarVisibility(insetsController, context, darkTheme)
|
NavigationModeUtils.applyNavigationBarVisibility(insetsController, context, darkTheme)
|
||||||
|
|||||||
Reference in New Issue
Block a user