Фикс скелетона и залипания вкладок в профиле

This commit is contained in:
2026-03-20 12:26:33 +05:00
parent 004b54ec7c
commit 0353f845a5
3 changed files with 42 additions and 11 deletions

View File

@@ -120,6 +120,7 @@ import kotlinx.coroutines.withContext
private val groupMembersCountCache = java.util.concurrent.ConcurrentHashMap<String, Int>()
private const val GROUP_MESSAGE_STACK_TIME_DIFF_MS = 5 * 60_000L
private const val DIRECT_MESSAGE_STACK_TIME_DIFF_MS = 60_000L
private const val MESSAGE_SKELETON_VISIBILITY_DELAY_MS = 180L
private fun isSameLocalDay(firstTimestampMs: Long, secondTimestampMs: Long): Boolean {
val firstCalendar =
@@ -631,6 +632,15 @@ fun ChatDetailScreen(
// If typing, the user is obviously online — never show "offline" while typing
val isOnline = rawIsOnline || isTyping
val isLoading by viewModel.isLoading.collectAsState() // 🔥 Для скелетона
val showMessageSkeleton by
produceState(initialValue = false, key1 = isLoading) {
if (!isLoading) {
value = false
return@produceState
}
delay(MESSAGE_SKELETON_VISIBILITY_DELAY_MS)
value = isLoading
}
// <20>🔥 Reply/Forward state
val replyMessages by viewModel.replyMessages.collectAsState()
@@ -2390,7 +2400,7 @@ fun ChatDetailScreen(
when {
// 🔥 СКЕЛЕТОН - показываем пока загружаются
// сообщения
isLoading -> {
showMessageSkeleton -> {
MessageSkeletonList(
isDarkTheme = isDarkTheme,
isGroupChat = isGroupChat,
@@ -2398,6 +2408,9 @@ fun ChatDetailScreen(
Modifier.fillMaxSize()
)
}
isLoading && messages.isEmpty() -> {
Box(modifier = Modifier.fillMaxSize())
}
// Пустое состояние (нет сообщений)
messages.isEmpty() -> {
Column(

View File

@@ -85,6 +85,7 @@ import compose.icons.tablericons.*
import com.rosetta.messenger.ui.icons.TelegramIcons
import java.text.SimpleDateFormat
import java.util.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeoutOrNull
import kotlin.math.hypot
@@ -108,6 +109,8 @@ private enum class DeviceResolveAction {
DECLINE
}
private const val SKELETON_VISIBILITY_DELAY_MS = 180L
// Avatar colors matching React Native app (Mantine inspired)
// Light theme colors (background lighter, text darker)
private val avatarColorsLight =
@@ -1791,7 +1794,18 @@ fun ChatsListScreen(
val requests = chatsState.requests
val requestsCount = chatsState.requestsCount
val showSkeleton = isLoading
val showSkeleton by
produceState(
initialValue = false,
key1 = isLoading
) {
if (!isLoading) {
value = false
return@produceState
}
delay(SKELETON_VISIBILITY_DELAY_MS)
value = isLoading
}
val chatListState = rememberLazyListState()
var isRequestsVisible by remember { mutableStateOf(true) }
var requestsPullProgress by remember { mutableStateOf(0f) }
@@ -2010,6 +2024,8 @@ fun ChatsListScreen(
} // Close Box wrapper
} else if (showSkeleton) {
ChatsListSkeleton(isDarkTheme = isDarkTheme)
} else if (isLoading && chatsState.isEmpty) {
Box(modifier = Modifier.fillMaxSize())
} else if (chatsState.isEmpty) {
EmptyChatsState(
isDarkTheme = isDarkTheme,

View File

@@ -108,6 +108,7 @@ import compose.icons.TablerIcons
import compose.icons.tablericons.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.flowOf
import androidx.compose.runtime.snapshotFlow
import kotlinx.coroutines.launch
@@ -199,19 +200,12 @@ fun OtherProfileScreen(
var avatarViewerTimestamp by remember { mutableStateOf(0L) }
var imageViewerInitialIndex by remember { mutableIntStateOf(0) }
var selectedTab by remember { mutableStateOf(OtherProfileTab.MEDIA) }
var tabSwitchJob by remember { mutableStateOf<Job?>(null) }
val pagerState = rememberPagerState(
initialPage = 0,
pageCount = { OtherProfileTab.entries.size }
)
// Tab click → animate pager
LaunchedEffect(selectedTab) {
val page = OtherProfileTab.entries.indexOf(selectedTab)
if (pagerState.currentPage != page) {
pagerState.animateScrollToPage(page)
}
}
// Pager swipe → update tab + control swipe-back
LaunchedEffect(pagerState) {
snapshotFlow { pagerState.currentPage }.collect { page ->
@@ -826,7 +820,15 @@ fun OtherProfileScreen(
) {
OtherProfileSharedTabs(
selectedTab = selectedTab,
onTabSelected = { tab -> selectedTab = tab },
onTabSelected = { tab ->
val targetPage = OtherProfileTab.entries.indexOf(tab)
if (targetPage == -1) return@OtherProfileSharedTabs
selectedTab = tab
tabSwitchJob?.cancel()
tabSwitchJob = coroutineScope.launch {
runCatching { pagerState.animateScrollToPage(targetPage) }
}
},
isDarkTheme = isDarkTheme
)
}