feat(messages): Implement "message read by" counter

This commit introduces the ability to see how many people have read an outgoing message in a group chat. A "views" count is now displayed in the message options dialog for relevant messages.

- **API & Data Layer:**
  - Added `getMessageReadPeers` to `MessagesService` and `MessagesRepository` to fetch users who have read a specific message.
  - Introduced `MessagesGetReadPeersResponse` to handle the API response.
  - A new URL constant `GET_MESSAGE_READ_PEERS` was added.

- **Domain Layer:**
  - A new `GetMessageReadPeersUseCase` is created to provide the view count to the ViewModel.
  - The use case is registered in the `DomainModule`.

- **ViewModel:**
  - `MessagesHistoryViewModel` now includes `loadMessageReadPeers` to asynchronously fetch and return the view count for a message.

- **UI (Compose):**
  - The `MessageOptionsDialog` now displays a "views" count for outgoing chat messages.
  - It uses a `LaunchedEffect` to call `loadMessageReadPeers` when the dialog is shown.
  - The `visibility` icon has been updated and its XML file renamed to `round_visibility_24px.xml` to follow a consistent naming convention.

- **Refactoring & Minor Fixes:**
  - Simplified several `derivedStateOf` usages to direct calculations or property delegates in `MessageBubble.kt` and `MessagesList.kt` for minor performance improvements.
  - Renamed `IncomingMessageBubble.kt` and `OutgoingMessageBubble.kt` to `MessageBubbleIncoming.kt` and `MessageBubbleOutgoing.kt` respectively for consistency.
  - Removed an unnecessary log statement in `MessagesList.kt`.
