Фикс скелетона и залипания вкладок в профиле
This commit is contained in:
@@ -120,6 +120,7 @@ import kotlinx.coroutines.withContext
|
|||||||
private val groupMembersCountCache = java.util.concurrent.ConcurrentHashMap<String, Int>()
|
private val groupMembersCountCache = java.util.concurrent.ConcurrentHashMap<String, Int>()
|
||||||
private const val GROUP_MESSAGE_STACK_TIME_DIFF_MS = 5 * 60_000L
|
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 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 {
|
private fun isSameLocalDay(firstTimestampMs: Long, secondTimestampMs: Long): Boolean {
|
||||||
val firstCalendar =
|
val firstCalendar =
|
||||||
@@ -631,6 +632,15 @@ fun ChatDetailScreen(
|
|||||||
// If typing, the user is obviously online — never show "offline" while typing
|
// If typing, the user is obviously online — never show "offline" while typing
|
||||||
val isOnline = rawIsOnline || isTyping
|
val isOnline = rawIsOnline || isTyping
|
||||||
val isLoading by viewModel.isLoading.collectAsState() // 🔥 Для скелетона
|
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
|
// <20>🔥 Reply/Forward state
|
||||||
val replyMessages by viewModel.replyMessages.collectAsState()
|
val replyMessages by viewModel.replyMessages.collectAsState()
|
||||||
@@ -2390,7 +2400,7 @@ fun ChatDetailScreen(
|
|||||||
when {
|
when {
|
||||||
// 🔥 СКЕЛЕТОН - показываем пока загружаются
|
// 🔥 СКЕЛЕТОН - показываем пока загружаются
|
||||||
// сообщения
|
// сообщения
|
||||||
isLoading -> {
|
showMessageSkeleton -> {
|
||||||
MessageSkeletonList(
|
MessageSkeletonList(
|
||||||
isDarkTheme = isDarkTheme,
|
isDarkTheme = isDarkTheme,
|
||||||
isGroupChat = isGroupChat,
|
isGroupChat = isGroupChat,
|
||||||
@@ -2398,6 +2408,9 @@ fun ChatDetailScreen(
|
|||||||
Modifier.fillMaxSize()
|
Modifier.fillMaxSize()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
isLoading && messages.isEmpty() -> {
|
||||||
|
Box(modifier = Modifier.fillMaxSize())
|
||||||
|
}
|
||||||
// Пустое состояние (нет сообщений)
|
// Пустое состояние (нет сообщений)
|
||||||
messages.isEmpty() -> {
|
messages.isEmpty() -> {
|
||||||
Column(
|
Column(
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ import compose.icons.tablericons.*
|
|||||||
import com.rosetta.messenger.ui.icons.TelegramIcons
|
import com.rosetta.messenger.ui.icons.TelegramIcons
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withTimeoutOrNull
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
import kotlin.math.hypot
|
import kotlin.math.hypot
|
||||||
@@ -108,6 +109,8 @@ private enum class DeviceResolveAction {
|
|||||||
DECLINE
|
DECLINE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const val SKELETON_VISIBILITY_DELAY_MS = 180L
|
||||||
|
|
||||||
// Avatar colors matching React Native app (Mantine inspired)
|
// Avatar colors matching React Native app (Mantine inspired)
|
||||||
// Light theme colors (background lighter, text darker)
|
// Light theme colors (background lighter, text darker)
|
||||||
private val avatarColorsLight =
|
private val avatarColorsLight =
|
||||||
@@ -1791,7 +1794,18 @@ fun ChatsListScreen(
|
|||||||
val requests = chatsState.requests
|
val requests = chatsState.requests
|
||||||
val requestsCount = chatsState.requestsCount
|
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()
|
val chatListState = rememberLazyListState()
|
||||||
var isRequestsVisible by remember { mutableStateOf(true) }
|
var isRequestsVisible by remember { mutableStateOf(true) }
|
||||||
var requestsPullProgress by remember { mutableStateOf(0f) }
|
var requestsPullProgress by remember { mutableStateOf(0f) }
|
||||||
@@ -2010,6 +2024,8 @@ fun ChatsListScreen(
|
|||||||
} // Close Box wrapper
|
} // Close Box wrapper
|
||||||
} else if (showSkeleton) {
|
} else if (showSkeleton) {
|
||||||
ChatsListSkeleton(isDarkTheme = isDarkTheme)
|
ChatsListSkeleton(isDarkTheme = isDarkTheme)
|
||||||
|
} else if (isLoading && chatsState.isEmpty) {
|
||||||
|
Box(modifier = Modifier.fillMaxSize())
|
||||||
} else if (chatsState.isEmpty) {
|
} else if (chatsState.isEmpty) {
|
||||||
EmptyChatsState(
|
EmptyChatsState(
|
||||||
isDarkTheme = isDarkTheme,
|
isDarkTheme = isDarkTheme,
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ import compose.icons.TablerIcons
|
|||||||
import compose.icons.tablericons.*
|
import compose.icons.tablericons.*
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import androidx.compose.runtime.snapshotFlow
|
import androidx.compose.runtime.snapshotFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -199,19 +200,12 @@ fun OtherProfileScreen(
|
|||||||
var avatarViewerTimestamp by remember { mutableStateOf(0L) }
|
var avatarViewerTimestamp by remember { mutableStateOf(0L) }
|
||||||
var imageViewerInitialIndex by remember { mutableIntStateOf(0) }
|
var imageViewerInitialIndex by remember { mutableIntStateOf(0) }
|
||||||
var selectedTab by remember { mutableStateOf(OtherProfileTab.MEDIA) }
|
var selectedTab by remember { mutableStateOf(OtherProfileTab.MEDIA) }
|
||||||
|
var tabSwitchJob by remember { mutableStateOf<Job?>(null) }
|
||||||
val pagerState = rememberPagerState(
|
val pagerState = rememberPagerState(
|
||||||
initialPage = 0,
|
initialPage = 0,
|
||||||
pageCount = { OtherProfileTab.entries.size }
|
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
|
// Pager swipe → update tab + control swipe-back
|
||||||
LaunchedEffect(pagerState) {
|
LaunchedEffect(pagerState) {
|
||||||
snapshotFlow { pagerState.currentPage }.collect { page ->
|
snapshotFlow { pagerState.currentPage }.collect { page ->
|
||||||
@@ -826,7 +820,15 @@ fun OtherProfileScreen(
|
|||||||
) {
|
) {
|
||||||
OtherProfileSharedTabs(
|
OtherProfileSharedTabs(
|
||||||
selectedTab = selectedTab,
|
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
|
isDarkTheme = isDarkTheme
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user