chat materials pagination and ui improvements

This commit is contained in:
2025-03-27 03:50:39 +03:00
parent 807c23926e
commit 51356aa4dd
13 changed files with 359 additions and 48 deletions
@@ -27,7 +27,7 @@ data class VkAudioData(
@Json(name = "title") val title: String,
@Json(name = "owner_id") val ownerId: Int,
@Json(name = "access_key") val accessKey: String,
@Json(name = "thumb") val thumb: Thumb
@Json(name = "thumb") val thumb: Thumb?
) {
@JsonClass(generateAdapter = true)
@@ -1,22 +1,22 @@
package dev.meloda.fast.model.api.data
import dev.meloda.fast.model.api.domain.VkPhotoDomain
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import dev.meloda.fast.model.api.domain.VkPhotoDomain
@JsonClass(generateAdapter = true)
data class VkPhotoData(
@Json(name = "album_id") val albumId: Int,
val date: Int,
val id: Int,
@Json(name = "date") val date: Int?,
@Json(name = "id") val id: Int,
@Json(name = "owner_id") val ownerId: Int,
@Json(name = "has_tags") val hasTags: Boolean,
@Json(name = "has_tags") val hasTags: Boolean?,
@Json(name = "access_key") val accessKey: String?,
val sizes: List<Size>,
val text: String?,
@Json(name = "sizes") val sizes: List<Size>,
@Json(name = "text") val text: String?,
@Json(name = "user_id") val userId: Int?,
val lat: Double?,
val long: Double?,
@Json(name = "lat") val lat: Double?,
@Json(name = "long") val long: Double?,
@Json(name = "post_id") val postId: Int?
) : VkAttachmentData {
@@ -33,7 +33,7 @@ data class VkPhotoData(
date = date,
id = id,
ownerId = ownerId,
hasTags = hasTags,
hasTags = hasTags == true,
accessKey = accessKey,
sizes = sizes,
text = text,
@@ -8,7 +8,7 @@ import java.util.Stack
// TODO: 11/04/2024, Danil Nikolaev: review
data class VkPhotoDomain(
val albumId: Int,
val date: Int,
val date: Int?,
val id: Int,
val ownerId: Int,
val hasTags: Boolean,
@@ -9,10 +9,12 @@ import dev.meloda.fast.chatmaterials.navigation.ChatMaterials
import dev.meloda.fast.chatmaterials.util.asPresentation
import dev.meloda.fast.common.extensions.listenValue
import dev.meloda.fast.common.extensions.setValue
import dev.meloda.fast.data.State
import dev.meloda.fast.data.processState
import dev.meloda.fast.domain.MessagesUseCase
import dev.meloda.fast.model.BaseError
import dev.meloda.fast.model.api.domain.VkAttachmentHistoryMessage
import dev.meloda.fast.network.VkErrorCode
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
@@ -60,7 +62,7 @@ class ChatMaterialsViewModelImpl(
}
override fun onPaginationConditionsMet() {
currentOffset.update { screenState.value.materials.size }
currentOffset.setValue { old -> old + LOAD_COUNT }
loadChatMaterials()
}
@@ -86,20 +88,24 @@ class ChatMaterialsViewModelImpl(
conversationMessageId = screenState.value.conversationMessageId
).listenValue(viewModelScope) { state ->
state.processState(
error = { error ->
},
error = ::handleError,
success = { response ->
val itemsCountSufficient = response.size == LOAD_COUNT
canPaginate.setValue { itemsCountSufficient }
val paginationExhausted = !itemsCountSufficient &&
screenState.value.materials.size >= LOAD_COUNT
val paginationExhausted = !itemsCountSufficient
&& screenState.value.materials.isNotEmpty()
val loadedMaterials = response.map(VkAttachmentHistoryMessage::asPresentation)
val loadedMaterials = response.mapNotNull(VkAttachmentHistoryMessage::asPresentation)
val newState = screenState.value.copy(
isPaginationExhausted = paginationExhausted
isPaginationExhausted = paginationExhausted,
conversationMessageId = if (loadedMaterials.size + offset > 200) {
currentOffset.setValue { 0 }
loadedMaterials.lastOrNull()?.conversationMessageId ?: -1
} else {
screenState.value.conversationMessageId
}
)
if (offset == 0) {
@@ -125,7 +131,45 @@ class ChatMaterialsViewModelImpl(
}
}
private fun handleError(error: State.Error) {
when (error) {
is State.Error.ApiError -> {
when (error.errorCode) {
VkErrorCode.USER_AUTHORIZATION_FAILED -> {
baseError.setValue { BaseError.SessionExpired }
}
else -> {
baseError.setValue {
BaseError.SimpleError(message = error.errorMessage)
}
}
}
}
State.Error.ConnectionError -> {
baseError.setValue {
BaseError.SimpleError(message = "Connection error")
}
}
State.Error.InternalError -> {
baseError.setValue {
BaseError.SimpleError(message = "Internal error")
}
}
State.Error.UnknownError -> {
baseError.setValue {
BaseError.SimpleError(message = "Unknown error")
}
}
else -> Unit
}
}
companion object {
const val LOAD_COUNT = 30
const val LOAD_COUNT = 200
}
}
@@ -1,36 +1,43 @@
package dev.meloda.fast.chatmaterials.model
sealed class UiChatMaterial {
sealed class UiChatMaterial(
open val conversationMessageId: Int
) {
data class Photo(
override val conversationMessageId: Int,
val previewUrl: String
) : UiChatMaterial()
) : UiChatMaterial(conversationMessageId)
data class Video(
override val conversationMessageId: Int,
val previewUrl: String?,
val title: String,
val views: Int,
val duration: String
) : UiChatMaterial()
) : UiChatMaterial(conversationMessageId)
data class Audio(
override val conversationMessageId: Int,
val previewUrl: String?,
val title: String,
val artist: String,
val duration: String
) : UiChatMaterial()
) : UiChatMaterial(conversationMessageId)
data class File(
override val conversationMessageId: Int,
val previewUrl: String?,
val title: String,
val size: String,
val extension: String
) : UiChatMaterial()
) : UiChatMaterial(conversationMessageId)
data class Link(
override val conversationMessageId: Int,
val previewUrl: String?,
val title: String?,
val url: String,
val urlFirstChar: String
) : UiChatMaterial()
) : UiChatMaterial(conversationMessageId)
}
@@ -17,9 +17,11 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.PrimaryTabRow
import androidx.compose.material3.Scaffold
import androidx.compose.material3.ScrollableTabRow
import androidx.compose.material3.Tab
import androidx.compose.material3.TabRowDefaults
import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
@@ -171,10 +173,17 @@ fun ChatMaterialsScreen(
}
}
)
PrimaryTabRow(
ScrollableTabRow(
modifier = Modifier.fillMaxWidth(),
selectedTabIndex = selectedTabIndex,
containerColor = Color.Transparent
containerColor = Color.Transparent,
edgePadding = 0.dp,
indicator = { tabPositions ->
TabRowDefaults.PrimaryIndicator(
modifier = Modifier.tabIndicatorOffset(tabPositions[selectedTabIndex]),
color = MaterialTheme.colorScheme.primary
)
}
) {
tabItems.forEachIndexed { index, item ->
Tab(
@@ -19,7 +19,13 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.KeyboardArrowUp
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
@@ -28,9 +34,11 @@ import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
@@ -60,6 +68,8 @@ import dev.meloda.fast.ui.components.FullScreenLoader
import dev.meloda.fast.ui.components.NoItemsView
import dev.meloda.fast.ui.theme.LocalHazeState
import dev.meloda.fast.ui.theme.LocalThemeConfig
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -74,6 +84,7 @@ fun FileMaterialsScreen(
setCanScrollBackward: (Boolean) -> Unit,
onPaginationConditionsMet: () -> Unit
) {
val coroutineScope = rememberCoroutineScope()
val hazeState = LocalHazeState.current
val currentTheme = LocalThemeConfig.current
val listState = rememberLazyListState()
@@ -84,6 +95,20 @@ fun FileMaterialsScreen(
.collect(setCanScrollBackward)
}
val paginationConditionMet by remember(canPaginate, listState) {
derivedStateOf {
canPaginate &&
(listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index
?: -9) >= (listState.layoutInfo.totalItemsCount - 6)
}
}
LaunchedEffect(paginationConditionMet) {
if (paginationConditionMet && !screenState.isPaginating) {
onPaginationConditionsMet()
}
}
when {
baseError != null -> {
when (baseError) {
@@ -207,6 +232,37 @@ fun FileMaterialsScreen(
}
}
}
item {
Column(
modifier = Modifier
.fillMaxWidth()
.animateItem(fadeInSpec = null, fadeOutSpec = null),
horizontalAlignment = Alignment.CenterHorizontally
) {
if (screenState.isPaginating) {
CircularProgressIndicator()
}
if (screenState.isPaginationExhausted) {
IconButton(
onClick = {
coroutineScope.launch(Dispatchers.Main) {
listState.scrollToItem(14)
listState.animateScrollToItem(0)
}
},
colors = IconButtonDefaults.filledIconButtonColors()
) {
Icon(
imageVector = Icons.Rounded.KeyboardArrowUp,
contentDescription = null
)
}
}
Spacer(modifier = Modifier.height(8.dp))
}
}
item {
Spacer(modifier = Modifier.height(padding.calculateBottomPadding()))
}
@@ -19,7 +19,13 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.KeyboardArrowUp
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
@@ -28,9 +34,11 @@ import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
@@ -60,6 +68,8 @@ import dev.meloda.fast.ui.components.FullScreenLoader
import dev.meloda.fast.ui.components.NoItemsView
import dev.meloda.fast.ui.theme.LocalHazeState
import dev.meloda.fast.ui.theme.LocalThemeConfig
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -74,6 +84,7 @@ fun LinkMaterialsScreen(
setCanScrollBackward: (Boolean) -> Unit,
onPaginationConditionsMet: () -> Unit
) {
val coroutineScope = rememberCoroutineScope()
val hazeState = LocalHazeState.current
val currentTheme = LocalThemeConfig.current
val listState = rememberLazyListState()
@@ -84,6 +95,20 @@ fun LinkMaterialsScreen(
.collect(setCanScrollBackward)
}
val paginationConditionMet by remember(canPaginate, listState) {
derivedStateOf {
canPaginate &&
(listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index
?: -9) >= (listState.layoutInfo.totalItemsCount - 6)
}
}
LaunchedEffect(paginationConditionMet) {
if (paginationConditionMet && !screenState.isPaginating) {
onPaginationConditionsMet()
}
}
when {
baseError != null -> {
when (baseError) {
@@ -226,6 +251,37 @@ fun LinkMaterialsScreen(
}
}
}
item {
Column(
modifier = Modifier
.fillMaxWidth()
.animateItem(fadeInSpec = null, fadeOutSpec = null),
horizontalAlignment = Alignment.CenterHorizontally
) {
if (screenState.isPaginating) {
CircularProgressIndicator()
}
if (screenState.isPaginationExhausted) {
IconButton(
onClick = {
coroutineScope.launch(Dispatchers.Main) {
listState.scrollToItem(14)
listState.animateScrollToItem(0)
}
},
colors = IconButtonDefaults.filledIconButtonColors()
) {
Icon(
imageVector = Icons.Rounded.KeyboardArrowUp,
contentDescription = null
)
}
}
Spacer(modifier = Modifier.height(8.dp))
}
}
item {
Spacer(modifier = Modifier.height(padding.calculateBottomPadding()))
}
@@ -2,6 +2,7 @@ package dev.meloda.fast.chatmaterials.presentation.materials
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
@@ -12,15 +13,26 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.KeyboardArrowUp
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -39,6 +51,8 @@ import dev.meloda.fast.ui.components.FullScreenLoader
import dev.meloda.fast.ui.components.NoItemsView
import dev.meloda.fast.ui.theme.LocalHazeState
import dev.meloda.fast.ui.theme.LocalThemeConfig
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -54,6 +68,7 @@ fun PhotoMaterialsScreen(
onPhotoClicked: (String) -> Unit,
onPaginationConditionsMet: () -> Unit
) {
val coroutineScope = rememberCoroutineScope()
val hazeState = LocalHazeState.current
val currentTheme = LocalThemeConfig.current
val gridState = rememberLazyGridState()
@@ -64,6 +79,20 @@ fun PhotoMaterialsScreen(
.collect(setCanScrollBackward)
}
val paginationConditionMet by remember(canPaginate, gridState) {
derivedStateOf {
canPaginate &&
(gridState.layoutInfo.visibleItemsInfo.lastOrNull()?.index
?: -9) >= (gridState.layoutInfo.totalItemsCount - 6)
}
}
LaunchedEffect(paginationConditionMet) {
if (paginationConditionMet && !screenState.isPaginating) {
onPaginationConditionsMet()
}
}
when {
baseError != null -> {
when (baseError) {
@@ -121,10 +150,8 @@ fun PhotoMaterialsScreen(
.fillMaxSize()
) {
repeat(3) {
item {
Spacer(modifier = Modifier.height(padding.calculateTopPadding()))
}
item(span = { GridItemSpan(3) }) {
Spacer(modifier = Modifier.height(padding.calculateTopPadding()))
}
items(items = screenState.materials) { item ->
item as UiChatMaterial.Photo
@@ -142,11 +169,46 @@ fun PhotoMaterialsScreen(
)
)
}
repeat(3) {
item {
Spacer(modifier = Modifier.height(padding.calculateBottomPadding()))
item(span = { GridItemSpan(3) }) {
Column(
modifier = Modifier
.fillMaxWidth()
.animateItem(fadeInSpec = null, fadeOutSpec = null),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
if (screenState.isPaginating) {
CircularProgressIndicator()
}
if (screenState.isPaginationExhausted) {
Spacer(modifier = Modifier.height(32.dp))
IconButton(
onClick = {
coroutineScope.launch(Dispatchers.Main) {
gridState.scrollToItem(14)
gridState.animateScrollToItem(0)
}
},
colors = IconButtonDefaults.filledIconButtonColors()
) {
Icon(
imageVector = Icons.Rounded.KeyboardArrowUp,
contentDescription = null
)
}
}
Spacer(modifier = Modifier.height(8.dp))
}
}
item {
Spacer(modifier = Modifier.height(padding.calculateBottomPadding()))
}
item(span = { GridItemSpan(3) }) {
Spacer(modifier = Modifier.height(padding.calculateBottomPadding()))
}
}
if (screenState.materials.isEmpty()) {
@@ -19,7 +19,13 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.KeyboardArrowUp
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
@@ -27,6 +33,10 @@ import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -51,6 +61,8 @@ import dev.meloda.fast.ui.components.FullScreenLoader
import dev.meloda.fast.ui.components.NoItemsView
import dev.meloda.fast.ui.theme.LocalHazeState
import dev.meloda.fast.ui.theme.LocalThemeConfig
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -65,6 +77,7 @@ fun VideoMaterialsScreen(
setCanScrollBackward: (Boolean) -> Unit,
onPaginationConditionsMet: () -> Unit
) {
val coroutineScope = rememberCoroutineScope()
val hazeState = LocalHazeState.current
val currentTheme = LocalThemeConfig.current
val listState = rememberLazyListState()
@@ -75,6 +88,20 @@ fun VideoMaterialsScreen(
.collect(setCanScrollBackward)
}
val paginationConditionMet by remember(canPaginate, listState) {
derivedStateOf {
canPaginate &&
(listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index
?: -9) >= (listState.layoutInfo.totalItemsCount - 6)
}
}
LaunchedEffect(paginationConditionMet) {
if (paginationConditionMet && !screenState.isPaginating) {
onPaginationConditionsMet()
}
}
when {
baseError != null -> {
when (baseError) {
@@ -197,6 +224,37 @@ fun VideoMaterialsScreen(
}
}
}
item {
Column(
modifier = Modifier
.fillMaxWidth()
.animateItem(fadeInSpec = null, fadeOutSpec = null),
horizontalAlignment = Alignment.CenterHorizontally
) {
if (screenState.isPaginating) {
CircularProgressIndicator()
}
if (screenState.isPaginationExhausted) {
IconButton(
onClick = {
coroutineScope.launch(Dispatchers.Main) {
listState.scrollToItem(14)
listState.animateScrollToItem(0)
}
},
colors = IconButtonDefaults.filledIconButtonColors()
) {
Icon(
imageVector = Icons.Rounded.KeyboardArrowUp,
contentDescription = null
)
}
}
Spacer(modifier = Modifier.height(8.dp))
}
}
item {
Spacer(modifier = Modifier.height(padding.calculateBottomPadding()))
}
@@ -1,5 +1,6 @@
package dev.meloda.fast.chatmaterials.util
import android.util.Log
import dev.meloda.fast.chatmaterials.model.UiChatMaterial
import dev.meloda.fast.common.util.AndroidUtils
import dev.meloda.fast.model.api.data.AttachmentType
@@ -11,11 +12,12 @@ import dev.meloda.fast.model.api.domain.VkPhotoDomain
import dev.meloda.fast.model.api.domain.VkVideoDomain
import java.util.Locale
fun VkAttachmentHistoryMessage.asPresentation(): UiChatMaterial =
fun VkAttachmentHistoryMessage.asPresentation(): UiChatMaterial? =
when (val type = this.attachment.type) {
AttachmentType.PHOTO -> {
val attachment = this.attachment as VkPhotoDomain
UiChatMaterial.Photo(
conversationMessageId = this.conversationMessageId,
previewUrl = attachment.getSizeOrSmaller(VkPhotoDomain.SIZE_TYPE_1080_1024)?.url.orEmpty()
)
}
@@ -45,6 +47,7 @@ fun VkAttachmentHistoryMessage.asPresentation(): UiChatMaterial =
builder.toString().format(Locale.getDefault(), *args.toTypedArray())
UiChatMaterial.Video(
conversationMessageId = this.conversationMessageId,
previewUrl = attachment.images.maxByOrNull(VkVideoDomain.VideoImage::width)?.url.orEmpty(),
title = attachment.title,
views = attachment.views,
@@ -77,6 +80,7 @@ fun VkAttachmentHistoryMessage.asPresentation(): UiChatMaterial =
builder.toString().format(Locale.getDefault(), *args.toTypedArray())
UiChatMaterial.Audio(
conversationMessageId = this.conversationMessageId,
previewUrl = null,
title = attachment.title,
artist = attachment.artist,
@@ -108,6 +112,7 @@ fun VkAttachmentHistoryMessage.asPresentation(): UiChatMaterial =
}
UiChatMaterial.File(
conversationMessageId = this.conversationMessageId,
title = attachment.title,
previewUrl = previewUrl,
size = AndroidUtils.bytesToHumanReadableSize(attachment.size.toDouble()),
@@ -119,6 +124,7 @@ fun VkAttachmentHistoryMessage.asPresentation(): UiChatMaterial =
val attachment = this.attachment as VkLinkDomain
UiChatMaterial.Link(
conversationMessageId = this.conversationMessageId,
title = attachment.title,
previewUrl = attachment.photo?.getMaxSize()?.url,
url = attachment.url,
@@ -129,5 +135,8 @@ fun VkAttachmentHistoryMessage.asPresentation(): UiChatMaterial =
)
}
else -> throw IllegalArgumentException("Unsupported type: $type")
else -> {
Log.w("ChatMaterialMapper", "Unsupported type: $type")
null
}
}
@@ -3,6 +3,7 @@ package dev.meloda.fast.conversations.presentation
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.animateIntAsState
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
@@ -17,6 +18,7 @@ import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.lazy.rememberLazyListState
@@ -51,7 +53,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
@@ -277,19 +278,28 @@ fun ConversationsScreen(
}
},
floatingActionButton = {
val offsetY by animateIntAsState(
targetValue = if (listState.isScrollingUp()) 0 else 600
)
Column {
AnimatedVisibility(
visible = listState.isScrollingUp(),
enter = slideIn { IntOffset(0, 600) } + fadeIn(tween(200)),
exit = slideOut { IntOffset(0, 600) } + fadeOut(tween(200))
) {
FloatingActionButton(onClick = onCreateChatButtonClicked) {
// AnimatedVisibility(
// visible = listState.isScrollingUp(),
// enter = slideIn { IntOffset(0, 600) } + fadeIn(tween(200)),
// exit = slideOut { IntOffset(0, 600) } + fadeOut(tween(200))
// ) {
FloatingActionButton(
onClick = onCreateChatButtonClicked,
modifier = Modifier.offset {
IntOffset(0, offsetY)
}
) {
Icon(
painter = painterResource(id = UiR.drawable.ic_baseline_create_24),
contentDescription = "Add chat button"
)
}
}
// }
Spacer(modifier = Modifier.height(LocalBottomPadding.current))
}
@@ -157,8 +157,8 @@ class FriendsViewModelImpl(
val itemsCountSufficient = response.size == LOAD_COUNT
canPaginate.setValue { itemsCountSufficient }
val paginationExhausted = !itemsCountSufficient &&
screenState.value.friends.size >= LOAD_COUNT
val paginationExhausted = !itemsCountSufficient
&& screenState.value.friends.isNotEmpty()
imagesToPreload.setValue {
response.mapNotNull(VkUser::photo100)