chat materials pagination and ui improvements
This commit is contained in:
@@ -27,7 +27,7 @@ data class VkAudioData(
|
|||||||
@Json(name = "title") val title: String,
|
@Json(name = "title") val title: String,
|
||||||
@Json(name = "owner_id") val ownerId: Int,
|
@Json(name = "owner_id") val ownerId: Int,
|
||||||
@Json(name = "access_key") val accessKey: String,
|
@Json(name = "access_key") val accessKey: String,
|
||||||
@Json(name = "thumb") val thumb: Thumb
|
@Json(name = "thumb") val thumb: Thumb?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
package dev.meloda.fast.model.api.data
|
package dev.meloda.fast.model.api.data
|
||||||
|
|
||||||
import dev.meloda.fast.model.api.domain.VkPhotoDomain
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
import dev.meloda.fast.model.api.domain.VkPhotoDomain
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class VkPhotoData(
|
data class VkPhotoData(
|
||||||
@Json(name = "album_id") val albumId: Int,
|
@Json(name = "album_id") val albumId: Int,
|
||||||
val date: Int,
|
@Json(name = "date") val date: Int?,
|
||||||
val id: Int,
|
@Json(name = "id") val id: Int,
|
||||||
@Json(name = "owner_id") val ownerId: 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?,
|
@Json(name = "access_key") val accessKey: String?,
|
||||||
val sizes: List<Size>,
|
@Json(name = "sizes") val sizes: List<Size>,
|
||||||
val text: String?,
|
@Json(name = "text") val text: String?,
|
||||||
@Json(name = "user_id") val userId: Int?,
|
@Json(name = "user_id") val userId: Int?,
|
||||||
val lat: Double?,
|
@Json(name = "lat") val lat: Double?,
|
||||||
val long: Double?,
|
@Json(name = "long") val long: Double?,
|
||||||
@Json(name = "post_id") val postId: Int?
|
@Json(name = "post_id") val postId: Int?
|
||||||
) : VkAttachmentData {
|
) : VkAttachmentData {
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ data class VkPhotoData(
|
|||||||
date = date,
|
date = date,
|
||||||
id = id,
|
id = id,
|
||||||
ownerId = ownerId,
|
ownerId = ownerId,
|
||||||
hasTags = hasTags,
|
hasTags = hasTags == true,
|
||||||
accessKey = accessKey,
|
accessKey = accessKey,
|
||||||
sizes = sizes,
|
sizes = sizes,
|
||||||
text = text,
|
text = text,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import java.util.Stack
|
|||||||
// TODO: 11/04/2024, Danil Nikolaev: review
|
// TODO: 11/04/2024, Danil Nikolaev: review
|
||||||
data class VkPhotoDomain(
|
data class VkPhotoDomain(
|
||||||
val albumId: Int,
|
val albumId: Int,
|
||||||
val date: Int,
|
val date: Int?,
|
||||||
val id: Int,
|
val id: Int,
|
||||||
val ownerId: Int,
|
val ownerId: Int,
|
||||||
val hasTags: Boolean,
|
val hasTags: Boolean,
|
||||||
|
|||||||
+53
-9
@@ -9,10 +9,12 @@ import dev.meloda.fast.chatmaterials.navigation.ChatMaterials
|
|||||||
import dev.meloda.fast.chatmaterials.util.asPresentation
|
import dev.meloda.fast.chatmaterials.util.asPresentation
|
||||||
import dev.meloda.fast.common.extensions.listenValue
|
import dev.meloda.fast.common.extensions.listenValue
|
||||||
import dev.meloda.fast.common.extensions.setValue
|
import dev.meloda.fast.common.extensions.setValue
|
||||||
|
import dev.meloda.fast.data.State
|
||||||
import dev.meloda.fast.data.processState
|
import dev.meloda.fast.data.processState
|
||||||
import dev.meloda.fast.domain.MessagesUseCase
|
import dev.meloda.fast.domain.MessagesUseCase
|
||||||
import dev.meloda.fast.model.BaseError
|
import dev.meloda.fast.model.BaseError
|
||||||
import dev.meloda.fast.model.api.domain.VkAttachmentHistoryMessage
|
import dev.meloda.fast.model.api.domain.VkAttachmentHistoryMessage
|
||||||
|
import dev.meloda.fast.network.VkErrorCode
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
@@ -60,7 +62,7 @@ class ChatMaterialsViewModelImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onPaginationConditionsMet() {
|
override fun onPaginationConditionsMet() {
|
||||||
currentOffset.update { screenState.value.materials.size }
|
currentOffset.setValue { old -> old + LOAD_COUNT }
|
||||||
loadChatMaterials()
|
loadChatMaterials()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,20 +88,24 @@ class ChatMaterialsViewModelImpl(
|
|||||||
conversationMessageId = screenState.value.conversationMessageId
|
conversationMessageId = screenState.value.conversationMessageId
|
||||||
).listenValue(viewModelScope) { state ->
|
).listenValue(viewModelScope) { state ->
|
||||||
state.processState(
|
state.processState(
|
||||||
error = { error ->
|
error = ::handleError,
|
||||||
|
|
||||||
},
|
|
||||||
success = { response ->
|
success = { response ->
|
||||||
val itemsCountSufficient = response.size == LOAD_COUNT
|
val itemsCountSufficient = response.size == LOAD_COUNT
|
||||||
canPaginate.setValue { itemsCountSufficient }
|
canPaginate.setValue { itemsCountSufficient }
|
||||||
|
|
||||||
val paginationExhausted = !itemsCountSufficient &&
|
val paginationExhausted = !itemsCountSufficient
|
||||||
screenState.value.materials.size >= LOAD_COUNT
|
&& screenState.value.materials.isNotEmpty()
|
||||||
|
|
||||||
val loadedMaterials = response.map(VkAttachmentHistoryMessage::asPresentation)
|
val loadedMaterials = response.mapNotNull(VkAttachmentHistoryMessage::asPresentation)
|
||||||
|
|
||||||
val newState = screenState.value.copy(
|
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) {
|
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 {
|
companion object {
|
||||||
const val LOAD_COUNT = 30
|
const val LOAD_COUNT = 200
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-6
@@ -1,36 +1,43 @@
|
|||||||
package dev.meloda.fast.chatmaterials.model
|
package dev.meloda.fast.chatmaterials.model
|
||||||
|
|
||||||
sealed class UiChatMaterial {
|
sealed class UiChatMaterial(
|
||||||
|
open val conversationMessageId: Int
|
||||||
|
) {
|
||||||
|
|
||||||
data class Photo(
|
data class Photo(
|
||||||
|
override val conversationMessageId: Int,
|
||||||
val previewUrl: String
|
val previewUrl: String
|
||||||
) : UiChatMaterial()
|
) : UiChatMaterial(conversationMessageId)
|
||||||
|
|
||||||
data class Video(
|
data class Video(
|
||||||
|
override val conversationMessageId: Int,
|
||||||
val previewUrl: String?,
|
val previewUrl: String?,
|
||||||
val title: String,
|
val title: String,
|
||||||
val views: Int,
|
val views: Int,
|
||||||
val duration: String
|
val duration: String
|
||||||
) : UiChatMaterial()
|
) : UiChatMaterial(conversationMessageId)
|
||||||
|
|
||||||
data class Audio(
|
data class Audio(
|
||||||
|
override val conversationMessageId: Int,
|
||||||
val previewUrl: String?,
|
val previewUrl: String?,
|
||||||
val title: String,
|
val title: String,
|
||||||
val artist: String,
|
val artist: String,
|
||||||
val duration: String
|
val duration: String
|
||||||
) : UiChatMaterial()
|
) : UiChatMaterial(conversationMessageId)
|
||||||
|
|
||||||
data class File(
|
data class File(
|
||||||
|
override val conversationMessageId: Int,
|
||||||
val previewUrl: String?,
|
val previewUrl: String?,
|
||||||
val title: String,
|
val title: String,
|
||||||
val size: String,
|
val size: String,
|
||||||
val extension: String
|
val extension: String
|
||||||
) : UiChatMaterial()
|
) : UiChatMaterial(conversationMessageId)
|
||||||
|
|
||||||
data class Link(
|
data class Link(
|
||||||
|
override val conversationMessageId: Int,
|
||||||
val previewUrl: String?,
|
val previewUrl: String?,
|
||||||
val title: String?,
|
val title: String?,
|
||||||
val url: String,
|
val url: String,
|
||||||
val urlFirstChar: String
|
val urlFirstChar: String
|
||||||
) : UiChatMaterial()
|
) : UiChatMaterial(conversationMessageId)
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-3
@@ -17,9 +17,11 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
|||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.PrimaryTabRow
|
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.ScrollableTabRow
|
||||||
import androidx.compose.material3.Tab
|
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.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
@@ -171,10 +173,17 @@ fun ChatMaterialsScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
PrimaryTabRow(
|
ScrollableTabRow(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
selectedTabIndex = selectedTabIndex,
|
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 ->
|
tabItems.forEachIndexed { index, item ->
|
||||||
Tab(
|
Tab(
|
||||||
|
|||||||
+56
@@ -19,7 +19,13 @@ import androidx.compose.foundation.lazy.LazyColumn
|
|||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
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.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.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||||
@@ -28,9 +34,11 @@ import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
|
|||||||
import androidx.compose.material3.surfaceColorAtElevation
|
import androidx.compose.material3.surfaceColorAtElevation
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.runtime.snapshotFlow
|
import androidx.compose.runtime.snapshotFlow
|
||||||
import androidx.compose.ui.Alignment
|
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.components.NoItemsView
|
||||||
import dev.meloda.fast.ui.theme.LocalHazeState
|
import dev.meloda.fast.ui.theme.LocalHazeState
|
||||||
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -74,6 +84,7 @@ fun FileMaterialsScreen(
|
|||||||
setCanScrollBackward: (Boolean) -> Unit,
|
setCanScrollBackward: (Boolean) -> Unit,
|
||||||
onPaginationConditionsMet: () -> Unit
|
onPaginationConditionsMet: () -> Unit
|
||||||
) {
|
) {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
val hazeState = LocalHazeState.current
|
val hazeState = LocalHazeState.current
|
||||||
val currentTheme = LocalThemeConfig.current
|
val currentTheme = LocalThemeConfig.current
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
@@ -84,6 +95,20 @@ fun FileMaterialsScreen(
|
|||||||
.collect(setCanScrollBackward)
|
.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 {
|
when {
|
||||||
baseError != null -> {
|
baseError != null -> {
|
||||||
when (baseError) {
|
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 {
|
item {
|
||||||
Spacer(modifier = Modifier.height(padding.calculateBottomPadding()))
|
Spacer(modifier = Modifier.height(padding.calculateBottomPadding()))
|
||||||
}
|
}
|
||||||
|
|||||||
+56
@@ -19,7 +19,13 @@ import androidx.compose.foundation.lazy.LazyColumn
|
|||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
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.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.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||||
@@ -28,9 +34,11 @@ import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
|
|||||||
import androidx.compose.material3.surfaceColorAtElevation
|
import androidx.compose.material3.surfaceColorAtElevation
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.runtime.snapshotFlow
|
import androidx.compose.runtime.snapshotFlow
|
||||||
import androidx.compose.ui.Alignment
|
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.components.NoItemsView
|
||||||
import dev.meloda.fast.ui.theme.LocalHazeState
|
import dev.meloda.fast.ui.theme.LocalHazeState
|
||||||
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -74,6 +84,7 @@ fun LinkMaterialsScreen(
|
|||||||
setCanScrollBackward: (Boolean) -> Unit,
|
setCanScrollBackward: (Boolean) -> Unit,
|
||||||
onPaginationConditionsMet: () -> Unit
|
onPaginationConditionsMet: () -> Unit
|
||||||
) {
|
) {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
val hazeState = LocalHazeState.current
|
val hazeState = LocalHazeState.current
|
||||||
val currentTheme = LocalThemeConfig.current
|
val currentTheme = LocalThemeConfig.current
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
@@ -84,6 +95,20 @@ fun LinkMaterialsScreen(
|
|||||||
.collect(setCanScrollBackward)
|
.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 {
|
when {
|
||||||
baseError != null -> {
|
baseError != null -> {
|
||||||
when (baseError) {
|
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 {
|
item {
|
||||||
Spacer(modifier = Modifier.height(padding.calculateBottomPadding()))
|
Spacer(modifier = Modifier.height(padding.calculateBottomPadding()))
|
||||||
}
|
}
|
||||||
|
|||||||
+66
-4
@@ -2,6 +2,7 @@ package dev.meloda.fast.chatmaterials.presentation.materials
|
|||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
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.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
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.LazyVerticalGrid
|
||||||
import androidx.compose.foundation.lazy.grid.items
|
import androidx.compose.foundation.lazy.grid.items
|
||||||
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
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.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.PullToRefreshBox
|
||||||
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
|
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
|
||||||
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
|
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
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.runtime.snapshotFlow
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
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.components.NoItemsView
|
||||||
import dev.meloda.fast.ui.theme.LocalHazeState
|
import dev.meloda.fast.ui.theme.LocalHazeState
|
||||||
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -54,6 +68,7 @@ fun PhotoMaterialsScreen(
|
|||||||
onPhotoClicked: (String) -> Unit,
|
onPhotoClicked: (String) -> Unit,
|
||||||
onPaginationConditionsMet: () -> Unit
|
onPaginationConditionsMet: () -> Unit
|
||||||
) {
|
) {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
val hazeState = LocalHazeState.current
|
val hazeState = LocalHazeState.current
|
||||||
val currentTheme = LocalThemeConfig.current
|
val currentTheme = LocalThemeConfig.current
|
||||||
val gridState = rememberLazyGridState()
|
val gridState = rememberLazyGridState()
|
||||||
@@ -64,6 +79,20 @@ fun PhotoMaterialsScreen(
|
|||||||
.collect(setCanScrollBackward)
|
.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 {
|
when {
|
||||||
baseError != null -> {
|
baseError != null -> {
|
||||||
when (baseError) {
|
when (baseError) {
|
||||||
@@ -121,11 +150,9 @@ fun PhotoMaterialsScreen(
|
|||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
|
|
||||||
) {
|
) {
|
||||||
repeat(3) {
|
item(span = { GridItemSpan(3) }) {
|
||||||
item {
|
|
||||||
Spacer(modifier = Modifier.height(padding.calculateTopPadding()))
|
Spacer(modifier = Modifier.height(padding.calculateTopPadding()))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
items(items = screenState.materials) { item ->
|
items(items = screenState.materials) { item ->
|
||||||
item as UiChatMaterial.Photo
|
item as UiChatMaterial.Photo
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
@@ -142,10 +169,45 @@ fun PhotoMaterialsScreen(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
repeat(3) {
|
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 {
|
item {
|
||||||
Spacer(modifier = Modifier.height(padding.calculateBottomPadding()))
|
Spacer(modifier = Modifier.height(padding.calculateBottomPadding()))
|
||||||
}
|
}
|
||||||
|
item(span = { GridItemSpan(3) }) {
|
||||||
|
Spacer(modifier = Modifier.height(padding.calculateBottomPadding()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+58
@@ -19,7 +19,13 @@ import androidx.compose.foundation.lazy.LazyColumn
|
|||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
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.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.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
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.material3.pulltorefresh.rememberPullToRefreshState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
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.runtime.snapshotFlow
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
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.components.NoItemsView
|
||||||
import dev.meloda.fast.ui.theme.LocalHazeState
|
import dev.meloda.fast.ui.theme.LocalHazeState
|
||||||
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -65,6 +77,7 @@ fun VideoMaterialsScreen(
|
|||||||
setCanScrollBackward: (Boolean) -> Unit,
|
setCanScrollBackward: (Boolean) -> Unit,
|
||||||
onPaginationConditionsMet: () -> Unit
|
onPaginationConditionsMet: () -> Unit
|
||||||
) {
|
) {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
val hazeState = LocalHazeState.current
|
val hazeState = LocalHazeState.current
|
||||||
val currentTheme = LocalThemeConfig.current
|
val currentTheme = LocalThemeConfig.current
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
@@ -75,6 +88,20 @@ fun VideoMaterialsScreen(
|
|||||||
.collect(setCanScrollBackward)
|
.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 {
|
when {
|
||||||
baseError != null -> {
|
baseError != null -> {
|
||||||
when (baseError) {
|
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 {
|
item {
|
||||||
Spacer(modifier = Modifier.height(padding.calculateBottomPadding()))
|
Spacer(modifier = Modifier.height(padding.calculateBottomPadding()))
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-2
@@ -1,5 +1,6 @@
|
|||||||
package dev.meloda.fast.chatmaterials.util
|
package dev.meloda.fast.chatmaterials.util
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import dev.meloda.fast.chatmaterials.model.UiChatMaterial
|
import dev.meloda.fast.chatmaterials.model.UiChatMaterial
|
||||||
import dev.meloda.fast.common.util.AndroidUtils
|
import dev.meloda.fast.common.util.AndroidUtils
|
||||||
import dev.meloda.fast.model.api.data.AttachmentType
|
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 dev.meloda.fast.model.api.domain.VkVideoDomain
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
fun VkAttachmentHistoryMessage.asPresentation(): UiChatMaterial =
|
fun VkAttachmentHistoryMessage.asPresentation(): UiChatMaterial? =
|
||||||
when (val type = this.attachment.type) {
|
when (val type = this.attachment.type) {
|
||||||
AttachmentType.PHOTO -> {
|
AttachmentType.PHOTO -> {
|
||||||
val attachment = this.attachment as VkPhotoDomain
|
val attachment = this.attachment as VkPhotoDomain
|
||||||
UiChatMaterial.Photo(
|
UiChatMaterial.Photo(
|
||||||
|
conversationMessageId = this.conversationMessageId,
|
||||||
previewUrl = attachment.getSizeOrSmaller(VkPhotoDomain.SIZE_TYPE_1080_1024)?.url.orEmpty()
|
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())
|
builder.toString().format(Locale.getDefault(), *args.toTypedArray())
|
||||||
|
|
||||||
UiChatMaterial.Video(
|
UiChatMaterial.Video(
|
||||||
|
conversationMessageId = this.conversationMessageId,
|
||||||
previewUrl = attachment.images.maxByOrNull(VkVideoDomain.VideoImage::width)?.url.orEmpty(),
|
previewUrl = attachment.images.maxByOrNull(VkVideoDomain.VideoImage::width)?.url.orEmpty(),
|
||||||
title = attachment.title,
|
title = attachment.title,
|
||||||
views = attachment.views,
|
views = attachment.views,
|
||||||
@@ -77,6 +80,7 @@ fun VkAttachmentHistoryMessage.asPresentation(): UiChatMaterial =
|
|||||||
builder.toString().format(Locale.getDefault(), *args.toTypedArray())
|
builder.toString().format(Locale.getDefault(), *args.toTypedArray())
|
||||||
|
|
||||||
UiChatMaterial.Audio(
|
UiChatMaterial.Audio(
|
||||||
|
conversationMessageId = this.conversationMessageId,
|
||||||
previewUrl = null,
|
previewUrl = null,
|
||||||
title = attachment.title,
|
title = attachment.title,
|
||||||
artist = attachment.artist,
|
artist = attachment.artist,
|
||||||
@@ -108,6 +112,7 @@ fun VkAttachmentHistoryMessage.asPresentation(): UiChatMaterial =
|
|||||||
}
|
}
|
||||||
|
|
||||||
UiChatMaterial.File(
|
UiChatMaterial.File(
|
||||||
|
conversationMessageId = this.conversationMessageId,
|
||||||
title = attachment.title,
|
title = attachment.title,
|
||||||
previewUrl = previewUrl,
|
previewUrl = previewUrl,
|
||||||
size = AndroidUtils.bytesToHumanReadableSize(attachment.size.toDouble()),
|
size = AndroidUtils.bytesToHumanReadableSize(attachment.size.toDouble()),
|
||||||
@@ -119,6 +124,7 @@ fun VkAttachmentHistoryMessage.asPresentation(): UiChatMaterial =
|
|||||||
val attachment = this.attachment as VkLinkDomain
|
val attachment = this.attachment as VkLinkDomain
|
||||||
|
|
||||||
UiChatMaterial.Link(
|
UiChatMaterial.Link(
|
||||||
|
conversationMessageId = this.conversationMessageId,
|
||||||
title = attachment.title,
|
title = attachment.title,
|
||||||
previewUrl = attachment.photo?.getMaxSize()?.url,
|
previewUrl = attachment.photo?.getMaxSize()?.url,
|
||||||
url = attachment.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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-7
@@ -3,6 +3,7 @@ package dev.meloda.fast.conversations.presentation
|
|||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.animateColorAsState
|
import androidx.compose.animation.animateColorAsState
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.animation.core.animateIntAsState
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
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.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.offset
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.statusBars
|
import androidx.compose.foundation.layout.statusBars
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
@@ -51,7 +53,6 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.runtime.snapshotFlow
|
import androidx.compose.runtime.snapshotFlow
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalView
|
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
@@ -277,19 +278,28 @@ fun ConversationsScreen(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
|
val offsetY by animateIntAsState(
|
||||||
|
targetValue = if (listState.isScrollingUp()) 0 else 600
|
||||||
|
)
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
AnimatedVisibility(
|
// AnimatedVisibility(
|
||||||
visible = listState.isScrollingUp(),
|
// visible = listState.isScrollingUp(),
|
||||||
enter = slideIn { IntOffset(0, 600) } + fadeIn(tween(200)),
|
// enter = slideIn { IntOffset(0, 600) } + fadeIn(tween(200)),
|
||||||
exit = slideOut { IntOffset(0, 600) } + fadeOut(tween(200))
|
// exit = slideOut { IntOffset(0, 600) } + fadeOut(tween(200))
|
||||||
|
// ) {
|
||||||
|
FloatingActionButton(
|
||||||
|
onClick = onCreateChatButtonClicked,
|
||||||
|
modifier = Modifier.offset {
|
||||||
|
IntOffset(0, offsetY)
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
FloatingActionButton(onClick = onCreateChatButtonClicked) {
|
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(id = UiR.drawable.ic_baseline_create_24),
|
painter = painterResource(id = UiR.drawable.ic_baseline_create_24),
|
||||||
contentDescription = "Add chat button"
|
contentDescription = "Add chat button"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
// }
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(LocalBottomPadding.current))
|
Spacer(modifier = Modifier.height(LocalBottomPadding.current))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -157,8 +157,8 @@ class FriendsViewModelImpl(
|
|||||||
val itemsCountSufficient = response.size == LOAD_COUNT
|
val itemsCountSufficient = response.size == LOAD_COUNT
|
||||||
canPaginate.setValue { itemsCountSufficient }
|
canPaginate.setValue { itemsCountSufficient }
|
||||||
|
|
||||||
val paginationExhausted = !itemsCountSufficient &&
|
val paginationExhausted = !itemsCountSufficient
|
||||||
screenState.value.friends.size >= LOAD_COUNT
|
&& screenState.value.friends.isNotEmpty()
|
||||||
|
|
||||||
imagesToPreload.setValue {
|
imagesToPreload.setValue {
|
||||||
response.mapNotNull(VkUser::photo100)
|
response.mapNotNull(VkUser::photo100)
|
||||||
|
|||||||
Reference in New Issue
Block a user