forked from melod1n/fast-messenger
[wip] chat materials; some experiments with local composition and blur
This commit is contained in:
@@ -27,6 +27,7 @@ android {
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = Configs.java.toString()
|
||||
freeCompilerArgs = listOf("-opt-in=kotlin.RequiresOptIn", "-Xcontext-receivers")
|
||||
}
|
||||
buildFeatures {
|
||||
compose = true
|
||||
|
||||
-91
@@ -1,91 +0,0 @@
|
||||
package com.meloda.app.fast.chatmaterials
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil.compose.AsyncImage
|
||||
import dev.chrisbanes.haze.HazeState
|
||||
import dev.chrisbanes.haze.haze
|
||||
import dev.chrisbanes.haze.hazeChild
|
||||
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
|
||||
import dev.chrisbanes.haze.materials.HazeMaterials
|
||||
|
||||
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
||||
@OptIn(
|
||||
ExperimentalMaterial3Api::class,
|
||||
ExperimentalHazeMaterialsApi::class
|
||||
)
|
||||
@Composable
|
||||
fun ChatMaterialsScreen(
|
||||
onBack: () -> Unit
|
||||
) {
|
||||
val hazeState = remember { HazeState() }
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(text = "Chat Materials")
|
||||
},
|
||||
colors = TopAppBarDefaults.largeTopAppBarColors(Color.Transparent),
|
||||
modifier = Modifier
|
||||
.hazeChild(
|
||||
state = hazeState,
|
||||
style = HazeMaterials.ultraThin()
|
||||
)
|
||||
.fillMaxWidth(),
|
||||
navigationIcon = {
|
||||
IconButton(
|
||||
onClick = onBack
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Rounded.ArrowBack,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
) {
|
||||
LazyVerticalGrid(
|
||||
columns = GridCells.Adaptive(200.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
modifier = Modifier
|
||||
.haze(
|
||||
state = hazeState,
|
||||
style = HazeMaterials.ultraThin()
|
||||
)
|
||||
) {
|
||||
items(100) { index ->
|
||||
val link = "https://random.imagecdn.app/500/150"
|
||||
|
||||
AsyncImage(
|
||||
model = link,
|
||||
contentDescription = "Image",
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
package com.meloda.app.fast.chatmaterials
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.meloda.app.fast.chatmaterials.model.ChatMaterialsScreenState
|
||||
import com.meloda.app.fast.chatmaterials.navigation.ChatMaterials
|
||||
import com.meloda.app.fast.chatmaterials.util.asPresentation
|
||||
import com.meloda.app.fast.common.extensions.listenValue
|
||||
import com.meloda.app.fast.common.extensions.setValue
|
||||
import com.meloda.app.fast.data.api.messages.MessagesUseCase
|
||||
import com.meloda.app.fast.data.processState
|
||||
import com.meloda.app.fast.model.BaseError
|
||||
import com.meloda.app.fast.model.api.domain.VkAttachmentHistoryMessage
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
interface ChatMaterialsViewModel {
|
||||
val screenState: StateFlow<ChatMaterialsScreenState>
|
||||
val baseError: StateFlow<BaseError?>
|
||||
val imagesToPreload: StateFlow<List<String>>
|
||||
val currentOffset: StateFlow<Int>
|
||||
val canPaginate: StateFlow<Boolean>
|
||||
|
||||
fun onMetPaginationCondition()
|
||||
|
||||
fun onRefresh()
|
||||
|
||||
fun onErrorConsumed()
|
||||
|
||||
fun onTypeChanged(newType: String)
|
||||
}
|
||||
|
||||
class ChatMaterialsViewModelImpl(
|
||||
private val messagesUseCase: MessagesUseCase,
|
||||
savedStateHandle: SavedStateHandle
|
||||
) : ViewModel(), ChatMaterialsViewModel {
|
||||
|
||||
override val screenState = MutableStateFlow(ChatMaterialsScreenState.EMPTY)
|
||||
|
||||
override val baseError = MutableStateFlow<BaseError?>(null)
|
||||
override val imagesToPreload = MutableStateFlow<List<String>>(emptyList())
|
||||
override val currentOffset = MutableStateFlow(0)
|
||||
override val canPaginate = MutableStateFlow(false)
|
||||
|
||||
init {
|
||||
val arguments = ChatMaterials.from(savedStateHandle)
|
||||
|
||||
screenState.setValue { old ->
|
||||
old.copy(
|
||||
peerId = arguments.peerId,
|
||||
conversationMessageId = arguments.conversationMessageId
|
||||
)
|
||||
}
|
||||
|
||||
loadChatMaterials()
|
||||
}
|
||||
|
||||
override fun onMetPaginationCondition() {
|
||||
currentOffset.update { screenState.value.materials.size }
|
||||
loadChatMaterials()
|
||||
}
|
||||
|
||||
override fun onRefresh() {
|
||||
loadChatMaterials(offset = 0)
|
||||
}
|
||||
|
||||
override fun onErrorConsumed() {
|
||||
baseError.setValue { null }
|
||||
}
|
||||
|
||||
override fun onTypeChanged(newType: String) {
|
||||
screenState.setValue { old -> old.copy(attachmentType = newType) }
|
||||
loadChatMaterials(0)
|
||||
}
|
||||
|
||||
private fun loadChatMaterials(
|
||||
offset: Int = currentOffset.value
|
||||
) {
|
||||
messagesUseCase.getHistoryAttachments(
|
||||
peerId = screenState.value.peerId,
|
||||
count = LOAD_COUNT,
|
||||
offset = offset,
|
||||
attachmentTypes = listOf(screenState.value.attachmentType),
|
||||
conversationMessageId = screenState.value.conversationMessageId
|
||||
).listenValue { state ->
|
||||
state.processState(
|
||||
error = { error ->
|
||||
|
||||
},
|
||||
success = { response ->
|
||||
val itemsCountSufficient = response.size == LOAD_COUNT
|
||||
canPaginate.setValue { itemsCountSufficient }
|
||||
|
||||
val paginationExhausted = !itemsCountSufficient &&
|
||||
screenState.value.materials.size >= LOAD_COUNT
|
||||
|
||||
val loadedMaterials = response.map(VkAttachmentHistoryMessage::asPresentation)
|
||||
|
||||
val newState = screenState.value.copy(
|
||||
isPaginationExhausted = paginationExhausted
|
||||
)
|
||||
|
||||
if (offset == 0) {
|
||||
screenState.setValue {
|
||||
newState.copy(materials = loadedMaterials)
|
||||
}
|
||||
} else {
|
||||
screenState.setValue {
|
||||
newState.copy(
|
||||
materials = newState.materials.plus(loadedMaterials)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
screenState.setValue { old ->
|
||||
old.copy(
|
||||
isLoading = offset == 0 && state.isLoading(),
|
||||
isPaginating = offset > 0 && state.isLoading()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val LOAD_COUNT = 100
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.meloda.app.fast.chatmaterials.di
|
||||
|
||||
import com.meloda.app.fast.chatmaterials.ChatMaterialsViewModelImpl
|
||||
import org.koin.androidx.viewmodel.dsl.viewModelOf
|
||||
import org.koin.dsl.module
|
||||
|
||||
val chatMaterialsModule = module {
|
||||
viewModelOf(::ChatMaterialsViewModelImpl)
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.meloda.app.fast.chatmaterials.model
|
||||
|
||||
data class ChatMaterialsScreenState(
|
||||
val isLoading: Boolean,
|
||||
val materials: List<UiChatMaterial>,
|
||||
val attachmentType: String,
|
||||
val isPaginating: Boolean,
|
||||
val isPaginationExhausted: Boolean,
|
||||
val peerId: Int,
|
||||
val conversationMessageId: Int
|
||||
) {
|
||||
|
||||
companion object {
|
||||
val EMPTY: ChatMaterialsScreenState = ChatMaterialsScreenState(
|
||||
isLoading = true,
|
||||
materials = emptyList(),
|
||||
attachmentType = "photo",
|
||||
isPaginating = false,
|
||||
isPaginationExhausted = false,
|
||||
peerId = -1,
|
||||
conversationMessageId = -1
|
||||
)
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package com.meloda.app.fast.chatmaterials.model
|
||||
|
||||
sealed class UiChatMaterial {
|
||||
|
||||
data class Photo(
|
||||
val previewUrl: String
|
||||
) : UiChatMaterial()
|
||||
|
||||
data class Video(
|
||||
val previewUrl: String
|
||||
) : UiChatMaterial()
|
||||
|
||||
data class Audio(
|
||||
val previewUrl: String?,
|
||||
val title: String,
|
||||
val artist: String,
|
||||
val duration: String
|
||||
) : UiChatMaterial()
|
||||
|
||||
data class File(
|
||||
val title: String
|
||||
) : UiChatMaterial()
|
||||
|
||||
data class Link(
|
||||
val title: String,
|
||||
val previewUrl: String?
|
||||
) : UiChatMaterial()
|
||||
}
|
||||
+19
-4
@@ -1,13 +1,23 @@
|
||||
package com.meloda.app.fast.chatmaterials.navigation
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.compose.composable
|
||||
import com.meloda.app.fast.chatmaterials.ChatMaterialsScreen
|
||||
import androidx.navigation.toRoute
|
||||
import com.meloda.app.fast.chatmaterials.presentation.ChatMaterialsScreen
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ChatMaterials(val a: String)
|
||||
data class ChatMaterials(
|
||||
val peerId: Int,
|
||||
val conversationMessageId: Int
|
||||
) {
|
||||
companion object {
|
||||
fun from(savedStateHandle: SavedStateHandle) =
|
||||
savedStateHandle.toRoute<ChatMaterials>()
|
||||
}
|
||||
}
|
||||
|
||||
fun NavGraphBuilder.chatMaterialsRoute(
|
||||
onBack: () -> Unit
|
||||
@@ -19,6 +29,11 @@ fun NavGraphBuilder.chatMaterialsRoute(
|
||||
}
|
||||
}
|
||||
|
||||
fun NavController.navigateToChatMaterials() {
|
||||
this.navigate(ChatMaterials(""))
|
||||
fun NavController.navigateToChatMaterials(peerId: Int, conversationMessageId: Int) {
|
||||
this.navigate(
|
||||
ChatMaterials(
|
||||
peerId = peerId,
|
||||
conversationMessageId = conversationMessageId
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package com.meloda.app.fast.chatmaterials.presentation
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import coil.ImageLoader
|
||||
import coil.compose.AsyncImage
|
||||
import com.meloda.app.fast.chatmaterials.model.UiChatMaterial
|
||||
|
||||
@Composable
|
||||
fun ChatMaterialItem(
|
||||
item: UiChatMaterial,
|
||||
imageLoader: ImageLoader
|
||||
) {
|
||||
when (item) {
|
||||
is UiChatMaterial.Photo -> {
|
||||
AsyncImage(
|
||||
model = item.previewUrl,
|
||||
contentDescription = null,
|
||||
imageLoader = imageLoader,
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.aspectRatio(1f)
|
||||
)
|
||||
}
|
||||
|
||||
is UiChatMaterial.Video -> {
|
||||
AsyncImage(
|
||||
model = item.previewUrl,
|
||||
contentDescription = null,
|
||||
imageLoader = imageLoader,
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.aspectRatio(1f)
|
||||
)
|
||||
}
|
||||
|
||||
is UiChatMaterial.Audio -> {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = item.title,
|
||||
style = MaterialTheme.typography.bodyLarge
|
||||
)
|
||||
Text(text = item.artist)
|
||||
}
|
||||
|
||||
Text(text = item.duration)
|
||||
}
|
||||
}
|
||||
|
||||
is UiChatMaterial.File -> {}
|
||||
|
||||
is UiChatMaterial.Link -> {}
|
||||
}
|
||||
}
|
||||
+352
@@ -0,0 +1,352 @@
|
||||
package com.meloda.app.fast.chatmaterials.presentation
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.util.Log
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.calculateEndPadding
|
||||
import androidx.compose.foundation.layout.calculateStartPadding
|
||||
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.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.grid.items
|
||||
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
|
||||
import androidx.compose.material.icons.outlined.MoreVert
|
||||
import androidx.compose.material.icons.rounded.Refresh
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.pulltorefresh.PullToRefreshContainer
|
||||
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.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import coil.imageLoader
|
||||
import com.meloda.app.fast.chatmaterials.ChatMaterialsViewModel
|
||||
import com.meloda.app.fast.chatmaterials.ChatMaterialsViewModelImpl
|
||||
import com.meloda.app.fast.designsystem.LocalTheme
|
||||
import com.meloda.app.fast.designsystem.R
|
||||
import dev.chrisbanes.haze.HazeState
|
||||
import dev.chrisbanes.haze.haze
|
||||
import dev.chrisbanes.haze.hazeChild
|
||||
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
|
||||
import dev.chrisbanes.haze.materials.HazeMaterials
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
|
||||
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
||||
@OptIn(
|
||||
ExperimentalMaterial3Api::class,
|
||||
ExperimentalHazeMaterialsApi::class
|
||||
)
|
||||
@Composable
|
||||
fun ChatMaterialsScreen(
|
||||
onBack: () -> Unit,
|
||||
viewModel: ChatMaterialsViewModel = koinViewModel<ChatMaterialsViewModelImpl>()
|
||||
) {
|
||||
val currentTheme = LocalTheme.current
|
||||
val screenState by viewModel.screenState.collectAsStateWithLifecycle()
|
||||
val attachments = screenState.materials
|
||||
|
||||
val imageLoader = LocalContext.current.imageLoader
|
||||
|
||||
var moreClearBlur by rememberSaveable {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
|
||||
val hazeState = remember { HazeState() }
|
||||
val hazeStyle = if (moreClearBlur) HazeMaterials.ultraThin() else HazeMaterials.regular()
|
||||
|
||||
var dropDownMenuExpanded by remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
var checkedTypeIndex by rememberSaveable {
|
||||
mutableIntStateOf(0)
|
||||
}
|
||||
|
||||
LaunchedEffect(checkedTypeIndex) {
|
||||
viewModel.onTypeChanged(
|
||||
when (checkedTypeIndex) {
|
||||
0 -> "photo"
|
||||
1 -> "video"
|
||||
2 -> "audio"
|
||||
3 -> "doc"
|
||||
4 -> "link"
|
||||
else -> ""
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val titles = listOf("Photos", "Videos", "Audios", "Files", "Links")
|
||||
|
||||
val listState = rememberLazyListState()
|
||||
val gridState = rememberLazyGridState()
|
||||
|
||||
val canScrollBackward = when (checkedTypeIndex) {
|
||||
in 0..1 -> gridState.canScrollBackward
|
||||
else -> listState.canScrollBackward
|
||||
}
|
||||
|
||||
Log.d("ChatMaterialsScreen", "ChatMaterialsScreen: canScrollBackward: $canScrollBackward")
|
||||
|
||||
val toolbarColorAlpha by animateFloatAsState(
|
||||
targetValue = if (!canScrollBackward) 1f else 0f,
|
||||
label = "toolbarColorAlpha",
|
||||
animationSpec = tween(durationMillis = 50)
|
||||
)
|
||||
|
||||
val toolbarContainerColor by animateColorAsState(
|
||||
targetValue =
|
||||
if (currentTheme.usingBlur || !canScrollBackward)
|
||||
MaterialTheme.colorScheme.surface
|
||||
else
|
||||
MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp),
|
||||
label = "toolbarColorAlpha",
|
||||
animationSpec = tween(durationMillis = 50)
|
||||
)
|
||||
|
||||
val pullToRefreshAlpha by animateFloatAsState(
|
||||
targetValue = if (!canScrollBackward) 1f else 0f,
|
||||
label = "pullToRefreshAlpha",
|
||||
animationSpec = tween(durationMillis = 50)
|
||||
)
|
||||
|
||||
val pullToRefreshState = rememberPullToRefreshState()
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(text = "Chat Materials")
|
||||
},
|
||||
modifier = Modifier
|
||||
.then(
|
||||
if (currentTheme.usingBlur) {
|
||||
Modifier.hazeChild(
|
||||
state = hazeState,
|
||||
style = hazeStyle
|
||||
)
|
||||
} else Modifier
|
||||
)
|
||||
.fillMaxWidth(),
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = toolbarContainerColor.copy(
|
||||
alpha = if (currentTheme.usingBlur) toolbarColorAlpha else 1f
|
||||
)
|
||||
),
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onBack) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Rounded.ArrowBack,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
IconButton(
|
||||
onClick = {
|
||||
dropDownMenuExpanded = true
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.MoreVert,
|
||||
contentDescription = "Options button"
|
||||
)
|
||||
}
|
||||
|
||||
DropdownMenu(
|
||||
modifier = Modifier.defaultMinSize(minWidth = 140.dp),
|
||||
expanded = dropDownMenuExpanded,
|
||||
onDismissRequest = {
|
||||
dropDownMenuExpanded = false
|
||||
},
|
||||
offset = DpOffset(x = (-4).dp, y = (-60).dp)
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
viewModel.onRefresh()
|
||||
dropDownMenuExpanded = false
|
||||
},
|
||||
text = {
|
||||
Text(text = stringResource(id = R.string.action_refresh))
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Refresh,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
if (currentTheme.usingBlur) {
|
||||
DropdownMenuItem(
|
||||
text = {
|
||||
Text(text = if (moreClearBlur) "Default blur" else "Clearer blur")
|
||||
},
|
||||
onClick = {
|
||||
moreClearBlur = !moreClearBlur
|
||||
dropDownMenuExpanded = false
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
HorizontalDivider()
|
||||
|
||||
titles.forEachIndexed { index, title ->
|
||||
DropdownMenuItem(
|
||||
leadingIcon = {
|
||||
RadioButton(
|
||||
selected = checkedTypeIndex == index,
|
||||
onClick = null
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Text(text = title)
|
||||
},
|
||||
onClick = {
|
||||
checkedTypeIndex = index
|
||||
dropDownMenuExpanded = false
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
) { padding ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(start = padding.calculateStartPadding(LayoutDirection.Ltr))
|
||||
.padding(end = padding.calculateEndPadding(LayoutDirection.Ltr))
|
||||
.nestedScroll(pullToRefreshState.nestedScrollConnection)
|
||||
) {
|
||||
if (checkedTypeIndex in listOf(0, 1)) {
|
||||
LazyVerticalGrid(
|
||||
columns = GridCells.Fixed(3),
|
||||
state = gridState,
|
||||
horizontalArrangement = Arrangement.spacedBy(2.dp),
|
||||
modifier = Modifier
|
||||
.then(
|
||||
if (currentTheme.usingBlur) {
|
||||
Modifier.haze(
|
||||
state = hazeState,
|
||||
style = hazeStyle
|
||||
)
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
)
|
||||
.fillMaxSize()
|
||||
|
||||
) {
|
||||
repeat(3) {
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(padding.calculateTopPadding()))
|
||||
}
|
||||
}
|
||||
items(attachments) { item ->
|
||||
ChatMaterialItem(
|
||||
item = item,
|
||||
imageLoader = imageLoader
|
||||
)
|
||||
}
|
||||
repeat(3) {
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(padding.calculateBottomPadding()))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
modifier = Modifier
|
||||
.then(
|
||||
if (currentTheme.usingBlur) {
|
||||
Modifier.haze(
|
||||
state = hazeState,
|
||||
style = hazeStyle
|
||||
)
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
)
|
||||
.fillMaxSize()
|
||||
|
||||
) {
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(padding.calculateTopPadding()))
|
||||
}
|
||||
items(attachments) { item ->
|
||||
ChatMaterialItem(
|
||||
item = item,
|
||||
imageLoader = imageLoader
|
||||
)
|
||||
}
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(padding.calculateBottomPadding()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pullToRefreshState.isRefreshing) {
|
||||
LaunchedEffect(true) {
|
||||
viewModel.onRefresh()
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(screenState.isLoading) {
|
||||
if (!screenState.isLoading) {
|
||||
pullToRefreshState.endRefresh()
|
||||
}
|
||||
}
|
||||
|
||||
PullToRefreshContainer(
|
||||
state = pullToRefreshState,
|
||||
modifier = Modifier
|
||||
.alpha(pullToRefreshAlpha)
|
||||
.align(Alignment.TopCenter)
|
||||
.padding(top = padding.calculateTopPadding()),
|
||||
contentColor = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package com.meloda.app.fast.chatmaterials.util
|
||||
|
||||
import com.meloda.app.fast.chatmaterials.model.UiChatMaterial
|
||||
import com.meloda.app.fast.model.api.data.AttachmentType
|
||||
import com.meloda.app.fast.model.api.domain.VkAttachmentHistoryMessage
|
||||
import com.meloda.app.fast.model.api.domain.VkAudioDomain
|
||||
import com.meloda.app.fast.model.api.domain.VkFileDomain
|
||||
import com.meloda.app.fast.model.api.domain.VkLinkDomain
|
||||
import com.meloda.app.fast.model.api.domain.VkPhotoDomain
|
||||
import com.meloda.app.fast.model.api.domain.VkVideoDomain
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
fun VkAttachmentHistoryMessage.asPresentation(): UiChatMaterial =
|
||||
when (val type = this.attachment.type) {
|
||||
AttachmentType.PHOTO -> {
|
||||
val attachment = this.attachment as VkPhotoDomain
|
||||
UiChatMaterial.Photo(
|
||||
previewUrl = attachment.getSizeOrSmaller(VkPhotoDomain.SIZE_TYPE_1080_1024)?.url.orEmpty()
|
||||
)
|
||||
}
|
||||
|
||||
AttachmentType.VIDEO -> {
|
||||
val attachment = this.attachment as VkVideoDomain
|
||||
UiChatMaterial.Video(
|
||||
previewUrl = attachment.images.firstOrNull()?.url.orEmpty()
|
||||
)
|
||||
}
|
||||
|
||||
AttachmentType.AUDIO -> {
|
||||
val attachment = this.attachment as VkAudioDomain
|
||||
UiChatMaterial.Audio(
|
||||
previewUrl = null,
|
||||
title = attachment.title,
|
||||
artist = attachment.artist,
|
||||
duration = SimpleDateFormat(
|
||||
"mm:ss",
|
||||
Locale.getDefault()
|
||||
).format(attachment.duration)
|
||||
)
|
||||
}
|
||||
|
||||
AttachmentType.FILE -> {
|
||||
val attachment = this.attachment as VkFileDomain
|
||||
UiChatMaterial.File(
|
||||
title = attachment.title
|
||||
)
|
||||
}
|
||||
|
||||
AttachmentType.LINK -> {
|
||||
val attachment = this.attachment as VkLinkDomain
|
||||
UiChatMaterial.Link(
|
||||
title = attachment.title ?: attachment.url,
|
||||
previewUrl = attachment.photo?.getMaxSize()?.url
|
||||
)
|
||||
}
|
||||
|
||||
else -> throw IllegalArgumentException("Unsupported type: $type")
|
||||
}
|
||||
+7
@@ -27,6 +27,7 @@ import com.meloda.app.fast.common.UserConfig
|
||||
import com.meloda.app.fast.conversations.model.ConversationOption
|
||||
import com.meloda.app.fast.conversations.model.ConversationsScreenState
|
||||
import com.meloda.app.fast.conversations.model.UiConversation
|
||||
import com.meloda.app.fast.designsystem.LocalBottomPadding
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -45,6 +46,8 @@ fun ConversationsListComposable(
|
||||
|
||||
val conversations = screenState.conversations
|
||||
|
||||
val bottomPadding = LocalBottomPadding.current
|
||||
|
||||
LazyColumn(
|
||||
modifier = modifier,
|
||||
state = state
|
||||
@@ -105,5 +108,9 @@ fun ConversationsListComposable(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(bottomPadding))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+38
-26
@@ -5,16 +5,20 @@ import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.slideIn
|
||||
import androidx.compose.animation.slideOut
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.calculateEndPadding
|
||||
import androidx.compose.foundation.layout.calculateStartPadding
|
||||
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.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.statusBars
|
||||
@@ -61,6 +65,7 @@ import androidx.compose.ui.unit.DpOffset
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.zIndex
|
||||
import androidx.core.view.HapticFeedbackConstantsCompat
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import coil.imageLoader
|
||||
@@ -70,6 +75,8 @@ import com.meloda.app.fast.conversations.ConversationsViewModel
|
||||
import com.meloda.app.fast.conversations.ConversationsViewModelImpl
|
||||
import com.meloda.app.fast.conversations.model.ConversationsScreenState
|
||||
import com.meloda.app.fast.conversations.model.UiConversation
|
||||
import com.meloda.app.fast.designsystem.LocalBottomPadding
|
||||
import com.meloda.app.fast.designsystem.LocalHazeState
|
||||
import com.meloda.app.fast.designsystem.LocalTheme
|
||||
import com.meloda.app.fast.designsystem.MaterialDialog
|
||||
import com.meloda.app.fast.designsystem.components.FullScreenLoader
|
||||
@@ -142,7 +149,8 @@ fun ConversationsScreen(
|
||||
}
|
||||
}
|
||||
|
||||
val hazeState = remember { HazeState() }
|
||||
// val hazeState = remember { HazeState() }
|
||||
val hazeState = LocalHazeState.current
|
||||
|
||||
var dropDownMenuExpanded by remember {
|
||||
mutableStateOf(false)
|
||||
@@ -252,38 +260,42 @@ fun ConversationsScreen(
|
||||
val scope = rememberCoroutineScope()
|
||||
val rotation = remember { Animatable(0f) }
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = isListScrollingUp,
|
||||
modifier = Modifier.navigationBarsPadding(),
|
||||
enter = slideIn { IntOffset(0, 400) },
|
||||
exit = slideOut { IntOffset(0, 400) }
|
||||
) {
|
||||
FloatingActionButton(
|
||||
onClick = {
|
||||
view.performHapticFeedback(HapticFeedbackConstantsCompat.REJECT)
|
||||
Column {
|
||||
AnimatedVisibility(
|
||||
visible = isListScrollingUp,
|
||||
modifier = Modifier.navigationBarsPadding(),
|
||||
enter = slideIn { IntOffset(0, 600) } + fadeIn(tween(200)),
|
||||
exit = slideOut { IntOffset(0, 600) } + fadeOut(tween(200))
|
||||
) {
|
||||
FloatingActionButton(
|
||||
onClick = {
|
||||
view.performHapticFeedback(HapticFeedbackConstantsCompat.REJECT)
|
||||
|
||||
scope.launch {
|
||||
for (i in 20 downTo 0 step 4) {
|
||||
rotation.animateTo(
|
||||
targetValue = i.toFloat(),
|
||||
animationSpec = tween(50)
|
||||
)
|
||||
if (i > 0) {
|
||||
scope.launch {
|
||||
for (i in 20 downTo 0 step 4) {
|
||||
rotation.animateTo(
|
||||
targetValue = -i.toFloat(),
|
||||
targetValue = i.toFloat(),
|
||||
animationSpec = tween(50)
|
||||
)
|
||||
if (i > 0) {
|
||||
rotation.animateTo(
|
||||
targetValue = -i.toFloat(),
|
||||
animationSpec = tween(50)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier.rotate(rotation.value)
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = UiR.drawable.ic_baseline_create_24),
|
||||
contentDescription = "Add chat button"
|
||||
)
|
||||
},
|
||||
modifier = Modifier.rotate(rotation.value)
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = UiR.drawable.ic_baseline_create_24),
|
||||
contentDescription = "Add chat button"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(LocalBottomPadding.current))
|
||||
}
|
||||
}
|
||||
) { padding ->
|
||||
|
||||
@@ -77,7 +77,7 @@ class FriendsViewModelImpl(
|
||||
}
|
||||
|
||||
private fun loadFriends(offset: Int = currentOffset.value) {
|
||||
friendsUseCase.getAllFriends(count = 30, offset = offset).listenValue { state ->
|
||||
friendsUseCase.getAllFriends(count = LOAD_COUNT, offset = offset).listenValue { state ->
|
||||
state.processState(
|
||||
error = { error ->
|
||||
when (error) {
|
||||
@@ -103,11 +103,11 @@ class FriendsViewModelImpl(
|
||||
},
|
||||
success = { info ->
|
||||
val response = info.friends
|
||||
val itemsCountSufficient = response.size == 30
|
||||
val itemsCountSufficient = response.size == LOAD_COUNT
|
||||
canPaginate.setValue { itemsCountSufficient }
|
||||
|
||||
val paginationExhausted = !itemsCountSufficient &&
|
||||
screenState.value.friends.size >= 30
|
||||
screenState.value.friends.size >= LOAD_COUNT
|
||||
|
||||
imagesToPreload.setValue {
|
||||
response.mapNotNull(VkUser::photo100)
|
||||
@@ -172,4 +172,8 @@ class FriendsViewModelImpl(
|
||||
}
|
||||
uiOnlineFriends.setValue { onlineUiFriends }
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val LOAD_COUNT = 30
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -354,6 +354,7 @@ class MessagesHistoryViewModelImpl(
|
||||
|
||||
val newMessage = VkMessage(
|
||||
id = -1 - sendingMessages.size,
|
||||
conversationMessageId = -1,
|
||||
text = lastMessageText,
|
||||
isOut = true,
|
||||
peerId = screenState.value.conversationId,
|
||||
|
||||
+40
-59
@@ -1,27 +1,28 @@
|
||||
package com.meloda.app.fast.messageshistory.domain
|
||||
|
||||
import com.meloda.app.fast.data.State
|
||||
import com.meloda.app.fast.data.api.messages.MessagesHistoryDomain
|
||||
import com.meloda.app.fast.data.api.messages.MessagesHistoryInfo
|
||||
import com.meloda.app.fast.data.api.messages.MessagesRepository
|
||||
import com.meloda.app.fast.data.api.messages.MessagesUseCase
|
||||
import com.meloda.app.fast.data.mapToState
|
||||
import com.meloda.app.fast.model.api.domain.VkAttachment
|
||||
import com.meloda.app.fast.model.api.domain.VkAttachmentHistoryMessage
|
||||
import com.meloda.app.fast.model.api.domain.VkMessage
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
|
||||
class MessagesUseCaseImpl(
|
||||
private val messagesRepository: MessagesRepository
|
||||
private val repository: MessagesRepository
|
||||
) : MessagesUseCase {
|
||||
|
||||
override fun getMessagesHistory(
|
||||
conversationId: Int,
|
||||
count: Int?,
|
||||
offset: Int?
|
||||
): Flow<State<MessagesHistoryDomain>> = flow {
|
||||
): Flow<State<MessagesHistoryInfo>> = flow {
|
||||
emit(State.Loading)
|
||||
|
||||
val newState = messagesRepository.getMessagesHistory(
|
||||
val newState = repository.getHistory(
|
||||
conversationId = conversationId,
|
||||
offset = offset,
|
||||
count = count
|
||||
@@ -31,60 +32,20 @@ class MessagesUseCaseImpl(
|
||||
}
|
||||
|
||||
override fun getById(
|
||||
messageId: Int,
|
||||
extended: Boolean?,
|
||||
fields: String?
|
||||
): Flow<State<VkMessage?>> = flow {
|
||||
emit(State.Loading)
|
||||
|
||||
val newState = messagesRepository.getMessageById(
|
||||
messagesIds = listOf(messageId),
|
||||
extended = extended,
|
||||
fields = fields
|
||||
).mapToState()
|
||||
emit(newState)
|
||||
}
|
||||
|
||||
override fun getByIds(
|
||||
messageIds: List<Int>,
|
||||
extended: Boolean?,
|
||||
fields: String?
|
||||
): Flow<State<List<VkMessage>>> = flow {}
|
||||
// flow {
|
||||
// emit(State.Loading)
|
||||
//
|
||||
// val newState = messagesRepository.getById(
|
||||
// params = MessagesGetByIdRequest(
|
||||
// messagesIds = messageIds,
|
||||
// extended = extended,
|
||||
// fields = fields
|
||||
// )
|
||||
// ).fold(
|
||||
// onSuccess = { response ->
|
||||
// val messages = response.items
|
||||
// val usersMap =
|
||||
// VkUsersMap.forUsers(response.profiles.orEmpty().map(VkUserData::mapToDomain))
|
||||
// val groupsMap =
|
||||
// VkGroupsMap.forGroups(response.groups.orEmpty().map(VkGroupData::mapToDomain))
|
||||
//
|
||||
// com.meloda.app.fast.network.State.Success(
|
||||
// messages.map { message ->
|
||||
// message.mapToDomain(
|
||||
// user = usersMap.messageUser(message),
|
||||
// group = groupsMap.messageGroup(message),
|
||||
// actionUser = usersMap.messageActionUser(message),
|
||||
// actionGroup = groupsMap.messageActionGroup(message)
|
||||
// )
|
||||
// }
|
||||
// )
|
||||
// },
|
||||
// onNetworkFailure = { com.meloda.app.fast.network.State.Error.ConnectionError },
|
||||
// onUnknownFailure = { com.meloda.app.fast.network.State.UNKNOWN_ERROR },
|
||||
// onHttpFailure = { result -> result.error.toStateApiError() },
|
||||
// onApiFailure = { result -> result.error.toStateApiError() }
|
||||
// )
|
||||
// emit(newState)
|
||||
// }
|
||||
): Flow<State<List<VkMessage>>> = flow {
|
||||
emit(State.Loading)
|
||||
|
||||
val newState = repository.getById(
|
||||
messagesIds = messageIds,
|
||||
extended = extended,
|
||||
fields = fields
|
||||
).mapToState()
|
||||
|
||||
emit(newState)
|
||||
}
|
||||
|
||||
override fun sendMessage(
|
||||
peerId: Int,
|
||||
@@ -95,7 +56,7 @@ class MessagesUseCaseImpl(
|
||||
): Flow<State<Int>> = flow {
|
||||
emit(State.Loading)
|
||||
|
||||
val newState = messagesRepository.send(
|
||||
val newState = repository.send(
|
||||
peerId = peerId,
|
||||
randomId = randomId,
|
||||
message = message,
|
||||
@@ -112,7 +73,7 @@ class MessagesUseCaseImpl(
|
||||
): Flow<State<Int>> = flow {
|
||||
emit(State.Loading)
|
||||
|
||||
val newState = messagesRepository.markAsRead(
|
||||
val newState = repository.markAsRead(
|
||||
peerId = peerId,
|
||||
startMessageId = startMessageId
|
||||
).mapToState()
|
||||
@@ -120,11 +81,31 @@ class MessagesUseCaseImpl(
|
||||
emit(newState)
|
||||
}
|
||||
|
||||
override fun getHistoryAttachments(
|
||||
peerId: Int,
|
||||
count: Int?,
|
||||
offset: Int?,
|
||||
attachmentTypes: List<String>,
|
||||
conversationMessageId: Int
|
||||
): Flow<State<List<VkAttachmentHistoryMessage>>> = flow {
|
||||
emit(State.Loading)
|
||||
|
||||
val newState = repository.getHistoryAttachments(
|
||||
peerId = peerId,
|
||||
count = count,
|
||||
offset = offset,
|
||||
attachmentTypes = attachmentTypes,
|
||||
conversationMessageId = conversationMessageId
|
||||
).mapToState()
|
||||
|
||||
emit(newState)
|
||||
}
|
||||
|
||||
override suspend fun storeMessage(message: VkMessage) {
|
||||
messagesRepository.storeMessages(listOf(message))
|
||||
repository.storeMessages(listOf(message))
|
||||
}
|
||||
|
||||
override suspend fun storeMessages(messages: List<VkMessage>) {
|
||||
messagesRepository.storeMessages(messages)
|
||||
repository.storeMessages(messages)
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -4,6 +4,7 @@ import com.meloda.app.fast.common.UiImage
|
||||
|
||||
data class UiMessage(
|
||||
val id: Int,
|
||||
val conversationMessageId: Int,
|
||||
val text: String?,
|
||||
val isOut: Boolean,
|
||||
val fromId: Int,
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@ val MessagesHistoryNavType = object : NavType<MessagesHistoryArguments>(isNullab
|
||||
fun NavGraphBuilder.messagesHistoryRoute(
|
||||
onError: (BaseError) -> Unit,
|
||||
onBack: () -> Unit,
|
||||
onNavigateToChatAttachments: () -> Unit
|
||||
onNavigateToChatAttachments: (peerId: Int, conversationMessageId: Int) -> Unit
|
||||
) {
|
||||
composable<MessagesHistory>(
|
||||
typeMap = mapOf(typeOf<MessagesHistoryArguments>() to MessagesHistoryNavType)
|
||||
|
||||
+7
-2
@@ -90,7 +90,7 @@ import com.meloda.app.fast.designsystem.R as UiR
|
||||
fun MessagesHistoryScreen(
|
||||
onError: (BaseError) -> Unit,
|
||||
onBack: () -> Unit,
|
||||
onNavigateToChatMaterials: () -> Unit,
|
||||
onNavigateToChatMaterials: (peerId: Int, conversationMessageId: Int) -> Unit,
|
||||
viewModel: MessagesHistoryViewModel = koinViewModel<MessagesHistoryViewModelImpl>()
|
||||
) {
|
||||
val view = LocalView.current
|
||||
@@ -215,7 +215,12 @@ fun MessagesHistoryScreen(
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
dropDownMenuExpanded = false
|
||||
onNavigateToChatMaterials()
|
||||
|
||||
// TODO: 11/07/2024, Danil Nikolaev: to VM
|
||||
onNavigateToChatMaterials(
|
||||
screenState.conversationId,
|
||||
screenState.messages.first().conversationMessageId
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Text(text = "Materials")
|
||||
|
||||
+1
@@ -91,6 +91,7 @@ fun VkMessage.asPresentation(
|
||||
nextMessage: VkMessage?
|
||||
): UiMessage = UiMessage(
|
||||
id = id,
|
||||
conversationMessageId = conversationMessageId,
|
||||
text = text,
|
||||
isOut = isOut,
|
||||
fromId = fromId,
|
||||
|
||||
Reference in New Issue
Block a user