Release 0.1.8 (#139)
* pagination in chat fixed * other fixes and improvements * fixed visual bug in progress bar in chat history * Refactor: Enhance conversations and friends features - In `ConversationsScreen`, removed `isNeedToScrollToTop` and `onScrolledToTop`, and refactored toolbar container color logic. Added `NoItemsView` for empty conversation lists. - In `MainGraph`, added `onMessageClicked` for navigation to message history. - In `ApiEvent`, introduced `parseOrNull` for handling unknown event types. - In `ConversationsViewModel`, removed `scrollToTop` logic and refactored error handling. - In `FriendsViewModel`, refactored error handling and introduced `onErrorConsumed` and `handleError`. - In `FriendItem`, added an icon button to initiate sending a message to a friend. - In `strings.xml`, added or updated strings for session expiration, log out, refreshing, and empty friend lists. - In `RootScreen`, added `onMessageClicked` for navigating to messages. - In `FriendsList`, added `onMessageClicked` for handling message clicks. - In `MainScreen`, removed unused `MutableSharedFlow`. - In `FriendsScreen`, added support for showing errors, added `onMessageClicked`, and replaced `hazeChild` with `hazeEffect` and `hazeSource`. - In `FriendsNavigation`, added `onMessageClicked` for handling message clicks. - In `ConversationsNavigation`, removed the unused `scrollToTopFlow` parameter. - In `ErrorView`, added text alignment. - In `NoItemsView`, added support for a button and custom text. - In `LongPollUpdatesParser`, replaced try-catch with `parseOrNull`. * Chat creation feature (#138) * - read indicator, edit status and time for message in messages history * message sending status
This commit is contained in:
+85
-36
@@ -24,11 +24,13 @@ import dev.meloda.fast.domain.LongPollUpdatesParser
|
||||
import dev.meloda.fast.domain.MessagesUseCase
|
||||
import dev.meloda.fast.messageshistory.model.ActionMode
|
||||
import dev.meloda.fast.messageshistory.model.MessagesHistoryScreenState
|
||||
import dev.meloda.fast.messageshistory.model.SendingStatus
|
||||
import dev.meloda.fast.messageshistory.model.UiItem
|
||||
import dev.meloda.fast.messageshistory.navigation.MessagesHistory
|
||||
import dev.meloda.fast.messageshistory.util.asPresentation
|
||||
import dev.meloda.fast.messageshistory.util.extractAvatar
|
||||
import dev.meloda.fast.messageshistory.util.extractTitle
|
||||
import dev.meloda.fast.messageshistory.util.findMessageById
|
||||
import dev.meloda.fast.model.BaseError
|
||||
import dev.meloda.fast.model.LongPollEvent
|
||||
import dev.meloda.fast.model.api.domain.VkAttachment
|
||||
@@ -160,7 +162,9 @@ class MessagesHistoryViewModelImpl(
|
||||
val message = event.message
|
||||
|
||||
Log.d("MessagesHistoryViewModel", "handleNewMessage: $message")
|
||||
|
||||
if (message.peerId != screenState.value.conversationId) return
|
||||
if (screenState.value.messages.findMessageById(message.id) != null) return
|
||||
|
||||
val randomIds = messages.value.map(VkMessage::randomId)
|
||||
if (message.randomId != 0 && message.randomId in randomIds) return
|
||||
@@ -174,22 +178,22 @@ class MessagesHistoryViewModelImpl(
|
||||
|
||||
val newMessage = message.asPresentation(
|
||||
resourceProvider = resourceProvider,
|
||||
showDate = false,
|
||||
showName = false,
|
||||
prevMessage = prevMessage,
|
||||
nextMessage = null,
|
||||
showTimeInActionMessages = userSettings.showTimeInActionMessages.value
|
||||
showTimeInActionMessages = userSettings.showTimeInActionMessages.value,
|
||||
conversation = screenState.value.conversation,
|
||||
)
|
||||
newMessages.add(0, newMessage)
|
||||
|
||||
prevMessage?.let { prev ->
|
||||
newMessages[1] = prev.asPresentation(
|
||||
resourceProvider = resourceProvider,
|
||||
showDate = false,
|
||||
showName = false,
|
||||
prevMessage = prevMessage,
|
||||
nextMessage = messages.value.first(),
|
||||
showTimeInActionMessages = userSettings.showTimeInActionMessages.value
|
||||
showTimeInActionMessages = userSettings.showTimeInActionMessages.value,
|
||||
conversation = screenState.value.conversation
|
||||
)
|
||||
}
|
||||
|
||||
@@ -205,11 +209,11 @@ class MessagesHistoryViewModelImpl(
|
||||
?.let { index ->
|
||||
val newMessage = message.asPresentation(
|
||||
resourceProvider = resourceProvider,
|
||||
showDate = false,
|
||||
showName = false,
|
||||
prevMessage = messages.value.getOrNull(index + 1),
|
||||
nextMessage = messages.value.getOrNull(index - 1),
|
||||
showTimeInActionMessages = userSettings.showTimeInActionMessages.value
|
||||
showTimeInActionMessages = userSettings.showTimeInActionMessages.value,
|
||||
conversation = screenState.value.conversation
|
||||
)
|
||||
|
||||
val newMessages = screenState.value.messages.toMutableList()
|
||||
@@ -224,7 +228,37 @@ class MessagesHistoryViewModelImpl(
|
||||
}
|
||||
|
||||
private fun handleReadOutgoingEvent(event: LongPollEvent.VkMessageReadOutgoingEvent) {
|
||||
if (event.peerId != screenState.value.conversationId) return
|
||||
|
||||
val messages = messages.value
|
||||
val messageIndex =
|
||||
messages.indexOfFirstOrNull { it.id == event.messageId }
|
||||
|
||||
if (messageIndex == null) { // диалога нет в списке
|
||||
// pizdets
|
||||
} else {
|
||||
val newConversation = screenState.value.conversation.copy(
|
||||
outRead = event.messageId
|
||||
)
|
||||
|
||||
val uiMessages = messages.mapIndexed { index, item ->
|
||||
item.asPresentation(
|
||||
resourceProvider = resourceProvider,
|
||||
showName = false,
|
||||
prevMessage = messages.getOrNull(index + 1),
|
||||
nextMessage = messages.getOrNull(index - 1),
|
||||
showTimeInActionMessages = userSettings.showTimeInActionMessages.value,
|
||||
conversation = newConversation
|
||||
)
|
||||
}
|
||||
|
||||
screenState.setValue { old ->
|
||||
old.copy(
|
||||
conversation = newConversation,
|
||||
messages = uiMessages,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadMessagesHistory(offset: Int = currentOffset.value) {
|
||||
@@ -236,9 +270,7 @@ class MessagesHistoryViewModelImpl(
|
||||
offset = offset,
|
||||
).listenValue(viewModelScope) { state ->
|
||||
state.processState(
|
||||
error = { error ->
|
||||
|
||||
},
|
||||
error = { error -> },
|
||||
success = { response ->
|
||||
val messages = response.messages
|
||||
val fullMessages = if (offset == 0) {
|
||||
@@ -256,16 +288,6 @@ class MessagesHistoryViewModelImpl(
|
||||
messagesUseCase.storeMessages(messages)
|
||||
conversationsUseCase.storeConversations(conversations)
|
||||
|
||||
val loadedMessages = fullMessages.mapIndexed { index, message ->
|
||||
message.asPresentation(
|
||||
resourceProvider = resourceProvider,
|
||||
showDate = false,
|
||||
showName = false,
|
||||
prevMessage = messages.getOrNull(index + 1),
|
||||
nextMessage = messages.getOrNull(index - 1),
|
||||
showTimeInActionMessages = userSettings.showTimeInActionMessages.value
|
||||
)
|
||||
}
|
||||
|
||||
val itemsCountSufficient = messages.size == MESSAGES_LOAD_COUNT
|
||||
|
||||
@@ -278,15 +300,28 @@ class MessagesHistoryViewModelImpl(
|
||||
conversations
|
||||
.firstOrNull { it.id == screenState.value.conversationId }
|
||||
?.let { conversation ->
|
||||
screenState.setValue { old -> old.copy(conversation = conversation) }
|
||||
newState = newState.copy(
|
||||
title = conversation.extractTitle(
|
||||
useContactName = AppSettings.General.useContactNames,
|
||||
resources = resourceProvider.resources
|
||||
),
|
||||
avatar = conversation.extractAvatar()
|
||||
avatar = conversation.extractAvatar(),
|
||||
conversation = conversation
|
||||
)
|
||||
}
|
||||
|
||||
val loadedMessages = fullMessages.mapIndexed { index, message ->
|
||||
message.asPresentation(
|
||||
resourceProvider = resourceProvider,
|
||||
showName = false,
|
||||
prevMessage = messages.getOrNull(index + 1),
|
||||
nextMessage = messages.getOrNull(index - 1),
|
||||
showTimeInActionMessages = userSettings.showTimeInActionMessages.value,
|
||||
conversation = screenState.value.conversation
|
||||
)
|
||||
}
|
||||
|
||||
this.messages.emit(fullMessages)
|
||||
screenState.setValue { newState.copy(messages = loadedMessages) }
|
||||
canPaginate.setValue { itemsCountSufficient }
|
||||
@@ -347,18 +382,14 @@ class MessagesHistoryViewModelImpl(
|
||||
val newMessages = screenState.value.messages.toMutableList()
|
||||
val newUiMessage = newMessage.asPresentation(
|
||||
resourceProvider = resourceProvider,
|
||||
showDate = false,
|
||||
showName = false,
|
||||
prevMessage = messages.value.firstOrNull(),
|
||||
nextMessage = null,
|
||||
showTimeInActionMessages = userSettings.showTimeInActionMessages.value
|
||||
showTimeInActionMessages = userSettings.showTimeInActionMessages.value,
|
||||
conversation = screenState.value.conversation
|
||||
)
|
||||
newMessages.add(0, newUiMessage)
|
||||
|
||||
messages.setValue { old ->
|
||||
listOf(newMessage).plus(old)
|
||||
}
|
||||
|
||||
screenState.setValue { old ->
|
||||
old.copy(
|
||||
message = TextFieldValue(),
|
||||
@@ -377,19 +408,37 @@ class MessagesHistoryViewModelImpl(
|
||||
state.processState(
|
||||
error = { error ->
|
||||
sendingMessages -= newMessage
|
||||
},
|
||||
success = { messageId ->
|
||||
sendingMessages += newMessage
|
||||
|
||||
val messages = screenState.value.messages.toMutableList()
|
||||
val uiMessages = screenState.value.messages.toMutableList()
|
||||
|
||||
messages.indexOfOrNull(newUiMessage)?.let { index ->
|
||||
(messages[index] as? UiItem.Message)?.let { message ->
|
||||
messages[index] = message.copy(id = messageId)
|
||||
uiMessages.indexOfOrNull(newUiMessage)?.let { index ->
|
||||
(uiMessages[index] as? UiItem.Message)?.let { message ->
|
||||
uiMessages[index] = message.copy(sendingStatus = SendingStatus.FAILED)
|
||||
}
|
||||
}
|
||||
|
||||
screenState.setValue { old -> old.copy(messages = messages) }
|
||||
screenState.setValue { old -> old.copy(messages = uiMessages) }
|
||||
},
|
||||
success = { messageId ->
|
||||
sendingMessages -= newMessage
|
||||
|
||||
val uiMessages = screenState.value.messages.toMutableList()
|
||||
messages.setValue { old ->
|
||||
listOf(newMessage.copy(id = messageId)).plus(old)
|
||||
}
|
||||
|
||||
uiMessages.indexOfOrNull(newUiMessage)?.let { index ->
|
||||
(uiMessages[index] as? UiItem.Message)?.let { message ->
|
||||
uiMessages[index] = message
|
||||
.copy(
|
||||
id = messageId,
|
||||
sendingStatus = SendingStatus.SENT
|
||||
)
|
||||
.copy(isRead = newMessage.isRead(screenState.value.conversation))
|
||||
}
|
||||
}
|
||||
|
||||
screenState.setValue { old -> old.copy(messages = uiMessages) }
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -508,11 +557,11 @@ class MessagesHistoryViewModelImpl(
|
||||
val uiMessages = messages.mapIndexed { index, item ->
|
||||
item.asPresentation(
|
||||
resourceProvider = resourceProvider,
|
||||
showDate = false,
|
||||
showName = false,
|
||||
prevMessage = messages.getOrNull(index + 1),
|
||||
nextMessage = messages.getOrNull(index - 1),
|
||||
showTimeInActionMessages = show
|
||||
showTimeInActionMessages = show,
|
||||
conversation = screenState.value.conversation
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+5
-2
@@ -4,6 +4,7 @@ import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import dev.meloda.fast.common.model.UiImage
|
||||
import dev.meloda.fast.model.api.domain.VkAttachment
|
||||
import dev.meloda.fast.model.api.domain.VkConversation
|
||||
|
||||
@Immutable
|
||||
data class MessagesHistoryScreenState(
|
||||
@@ -18,7 +19,8 @@ data class MessagesHistoryScreenState(
|
||||
val isPaginating: Boolean,
|
||||
val isPaginationExhausted: Boolean,
|
||||
val actionMode: ActionMode,
|
||||
val chatImageUrl: String?
|
||||
val chatImageUrl: String?,
|
||||
val conversation: VkConversation
|
||||
) {
|
||||
|
||||
companion object {
|
||||
@@ -34,7 +36,8 @@ data class MessagesHistoryScreenState(
|
||||
isPaginating = false,
|
||||
isPaginationExhausted = false,
|
||||
actionMode = ActionMode.Record,
|
||||
chatImageUrl = null
|
||||
chatImageUrl = null,
|
||||
conversation = VkConversation.EMPTY
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package dev.meloda.fast.messageshistory.model
|
||||
|
||||
enum class SendingStatus {
|
||||
SENDING, SENT, FAILED
|
||||
}
|
||||
+3
-2
@@ -22,7 +22,9 @@ sealed class UiItem(
|
||||
val showAvatar: Boolean,
|
||||
val showName: Boolean,
|
||||
val avatar: UiImage,
|
||||
val isEdited: Boolean
|
||||
val isEdited: Boolean,
|
||||
val isRead: Boolean,
|
||||
val sendingStatus: SendingStatus = SendingStatus.SENT
|
||||
) : UiItem(id, conversationMessageId)
|
||||
|
||||
data class ActionMessage(
|
||||
@@ -32,4 +34,3 @@ sealed class UiItem(
|
||||
val actionCmId: Int?
|
||||
) : UiItem(id, conversationMessageId)
|
||||
}
|
||||
|
||||
|
||||
+10
-6
@@ -31,6 +31,7 @@ import dev.meloda.fast.messageshistory.model.UiItem
|
||||
fun IncomingMessageBubble(
|
||||
modifier: Modifier = Modifier,
|
||||
message: UiItem.Message,
|
||||
animate: Boolean
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
@@ -44,12 +45,12 @@ fun IncomingMessageBubble(
|
||||
if (message.isInChat) {
|
||||
Image(
|
||||
painter =
|
||||
message.avatar.extractUrl()?.let { url ->
|
||||
rememberAsyncImagePainter(
|
||||
model = url,
|
||||
imageLoader = context.imageLoader
|
||||
)
|
||||
} ?: painterResource(id = message.avatar.extractResId()),
|
||||
message.avatar.extractUrl()?.let { url ->
|
||||
rememberAsyncImagePainter(
|
||||
model = url,
|
||||
imageLoader = context.imageLoader
|
||||
)
|
||||
} ?: painterResource(id = message.avatar.extractResId()),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.padding(bottom = 6.dp)
|
||||
@@ -80,6 +81,9 @@ fun IncomingMessageBubble(
|
||||
isOut = false,
|
||||
date = message.date,
|
||||
edited = message.isEdited,
|
||||
animate = animate,
|
||||
isRead = message.isRead,
|
||||
sendingStatus = message.sendingStatus
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+72
-27
@@ -1,19 +1,35 @@
|
||||
package dev.meloda.fast.messageshistory.presentation
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Create
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.surfaceColorAtElevation
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.meloda.fast.messageshistory.model.SendingStatus
|
||||
import dev.meloda.fast.ui.R as UiR
|
||||
|
||||
@Composable
|
||||
fun MessageBubble(
|
||||
@@ -22,6 +38,9 @@ fun MessageBubble(
|
||||
isOut: Boolean,
|
||||
date: String?,
|
||||
edited: Boolean,
|
||||
animate: Boolean,
|
||||
isRead: Boolean,
|
||||
sendingStatus: SendingStatus
|
||||
) {
|
||||
val backgroundColor = if (!isOut) {
|
||||
MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp)
|
||||
@@ -45,44 +64,70 @@ fun MessageBubble(
|
||||
vertical = 6.dp
|
||||
)
|
||||
) {
|
||||
val minDateContainerWidth = remember(edited, isOut) {
|
||||
val mainPart = if (edited) 50.dp else 30.dp
|
||||
val readIndicatorPart = if (isOut) 14.dp else 0.dp
|
||||
|
||||
mainPart + readIndicatorPart
|
||||
}
|
||||
|
||||
val dateContainerWidth by animateDpAsState(
|
||||
targetValue = minDateContainerWidth,
|
||||
label = "dateContainerWidth"
|
||||
)
|
||||
|
||||
if (text != null) {
|
||||
Text(
|
||||
text = text,
|
||||
modifier = Modifier
|
||||
.padding(2.dp)
|
||||
.align(Alignment.Center)
|
||||
.animateContentSize(),
|
||||
.padding(end = 4.dp)
|
||||
.padding(end = dateContainerWidth)
|
||||
.padding(end = 4.dp)
|
||||
.then(if (animate) Modifier.animateContentSize() else Modifier),
|
||||
color = textColor
|
||||
)
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomEnd)
|
||||
.defaultMinSize(minWidth = dateContainerWidth)
|
||||
) {
|
||||
if (edited) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Create,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(14.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
}
|
||||
Text(
|
||||
text = date.orEmpty(),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
|
||||
// val dateContainerWidth by animateDpAsState(
|
||||
// targetValue = if (edited) 50.dp else 30.dp,
|
||||
// label = "dateContainerWidth"
|
||||
// )
|
||||
if (isOut) {
|
||||
Icon(
|
||||
modifier = Modifier.size(14.dp),
|
||||
painter = painterResource(
|
||||
when (sendingStatus) {
|
||||
SendingStatus.SENDING -> UiR.drawable.round_access_time_24
|
||||
SendingStatus.SENT -> {
|
||||
if (isRead) UiR.drawable.round_done_all_24
|
||||
else UiR.drawable.ic_round_done_24
|
||||
}
|
||||
|
||||
// AnimatedVisibility(
|
||||
// date != null,
|
||||
// modifier = Modifier
|
||||
// .width(dateContainerWidth)
|
||||
// .align(Alignment.BottomEnd)
|
||||
// ) {
|
||||
// Row(modifier = Modifier.fillMaxWidth()) {
|
||||
// if (edited) {
|
||||
// Icon(
|
||||
// imageVector = Icons.Rounded.Create,
|
||||
// contentDescription = null,
|
||||
// modifier = Modifier.size(14.dp)
|
||||
// )
|
||||
// Spacer(modifier = Modifier.width(4.dp))
|
||||
// }
|
||||
// Text(
|
||||
// text = date.orEmpty(),
|
||||
// style = MaterialTheme.typography.labelSmall
|
||||
// )
|
||||
// Spacer(modifier = Modifier.width(2.dp))
|
||||
// }
|
||||
// }
|
||||
SendingStatus.FAILED -> UiR.drawable.round_error_outline_24
|
||||
}
|
||||
),
|
||||
tint = if (sendingStatus == SendingStatus.FAILED) Color.Red
|
||||
else LocalContentColor.current,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -159,7 +159,7 @@ fun MessagesHistoryScreen(
|
||||
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
val paginationConditionMet by remember {
|
||||
val paginationConditionMet by remember(canPaginate, listState) {
|
||||
derivedStateOf {
|
||||
canPaginate &&
|
||||
(listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index
|
||||
@@ -282,6 +282,7 @@ fun MessagesHistoryScreen(
|
||||
|
||||
// TODO: 11/07/2024, Danil Nikolaev: to VM
|
||||
|
||||
// TODO: 23-Mar-25, Danil Nikolaev: crash if not messages (ex. new chat)
|
||||
onChatMaterialsDropdownItemClicked(
|
||||
screenState.conversationId,
|
||||
screenState.messages.firstMessage().conversationMessageId
|
||||
|
||||
+22
-19
@@ -90,26 +90,28 @@ fun MessagesList(
|
||||
if (item.isOut) {
|
||||
OutgoingMessageBubble(
|
||||
modifier =
|
||||
Modifier.then(
|
||||
if (enableAnimations) Modifier.animateItem(
|
||||
fadeInSpec = null,
|
||||
fadeOutSpec = null
|
||||
)
|
||||
else Modifier
|
||||
),
|
||||
Modifier.then(
|
||||
if (enableAnimations) Modifier.animateItem(
|
||||
fadeInSpec = null,
|
||||
fadeOutSpec = null
|
||||
)
|
||||
else Modifier
|
||||
),
|
||||
message = item,
|
||||
animate = enableAnimations
|
||||
)
|
||||
} else {
|
||||
IncomingMessageBubble(
|
||||
modifier =
|
||||
Modifier.then(
|
||||
if (enableAnimations) Modifier.animateItem(
|
||||
fadeInSpec = null,
|
||||
fadeOutSpec = null
|
||||
)
|
||||
else Modifier
|
||||
),
|
||||
Modifier.then(
|
||||
if (enableAnimations) Modifier.animateItem(
|
||||
fadeInSpec = null,
|
||||
fadeOutSpec = null
|
||||
)
|
||||
else Modifier
|
||||
),
|
||||
message = item,
|
||||
animate = enableAnimations
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -128,16 +130,17 @@ fun MessagesList(
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.statusBarsPadding()
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.height(64.dp)
|
||||
.fillMaxWidth()
|
||||
)
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.statusBarsPadding()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-14
@@ -3,12 +3,8 @@ package dev.meloda.fast.messageshistory.presentation
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
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
|
||||
@@ -20,6 +16,7 @@ import dev.meloda.fast.messageshistory.model.UiItem
|
||||
fun OutgoingMessageBubble(
|
||||
modifier: Modifier = Modifier,
|
||||
message: UiItem.Message,
|
||||
animate: Boolean
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
@@ -37,18 +34,12 @@ fun OutgoingMessageBubble(
|
||||
modifier = Modifier,
|
||||
text = message.text.orDots(),
|
||||
isOut = true,
|
||||
date = null,
|
||||
date = message.date,
|
||||
edited = message.isEdited,
|
||||
animate = animate,
|
||||
isRead = message.isRead,
|
||||
sendingStatus = message.sendingStatus
|
||||
)
|
||||
|
||||
if (message.showDate) {
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Text(
|
||||
modifier = Modifier.padding(end = 12.dp),
|
||||
text = message.date,
|
||||
style = MaterialTheme.typography.labelSmall
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,13 @@ import dev.meloda.fast.messageshistory.model.UiItem
|
||||
|
||||
fun List<UiItem>.firstMessage(): UiItem.Message = filterIsInstance<UiItem.Message>().first()
|
||||
|
||||
fun List<UiItem>.firstMessageOrNull(): UiItem.Message? = filterIsInstance<UiItem.Message>().firstOrNull()
|
||||
|
||||
fun List<UiItem>.indexOfMessageById(messageId: Int): Int =
|
||||
indexOfFirst { it.id == messageId }
|
||||
|
||||
fun List<UiItem>.findMessageById(messageId: Int): UiItem.Message =
|
||||
first { it.id == messageId } as UiItem.Message
|
||||
fun List<UiItem>.findMessageById(messageId: Int): UiItem.Message? =
|
||||
firstOrNull { it.id == messageId } as UiItem.Message?
|
||||
|
||||
fun List<UiItem>.indexOfMessageByCmId(cmId: Int): Int =
|
||||
indexOfFirst { it.cmId == cmId }
|
||||
|
||||
+9
-4
@@ -12,6 +12,7 @@ import dev.meloda.fast.common.model.parseString
|
||||
import dev.meloda.fast.common.provider.ResourceProvider
|
||||
import dev.meloda.fast.data.UserConfig
|
||||
import dev.meloda.fast.data.VkMemoryCache
|
||||
import dev.meloda.fast.messageshistory.model.SendingStatus
|
||||
import dev.meloda.fast.messageshistory.model.UiItem
|
||||
import dev.meloda.fast.model.api.PeerType
|
||||
import dev.meloda.fast.model.api.domain.VkConversation
|
||||
@@ -90,8 +91,8 @@ fun VkConversation.extractTitle(
|
||||
}.parseString(resources).orDots()
|
||||
|
||||
fun VkMessage.asPresentation(
|
||||
conversation: VkConversation,
|
||||
resourceProvider: ResourceProvider,
|
||||
showDate: Boolean,
|
||||
showName: Boolean,
|
||||
prevMessage: VkMessage?,
|
||||
nextMessage: VkMessage?,
|
||||
@@ -118,15 +119,19 @@ fun VkMessage.asPresentation(
|
||||
randomId = randomId,
|
||||
isInChat = isPeerChat(),
|
||||
name = extractTitle(),
|
||||
showDate = showDate,
|
||||
showDate = true,
|
||||
showAvatar = extractShowAvatar(nextMessage),
|
||||
showName = showName && extractShowName(prevMessage),
|
||||
avatar = extractAvatar(),
|
||||
isEdited = updateTime != null
|
||||
isEdited = updateTime != null,
|
||||
isRead = isRead(conversation),
|
||||
sendingStatus = when {
|
||||
id <= 0 -> SendingStatus.SENDING
|
||||
else -> SendingStatus.SENT
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
fun VkMessage.extractShowAvatar(nextMessage: VkMessage?): Boolean {
|
||||
if (isOut) return false
|
||||
return nextMessage == null || nextMessage.fromId != fromId
|
||||
|
||||
Reference in New Issue
Block a user