This commit is contained in:
2025-12-06 14:37:08 +03:00
parent 5310596cf6
commit 7b2c102470
19 changed files with 216 additions and 98 deletions
@@ -6,6 +6,7 @@ import dev.meloda.fast.model.api.domain.VkAttachment
import dev.meloda.fast.model.api.domain.VkAttachmentHistoryMessage import dev.meloda.fast.model.api.domain.VkAttachmentHistoryMessage
import dev.meloda.fast.model.api.domain.VkMessage import dev.meloda.fast.model.api.domain.VkMessage
import dev.meloda.fast.model.api.responses.MessagesGetConversationMembersResponse import dev.meloda.fast.model.api.responses.MessagesGetConversationMembersResponse
import dev.meloda.fast.model.api.responses.MessagesGetReadPeersResponse
import dev.meloda.fast.model.api.responses.MessagesSendResponse import dev.meloda.fast.model.api.responses.MessagesSendResponse
import dev.meloda.fast.network.RestApiErrorDomain import dev.meloda.fast.network.RestApiErrorDomain
@@ -110,4 +111,9 @@ interface MessagesRepository {
chatId: Long, chatId: Long,
memberId: Long memberId: Long
): ApiResult<Int, RestApiErrorDomain> ): ApiResult<Int, RestApiErrorDomain>
suspend fun getMessageReadPeers(
peerId: Long,
cmId: Long
): ApiResult<MessagesGetReadPeersResponse, RestApiErrorDomain>
} }
@@ -37,6 +37,7 @@ import dev.meloda.fast.model.api.requests.MessagesRemoveChatUserRequest
import dev.meloda.fast.model.api.requests.MessagesSendRequest import dev.meloda.fast.model.api.requests.MessagesSendRequest
import dev.meloda.fast.model.api.requests.MessagesUnpinMessageRequest import dev.meloda.fast.model.api.requests.MessagesUnpinMessageRequest
import dev.meloda.fast.model.api.responses.MessagesGetConversationMembersResponse import dev.meloda.fast.model.api.responses.MessagesGetConversationMembersResponse
import dev.meloda.fast.model.api.responses.MessagesGetReadPeersResponse
import dev.meloda.fast.model.api.responses.MessagesSendResponse import dev.meloda.fast.model.api.responses.MessagesSendResponse
import dev.meloda.fast.network.RestApiErrorDomain import dev.meloda.fast.network.RestApiErrorDomain
import dev.meloda.fast.network.mapApiDefault import dev.meloda.fast.network.mapApiDefault
@@ -419,4 +420,18 @@ class MessagesRepositoryImpl(
messagesService.removeChatUser(requestModel.map).mapApiDefault() messagesService.removeChatUser(requestModel.map).mapApiDefault()
} }
override suspend fun getMessageReadPeers(
peerId: Long,
cmId: Long
): ApiResult<MessagesGetReadPeersResponse, RestApiErrorDomain> = withContext(Dispatchers.IO) {
messagesService.getMessageReadPeers(
mapOf(
"peer_id" to peerId.toString(),
"cmid" to cmId.toString(),
"extended" to "1",
"fields" to VkConstants.USER_FIELDS
)
).mapApiDefault()
}
} }
@@ -0,0 +1,21 @@
package dev.meloda.fast.domain
import dev.meloda.fast.data.State
import dev.meloda.fast.data.api.messages.MessagesRepository
import dev.meloda.fast.data.mapToState
import kotlinx.coroutines.flow.Flow
class GetMessageReadPeersUseCase(
private val repository: MessagesRepository
) : BaseUseCase {
operator fun invoke(
peerId: Long,
cmId: Long
): Flow<State<Int>> = flowNewState {
repository.getMessageReadPeers(
peerId = peerId,
cmId = cmId
).mapToState(successMapper = { it.totalCount })
}
}
@@ -6,6 +6,7 @@ import dev.meloda.fast.domain.AccountUseCaseImpl
import dev.meloda.fast.domain.GetCurrentAccountUseCase import dev.meloda.fast.domain.GetCurrentAccountUseCase
import dev.meloda.fast.domain.GetLocalUserByIdUseCase import dev.meloda.fast.domain.GetLocalUserByIdUseCase
import dev.meloda.fast.domain.GetLocalUsersByIdsUseCase import dev.meloda.fast.domain.GetLocalUsersByIdsUseCase
import dev.meloda.fast.domain.GetMessageReadPeersUseCase
import dev.meloda.fast.domain.LoadConversationsByIdUseCase import dev.meloda.fast.domain.LoadConversationsByIdUseCase
import dev.meloda.fast.domain.LoadUserByIdUseCase import dev.meloda.fast.domain.LoadUserByIdUseCase
import dev.meloda.fast.domain.LoadUsersByIdsUseCase import dev.meloda.fast.domain.LoadUsersByIdsUseCase
@@ -27,4 +28,6 @@ val domainModule = module {
singleOf(::GetCurrentAccountUseCase) singleOf(::GetCurrentAccountUseCase)
singleOf(::LoadConversationsByIdUseCase) singleOf(::LoadConversationsByIdUseCase)
singleOf(::GetMessageReadPeersUseCase)
} }
@@ -70,3 +70,10 @@ data class MessagesMarkAsImportantResponse(
@Json(name = "peer_id") val peerId: Long @Json(name = "peer_id") val peerId: Long
) )
} }
@JsonClass(generateAdapter = true)
data class MessagesGetReadPeersResponse(
@Json(name = "items") val items: List<Long>,
@Json(name = "total_count") val totalCount: Int,
@Json(name = "profiles") val profiles: List<VkUserData>?,
)
@@ -9,6 +9,7 @@ import dev.meloda.fast.model.api.responses.MessagesGetByIdResponse
import dev.meloda.fast.model.api.responses.MessagesGetConversationMembersResponse import dev.meloda.fast.model.api.responses.MessagesGetConversationMembersResponse
import dev.meloda.fast.model.api.responses.MessagesGetHistoryAttachmentsResponse import dev.meloda.fast.model.api.responses.MessagesGetHistoryAttachmentsResponse
import dev.meloda.fast.model.api.responses.MessagesGetHistoryResponse import dev.meloda.fast.model.api.responses.MessagesGetHistoryResponse
import dev.meloda.fast.model.api.responses.MessagesGetReadPeersResponse
import dev.meloda.fast.model.api.responses.MessagesMarkAsImportantResponse import dev.meloda.fast.model.api.responses.MessagesMarkAsImportantResponse
import dev.meloda.fast.model.api.responses.MessagesSendResponse import dev.meloda.fast.model.api.responses.MessagesSendResponse
import dev.meloda.fast.network.ApiResponse import dev.meloda.fast.network.ApiResponse
@@ -108,4 +109,10 @@ interface MessagesService {
suspend fun removeChatUser( suspend fun removeChatUser(
@FieldMap params: Map<String, String> @FieldMap params: Map<String, String>
): ApiResult<ApiResponse<Int>, RestApiError> ): ApiResult<ApiResponse<Int>, RestApiError>
@FormUrlEncoded
@POST(MessagesUrls.GET_MESSAGE_READ_PEERS)
suspend fun getMessageReadPeers(
@FieldMap params: Map<String, String>
): ApiResult<ApiResponse<MessagesGetReadPeersResponse>, RestApiError>
} }
@@ -22,4 +22,5 @@ object MessagesUrls {
const val REMOVE_CHAT_USER = "$URL/messages.removeChatUser" const val REMOVE_CHAT_USER = "$URL/messages.removeChatUser"
const val GET_HISTORY_ATTACHMENTS = "$URL/messages.getHistoryAttachments" const val GET_HISTORY_ATTACHMENTS = "$URL/messages.getHistoryAttachments"
const val CREATE_CHAT = "$URL/messages.createChat" const val CREATE_CHAT = "$URL/messages.createChat"
const val GET_MESSAGE_READ_PEERS = "$URL/messages.getMessageReadPeers"
} }
@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M12,4C7,4 2.73,7.11 1,11.5 2.73,15.89 7,19 12,19s9.27,-3.11 11,-7.5C21.27,7.11 17,4 12,4zM12,16.5c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,8.5c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z" />
</vector>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#ffffff"
android:pathData="M480,640Q555,640 607.5,587.5Q660,535 660,460Q660,385 607.5,332.5Q555,280 480,280Q405,280 352.5,332.5Q300,385 300,460Q300,535 352.5,587.5Q405,640 480,640ZM480,568Q435,568 403.5,536.5Q372,505 372,460Q372,415 403.5,383.5Q435,352 480,352Q525,352 556.5,383.5Q588,415 588,460Q588,505 556.5,536.5Q525,568 480,568ZM480,760Q334,760 214,678.5Q94,597 40,460Q94,323 214,241.5Q334,160 480,160Q626,160 746,241.5Q866,323 920,460Q866,597 746,678.5Q626,760 480,760ZM480,460Q480,460 480,460Q480,460 480,460Q480,460 480,460Q480,460 480,460Q480,460 480,460Q480,460 480,460Q480,460 480,460Q480,460 480,460ZM480,680Q593,680 687.5,620.5Q782,561 832,460Q782,359 687.5,299.5Q593,240 480,240Q367,240 272.5,299.5Q178,359 128,460Q178,561 272.5,620.5Q367,680 480,680Z" />
</vector>
@@ -296,7 +296,7 @@ fun LoginScreen(
trailingIcon = { trailingIcon = {
val imagePainter = painterResource( val imagePainter = painterResource(
id = if (screenState.passwordVisible) R.drawable.round_visibility_off_24 id = if (screenState.passwordVisible) R.drawable.round_visibility_off_24
else R.drawable.round_visibility_24 else R.drawable.round_visibility_24px
) )
IconButton(onClick = onPasswordVisibilityButtonClicked) { IconButton(onClick = onPasswordVisibilityButtonClicked) {
@@ -65,4 +65,6 @@ interface MessagesHistoryViewModel {
fun onReplyCloseClicked() fun onReplyCloseClicked()
fun onRequestReplyToMessage(cmId: Long) fun onRequestReplyToMessage(cmId: Long)
suspend fun loadMessageReadPeers(peerId: Long, cmId: Long): Int
} }
@@ -38,6 +38,7 @@ import dev.meloda.fast.data.processState
import dev.meloda.fast.datastore.AppSettings import dev.meloda.fast.datastore.AppSettings
import dev.meloda.fast.datastore.UserSettings import dev.meloda.fast.datastore.UserSettings
import dev.meloda.fast.domain.ConversationsUseCase import dev.meloda.fast.domain.ConversationsUseCase
import dev.meloda.fast.domain.GetMessageReadPeersUseCase
import dev.meloda.fast.domain.LoadConversationsByIdUseCase import dev.meloda.fast.domain.LoadConversationsByIdUseCase
import dev.meloda.fast.domain.LongPollUpdatesParser import dev.meloda.fast.domain.LongPollUpdatesParser
import dev.meloda.fast.domain.MessagesUseCase import dev.meloda.fast.domain.MessagesUseCase
@@ -70,6 +71,8 @@ import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put import kotlinx.serialization.json.put
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import kotlin.math.abs import kotlin.math.abs
import kotlin.random.Random import kotlin.random.Random
@@ -80,6 +83,7 @@ class MessagesHistoryViewModelImpl(
private val resourceProvider: ResourceProvider, private val resourceProvider: ResourceProvider,
private val userSettings: UserSettings, private val userSettings: UserSettings,
private val loadConversationsByIdUseCase: LoadConversationsByIdUseCase, private val loadConversationsByIdUseCase: LoadConversationsByIdUseCase,
private val getMessageReadPeersUseCase: GetMessageReadPeersUseCase,
updatesParser: LongPollUpdatesParser, updatesParser: LongPollUpdatesParser,
savedStateHandle: SavedStateHandle savedStateHandle: SavedStateHandle
) : MessagesHistoryViewModel, ViewModel() { ) : MessagesHistoryViewModel, ViewModel() {
@@ -575,6 +579,23 @@ class MessagesHistoryViewModelImpl(
replyToMessage(cmId) replyToMessage(cmId)
} }
override suspend fun loadMessageReadPeers(peerId: Long, cmId: Long): Int = suspendCoroutine {
viewModelScope.launch {
getMessageReadPeersUseCase
.invoke(peerId = peerId, cmId = cmId)
.listenValue(viewModelScope) { state ->
state.processState(
error = { error ->
it.resume(-1)
},
success = { count ->
it.resume(count)
}
)
}
}
}
private fun handleNewMessage(event: LongPollParsedEvent.NewMessage) { private fun handleNewMessage(event: LongPollParsedEvent.NewMessage) {
val message = event.message val message = event.message
@@ -83,9 +83,7 @@ fun MessageBubble(
MaterialTheme.colorScheme.onPrimaryContainer MaterialTheme.colorScheme.onPrimaryContainer
} }
val shouldShowBubble by remember(text) { val shouldShowBubble = !text.isNullOrEmpty()
derivedStateOf { text != null }
}
var bubbleContainerWidth by remember { var bubbleContainerWidth by remember {
mutableIntStateOf(0) mutableIntStateOf(0)
@@ -95,25 +93,21 @@ fun MessageBubble(
mutableIntStateOf(0) mutableIntStateOf(0)
} }
val shouldFill by remember(bubbleContainerWidth, attachmentsContainerWidth) { val shouldFill by derivedStateOf {
derivedStateOf { attachmentsContainerWidth >= bubbleContainerWidth
attachmentsContainerWidth >= bubbleContainerWidth
}
} }
var containerWidth by remember { var containerWidth by remember {
mutableIntStateOf(0) mutableIntStateOf(0)
} }
val minDateContainerWidth by remember(isEdited, isOut, isPinned, isImportant) { val minDateContainerWidth = remember(isEdited, isOut, isPinned, isImportant) {
derivedStateOf { val mainPart = if (isEdited) 50 else 30
val mainPart = if (isEdited) 50 else 30 val readIndicatorPart = if (isOut) 14 else 0
val readIndicatorPart = if (isOut) 14 else 0 val pinnedIndicatorPart = if (isPinned) 14 else 0
val pinnedIndicatorPart = if (isPinned) 14 else 0 val importantIndicatorPart = if (isImportant) 14 else 0
val importantIndicatorPart = if (isImportant) 14 else 0
(mainPart + readIndicatorPart + pinnedIndicatorPart + importantIndicatorPart).dp (mainPart + readIndicatorPart + pinnedIndicatorPart + importantIndicatorPart).dp
}
} }
val dateContainerWidth by animateDpAsState( val dateContainerWidth by animateDpAsState(
@@ -14,11 +14,13 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.minimumInteractiveComponentSize import androidx.compose.material3.minimumInteractiveComponentSize
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
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.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
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.unit.dp import androidx.compose.ui.unit.dp
@@ -38,6 +40,7 @@ import java.util.concurrent.TimeUnit
fun HandleDialogs( fun HandleDialogs(
screenState: MessagesHistoryScreenState, screenState: MessagesHistoryScreenState,
dialog: MessageDialog?, dialog: MessageDialog?,
messageReadPeersLoader: suspend (peerId: Long, cmId: Long) -> Int,
onConfirmed: (MessageDialog, Bundle) -> Unit = { _, _ -> }, onConfirmed: (MessageDialog, Bundle) -> Unit = { _, _ -> },
onDismissed: (MessageDialog) -> Unit = {}, onDismissed: (MessageDialog) -> Unit = {},
onItemPicked: (MessageDialog, Bundle) -> Unit = { _, _ -> } onItemPicked: (MessageDialog, Bundle) -> Unit = { _, _ -> }
@@ -49,6 +52,7 @@ fun HandleDialogs(
MessageOptionsDialog( MessageOptionsDialog(
screenState = screenState, screenState = screenState,
message = dialog.message, message = dialog.message,
messageReadPeersLoader = messageReadPeersLoader,
onDismissed = { onDismissed(dialog) }, onDismissed = { onDismissed(dialog) },
onItemPicked = { bundle -> onItemPicked(dialog, bundle) } onItemPicked = { bundle -> onItemPicked(dialog, bundle) }
) )
@@ -102,9 +106,15 @@ fun HandleDialogs(
fun MessageOptionsDialog( fun MessageOptionsDialog(
screenState: MessagesHistoryScreenState, screenState: MessagesHistoryScreenState,
message: VkMessage, message: VkMessage,
messageReadPeersLoader: suspend (peerId: Long, cmId: Long) -> Int,
onDismissed: () -> Unit = {}, onDismissed: () -> Unit = {},
onItemPicked: (Bundle) -> Unit onItemPicked: (Bundle) -> Unit
) { ) {
val primaryColor = MaterialTheme.colorScheme.primary
val errorColor = MaterialTheme.colorScheme.error
val showReadPeers = message.isOut && message.isPeerChat()
val options = mutableListOf<MessageOption>() val options = mutableListOf<MessageOption>()
if (message.isFailed()) { if (message.isFailed()) {
options += MessageOption.Retry options += MessageOption.Retry
@@ -142,41 +152,47 @@ fun MessageOptionsDialog(
options += MessageOption.Delete options += MessageOption.Delete
val messageOptions = options.map { option -> val messageOptions = remember(options) {
Triple( options.map { option ->
stringResource(option.titleResId), Triple(
painterResource(option.iconResId), option.titleResId,
when { option.iconResId,
option in listOf( when {
MessageOption.Delete, option in listOf(
MessageOption.MarkAsSpam MessageOption.Delete,
) -> MaterialTheme.colorScheme.error MessageOption.MarkAsSpam
) -> errorColor
else -> MaterialTheme.colorScheme.primary else -> primaryColor
} }
) )
}
} }
MaterialDialog(onDismissRequest = onDismissed) { MaterialDialog(onDismissRequest = onDismissed) {
if (showReadPeers) {
var viewCount by remember {
mutableStateOf<Int?>(null)
}
LaunchedEffect(Unit) {
viewCount = messageReadPeersLoader.invoke(message.peerId, message.cmId)
}
MessageOptionItem(
title = viewCount?.let { "$it views" } ?: "...",
iconResId = R.drawable.round_visibility_24px,
tintColor = primaryColor,
onClick = {}
)
}
messageOptions messageOptions
.forEachIndexed { index, (title, painter, tintColor) -> .forEachIndexed { index, (titleResId, iconResId, tintColor) ->
DropdownMenuItem( MessageOptionItem(
text = { title = stringResource(titleResId),
Row { iconResId = iconResId,
Text(text = title) tintColor = tintColor,
Spacer(modifier = Modifier.width(8.dp))
}
},
leadingIcon = {
Row {
Spacer(modifier = Modifier.width(8.dp))
Icon(
painter = painter,
contentDescription = null,
tint = tintColor
)
}
},
onClick = { onClick = {
onDismissed() onDismissed()
val pickedOption = options[index] val pickedOption = options[index]
@@ -193,6 +209,34 @@ fun MessageOptionsDialog(
} }
} }
@Composable
private fun MessageOptionItem(
title: String,
iconResId: Int,
tintColor: Color,
onClick: () -> Unit
) {
DropdownMenuItem(
text = {
Row {
Text(text = title)
Spacer(modifier = Modifier.width(8.dp))
}
},
leadingIcon = {
Row {
Spacer(modifier = Modifier.width(8.dp))
Icon(
painter = painterResource(iconResId),
contentDescription = null,
tint = tintColor
)
}
},
onClick = onClick
)
}
@Composable @Composable
fun MessageDeleteDialog( fun MessageDeleteDialog(
messages: List<VkMessage>, messages: List<VkMessage>,
@@ -85,6 +85,7 @@ fun MessagesHistoryRoute(
HandleDialogs( HandleDialogs(
screenState = screenState, screenState = screenState,
dialog = dialog, dialog = dialog,
messageReadPeersLoader = viewModel::loadMessageReadPeers,
onConfirmed = viewModel::onDialogConfirmed, onConfirmed = viewModel::onDialogConfirmed,
onDismissed = viewModel::onDialogDismissed, onDismissed = viewModel::onDialogDismissed,
onItemPicked = viewModel::onDialogItemPicked onItemPicked = viewModel::onDialogItemPicked
@@ -78,22 +78,21 @@ fun MessagesList(
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val onAttachmentClick by rememberUpdatedState( val onAttachmentClick by rememberUpdatedState { message: UiItem.Message, attachment: VkAttachment ->
{ message: UiItem.Message, attachment: VkAttachment -> if (isSelectedAtLeastOne) {
if (isSelectedAtLeastOne) { onMessageClicked(message.id)
onMessageClicked(message.id) } else {
} else { when (attachment) {
when (attachment) { is VkPhotoDomain -> {
is VkPhotoDomain -> { val photos = message.attachments
val photos = message.attachments .orEmpty()
.orEmpty() .filterIsInstance<VkPhotoDomain>()
.filterIsInstance<VkPhotoDomain>() .mapNotNull { photo -> photo.getMaxSize()?.url }
.mapNotNull { photo -> photo.getMaxSize()?.url }
onPhotoClicked( onPhotoClicked(
photos, photos,
photos.indexOfFirst { it == attachment.getMaxSize()?.url } photos.indexOfFirst { it == attachment.getMaxSize()?.url }
) )
// val maxSize = attachment.getMaxSize() // val maxSize = attachment.getMaxSize()
// maxSize?.let { // maxSize?.let {
@@ -101,39 +100,36 @@ fun MessagesList(
// Intent(Intent.ACTION_VIEW, maxSize.url.toUri()) // Intent(Intent.ACTION_VIEW, maxSize.url.toUri())
// ) // )
// } // }
} }
is VkFileDomain -> { is VkFileDomain -> {
context.startActivity( context.startActivity(
Intent(Intent.ACTION_VIEW, attachment.url.toUri()) Intent(Intent.ACTION_VIEW, attachment.url.toUri())
) )
} }
is VkLinkDomain -> { is VkLinkDomain -> {
context.startActivity( context.startActivity(
Intent(Intent.ACTION_VIEW, attachment.url.toUri()) Intent(Intent.ACTION_VIEW, attachment.url.toUri())
) )
}
} }
} }
} }
) }
val onAttachmentLongClick by rememberUpdatedState( val onAttachmentLongClick by rememberUpdatedState { message: UiItem.Message, attachment: VkAttachment ->
{ message: UiItem.Message, attachment: VkAttachment -> if (isSelectedAtLeastOne) {
if (isSelectedAtLeastOne) { onMessageLongClicked(message.id)
onMessageLongClicked(message.id) uiMessages
uiMessages } else {
} else { when (attachment) {
when (attachment) { is VkPhotoDomain -> {
is VkPhotoDomain -> { val maxSize = attachment.getMaxSize()
val maxSize = attachment.getMaxSize() Log.d("MessagesList", "onPhotoLongClicked. Max size: ${maxSize?.url}")
Log.d("MessagesList", "onPhotoLongClicked. Max size: ${maxSize?.url}")
}
} }
} }
} }
) }
LazyColumn( LazyColumn(
modifier = modifier modifier = modifier
@@ -207,7 +203,6 @@ fun MessagesList(
if (offsetDistinct == -100f && AppSettings.General.enableHaptic) { if (offsetDistinct == -100f && AppSettings.General.enableHaptic) {
view.performHapticFeedback(HapticFeedbackConstantsCompat.CONTEXT_CLICK) view.performHapticFeedback(HapticFeedbackConstantsCompat.CONTEXT_CLICK)
} }
Log.d("MessagesList", "offsetDistinct: $offsetDistinct")
} }
Surface( Surface(
@@ -28,14 +28,14 @@ import androidx.compose.ui.unit.dp
@Composable @Composable
fun Reply( fun Reply(
modifier: Modifier = Modifier, onClick: () -> Unit,
bottomPadding: Dp, bottomPadding: Dp,
shape: Shape, shape: Shape,
onClick: () -> Unit,
backgroundColor: Color, backgroundColor: Color,
innerBackgroundColor: Color, innerBackgroundColor: Color,
title: String, title: String,
summary: String? summary: String?,
modifier: Modifier = Modifier
) { ) {
Box( Box(
modifier = modifier modifier = modifier