message sending status

This commit is contained in:
2025-03-23 09:08:29 +03:00
parent 79f539a27b
commit a4feb8978f
17 changed files with 172 additions and 47 deletions
@@ -43,6 +43,12 @@ class ResponseConverterFactory(private val converter: JsonConverter) : Converter
converter.fromJson(successType, string) converter.fromJson(successType, string)
}.fold( }.fold(
onSuccess = { successModel -> onSuccess = { successModel ->
if (successModel is ApiResponse<*>) {
if (successModel.error != null) {
throw ApiException(successModel.error)
}
}
return successModel return successModel
}, },
onFailure = { failure -> onFailure = { failure ->
@@ -42,6 +42,8 @@ enum class VkErrorCode(val code: Int) {
ACCESS_TO_DOC_DENIED(1153), ACCESS_TO_DOC_DENIED(1153),
SOME_AUTH_ERROR(104), SOME_AUTH_ERROR(104),
CANNOT_SEND_MESSAGE_DUE_TO_PRIVACY_SETTINGS(902),
ACCESS_TOKEN_EXPIRED(1117); ACCESS_TOKEN_EXPIRED(1117);
companion object { companion object {
@@ -1,5 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp"> <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,2C6.48,2 2,6.48 2,12s4.48,10 10,10s10,-4.48 10,-10S17.52,2 12,2zM12,6c1.93,0 3.5,1.57 3.5,3.5S13.93,13 12,13s-3.5,-1.57 -3.5,-3.5S10.07,6 12,6zM12,20c-2.03,0 -4.43,-0.82 -6.14,-2.88C7.55,15.8 9.68,15 12,15s4.45,0.8 6.14,2.12C16.43,19.18 14.03,20 12,20z"/> <path
android:fillColor="@android:color/white"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10s10,-4.48 10,-10S17.52,2 12,2zM12,6c1.93,0 3.5,1.57 3.5,3.5S13.93,13 12,13s-3.5,-1.57 -3.5,-3.5S10.07,6 12,6zM12,20c-2.03,0 -4.43,-0.82 -6.14,-2.88C7.55,15.8 9.68,15 12,15s4.45,0.8 6.14,2.12C16.43,19.18 14.03,20 12,20z" />
</vector> </vector>
@@ -1,5 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp"> <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:autoMirrored="true"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="@android:color/white" android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM6,9h12v2L6,11L6,9zM14,14L6,14v-2h8v2zM18,8L6,8L6,6h12v2z"/> <path
android:fillColor="@android:color/white"
android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM6,9h12v2L6,11L6,9zM14,14L6,14v-2h8v2zM18,8L6,8L6,6h12v2z" />
</vector> </vector>
@@ -1,11 +1,27 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp"> <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="#ffffff" android:fillType="evenOdd" android:pathData="M16.67,13.13C18.04,14.06 19,15.32 19,17v3h4v-3C23,14.82 19.43,13.53 16.67,13.13z"/> <path
android:fillColor="#ffffff"
android:fillType="evenOdd"
android:pathData="M16.67,13.13C18.04,14.06 19,15.32 19,17v3h4v-3C23,14.82 19.43,13.53 16.67,13.13z" />
<path android:fillColor="#ffffff" android:fillType="evenOdd" android:pathData="M9,8m-4,0a4,4 0,1 1,8 0a4,4 0,1 1,-8 0"/> <path
android:fillColor="#ffffff"
android:fillType="evenOdd"
android:pathData="M9,8m-4,0a4,4 0,1 1,8 0a4,4 0,1 1,-8 0" />
<path android:fillColor="#ffffff" android:fillType="evenOdd" android:pathData="M15,12c2.21,0 4,-1.79 4,-4c0,-2.21 -1.79,-4 -4,-4c-0.47,0 -0.91,0.1 -1.33,0.24C14.5,5.27 15,6.58 15,8s-0.5,2.73 -1.33,3.76C14.09,11.9 14.53,12 15,12z"/> <path
android:fillColor="#ffffff"
android:fillType="evenOdd"
android:pathData="M15,12c2.21,0 4,-1.79 4,-4c0,-2.21 -1.79,-4 -4,-4c-0.47,0 -0.91,0.1 -1.33,0.24C14.5,5.27 15,6.58 15,8s-0.5,2.73 -1.33,3.76C14.09,11.9 14.53,12 15,12z" />
<path android:fillColor="#ffffff" android:fillType="evenOdd" android:pathData="M9,13c-2.67,0 -8,1.34 -8,4v3h16v-3C17,14.34 11.67,13 9,13z"/> <path
android:fillColor="#ffffff"
android:fillType="evenOdd"
android:pathData="M9,13c-2.67,0 -8,1.34 -8,4v3h16v-3C17,14.34 11.67,13 9,13z" />
</vector> </vector>
@@ -1,7 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp"> <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,2C6.48,2 2,6.48 2,12s4.48,10 10,10s10,-4.48 10,-10S17.52,2 12,2zM7.35,18.5C8.66,17.56 10.26,17 12,17s3.34,0.56 4.65,1.5C15.34,19.44 13.74,20 12,20S8.66,19.44 7.35,18.5zM18.14,17.12L18.14,17.12C16.45,15.8 14.32,15 12,15s-4.45,0.8 -6.14,2.12l0,0C4.7,15.73 4,13.95 4,12c0,-4.42 3.58,-8 8,-8s8,3.58 8,8C20,13.95 19.3,15.73 18.14,17.12z"/> <path
android:fillColor="@android:color/white"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10s10,-4.48 10,-10S17.52,2 12,2zM7.35,18.5C8.66,17.56 10.26,17 12,17s3.34,0.56 4.65,1.5C15.34,19.44 13.74,20 12,20S8.66,19.44 7.35,18.5zM18.14,17.12L18.14,17.12C16.45,15.8 14.32,15 12,15s-4.45,0.8 -6.14,2.12l0,0C4.7,15.73 4,13.95 4,12c0,-4.42 3.58,-8 8,-8s8,3.58 8,8C20,13.95 19.3,15.73 18.14,17.12z" />
<path android:fillColor="@android:color/white" android:pathData="M12,6c-1.93,0 -3.5,1.57 -3.5,3.5S10.07,13 12,13s3.5,-1.57 3.5,-3.5S13.93,6 12,6zM12,11c-0.83,0 -1.5,-0.67 -1.5,-1.5S11.17,8 12,8s1.5,0.67 1.5,1.5S12.83,11 12,11z"/> <path
android:fillColor="@android:color/white"
android:pathData="M12,6c-1.93,0 -3.5,1.57 -3.5,3.5S10.07,13 12,13s3.5,-1.57 3.5,-3.5S13.93,6 12,6zM12,11c-0.83,0 -1.5,-0.67 -1.5,-1.5S11.17,8 12,8s1.5,0.67 1.5,1.5S12.83,11 12,11z" />
</vector> </vector>
@@ -1,5 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp"> <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:autoMirrored="true"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="@android:color/white" android:pathData="M4,4h16v12L5.17,16L4,17.17L4,4m0,-2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2L4,2zM6,12h8v2L6,14v-2zM6,9h12v2L6,11L6,9zM6,6h12v2L6,8L6,6z"/> <path
android:fillColor="@android:color/white"
android:pathData="M4,4h16v12L5.17,16L4,17.17L4,4m0,-2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2L4,2zM6,12h8v2L6,14v-2zM6,9h12v2L6,11L6,9zM6,6h12v2L6,8L6,6z" />
</vector> </vector>
@@ -1,11 +1,23 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp"> <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="M16.67,13.13C18.04,14.06 19,15.32 19,17v3h4v-3C23,14.82 19.43,13.53 16.67,13.13z"/> <path
android:fillColor="@android:color/white"
android:pathData="M16.67,13.13C18.04,14.06 19,15.32 19,17v3h4v-3C23,14.82 19.43,13.53 16.67,13.13z" />
<path android:fillColor="@android:color/white" android:pathData="M15,12c2.21,0 4,-1.79 4,-4c0,-2.21 -1.79,-4 -4,-4c-0.47,0 -0.91,0.1 -1.33,0.24C14.5,5.27 15,6.58 15,8s-0.5,2.73 -1.33,3.76C14.09,11.9 14.53,12 15,12z"/> <path
android:fillColor="@android:color/white"
android:pathData="M15,12c2.21,0 4,-1.79 4,-4c0,-2.21 -1.79,-4 -4,-4c-0.47,0 -0.91,0.1 -1.33,0.24C14.5,5.27 15,6.58 15,8s-0.5,2.73 -1.33,3.76C14.09,11.9 14.53,12 15,12z" />
<path android:fillColor="@android:color/white" android:pathData="M9,12c2.21,0 4,-1.79 4,-4c0,-2.21 -1.79,-4 -4,-4S5,5.79 5,8C5,10.21 6.79,12 9,12zM9,6c1.1,0 2,0.9 2,2c0,1.1 -0.9,2 -2,2S7,9.1 7,8C7,6.9 7.9,6 9,6z"/> <path
android:fillColor="@android:color/white"
android:pathData="M9,12c2.21,0 4,-1.79 4,-4c0,-2.21 -1.79,-4 -4,-4S5,5.79 5,8C5,10.21 6.79,12 9,12zM9,6c1.1,0 2,0.9 2,2c0,1.1 -0.9,2 -2,2S7,9.1 7,8C7,6.9 7.9,6 9,6z" />
<path android:fillColor="@android:color/white" android:pathData="M9,13c-2.67,0 -8,1.34 -8,4v3h16v-3C17,14.34 11.67,13 9,13zM15,18H3l0,-0.99C3.2,16.29 6.3,15 9,15s5.8,1.29 6,2V18z"/> <path
android:fillColor="@android:color/white"
android:pathData="M9,13c-2.67,0 -8,1.34 -8,4v3h16v-3C17,14.34 11.67,13 9,13zM15,18H3l0,-0.99C3.2,16.29 6.3,15 9,15s5.8,1.29 6,2V18z" />
</vector> </vector>
@@ -0,0 +1,11 @@
<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="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM11.78,7h-0.06c-0.4,0 -0.72,0.32 -0.72,0.72v4.72c0,0.35 0.18,0.68 0.49,0.86l4.15,2.49c0.34,0.2 0.78,0.1 0.98,-0.24 0.21,-0.34 0.1,-0.79 -0.25,-0.99l-3.87,-2.3L12.5,7.72c0,-0.4 -0.32,-0.72 -0.72,-0.72z" />
</vector>
@@ -0,0 +1,11 @@
<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,7c0.55,0 1,0.45 1,1v4c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1L11,8c0,-0.55 0.45,-1 1,-1zM11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM13,17h-2v-2h2v2z" />
</vector>
@@ -24,6 +24,7 @@ import dev.meloda.fast.domain.LongPollUpdatesParser
import dev.meloda.fast.domain.MessagesUseCase import dev.meloda.fast.domain.MessagesUseCase
import dev.meloda.fast.messageshistory.model.ActionMode import dev.meloda.fast.messageshistory.model.ActionMode
import dev.meloda.fast.messageshistory.model.MessagesHistoryScreenState 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.model.UiItem
import dev.meloda.fast.messageshistory.navigation.MessagesHistory import dev.meloda.fast.messageshistory.navigation.MessagesHistory
import dev.meloda.fast.messageshistory.util.asPresentation import dev.meloda.fast.messageshistory.util.asPresentation
@@ -407,6 +408,16 @@ class MessagesHistoryViewModelImpl(
state.processState( state.processState(
error = { error -> error = { error ->
sendingMessages -= newMessage sendingMessages -= newMessage
val uiMessages = screenState.value.messages.toMutableList()
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 = uiMessages) }
}, },
success = { messageId -> success = { messageId ->
sendingMessages -= newMessage sendingMessages -= newMessage
@@ -419,7 +430,10 @@ class MessagesHistoryViewModelImpl(
uiMessages.indexOfOrNull(newUiMessage)?.let { index -> uiMessages.indexOfOrNull(newUiMessage)?.let { index ->
(uiMessages[index] as? UiItem.Message)?.let { message -> (uiMessages[index] as? UiItem.Message)?.let { message ->
uiMessages[index] = message uiMessages[index] = message
.copy(id = messageId) .copy(
id = messageId,
sendingStatus = SendingStatus.SENT
)
.copy(isRead = newMessage.isRead(screenState.value.conversation)) .copy(isRead = newMessage.isRead(screenState.value.conversation))
} }
} }
@@ -0,0 +1,5 @@
package dev.meloda.fast.messageshistory.model
enum class SendingStatus {
SENDING, SENT, FAILED
}
@@ -23,7 +23,8 @@ sealed class UiItem(
val showName: Boolean, val showName: Boolean,
val avatar: UiImage, val avatar: UiImage,
val isEdited: Boolean, val isEdited: Boolean,
val isRead: Boolean val isRead: Boolean,
val sendingStatus: SendingStatus = SendingStatus.SENT
) : UiItem(id, conversationMessageId) ) : UiItem(id, conversationMessageId)
data class ActionMessage( data class ActionMessage(
@@ -82,7 +82,8 @@ fun IncomingMessageBubble(
date = message.date, date = message.date,
edited = message.isEdited, edited = message.isEdited,
animate = animate, animate = animate,
isRead = message.isRead isRead = message.isRead,
sendingStatus = message.sendingStatus
) )
} }
} }
@@ -15,6 +15,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Create import androidx.compose.material.icons.rounded.Create
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.material3.surfaceColorAtElevation
@@ -24,8 +25,10 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import dev.meloda.fast.messageshistory.model.SendingStatus
import dev.meloda.fast.ui.R as UiR import dev.meloda.fast.ui.R as UiR
@Composable @Composable
@@ -36,7 +39,8 @@ fun MessageBubble(
date: String?, date: String?,
edited: Boolean, edited: Boolean,
animate: Boolean, animate: Boolean,
isRead: Boolean isRead: Boolean,
sendingStatus: SendingStatus
) { ) {
val backgroundColor = if (!isOut) { val backgroundColor = if (!isOut) {
MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp) MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp)
@@ -109,9 +113,18 @@ fun MessageBubble(
Icon( Icon(
modifier = Modifier.size(14.dp), modifier = Modifier.size(14.dp),
painter = painterResource( painter = painterResource(
if (isRead) UiR.drawable.round_done_all_24 when (sendingStatus) {
else UiR.drawable.ic_round_done_24 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
}
SendingStatus.FAILED -> UiR.drawable.round_error_outline_24
}
), ),
tint = if (sendingStatus == SendingStatus.FAILED) Color.Red
else LocalContentColor.current,
contentDescription = null contentDescription = null
) )
} }
@@ -37,7 +37,8 @@ fun OutgoingMessageBubble(
date = message.date, date = message.date,
edited = message.isEdited, edited = message.isEdited,
animate = animate, animate = animate,
isRead = message.isRead isRead = message.isRead,
sendingStatus = message.sendingStatus
) )
} }
} }
@@ -12,6 +12,7 @@ import dev.meloda.fast.common.model.parseString
import dev.meloda.fast.common.provider.ResourceProvider import dev.meloda.fast.common.provider.ResourceProvider
import dev.meloda.fast.data.UserConfig import dev.meloda.fast.data.UserConfig
import dev.meloda.fast.data.VkMemoryCache import dev.meloda.fast.data.VkMemoryCache
import dev.meloda.fast.messageshistory.model.SendingStatus
import dev.meloda.fast.messageshistory.model.UiItem import dev.meloda.fast.messageshistory.model.UiItem
import dev.meloda.fast.model.api.PeerType import dev.meloda.fast.model.api.PeerType
import dev.meloda.fast.model.api.domain.VkConversation import dev.meloda.fast.model.api.domain.VkConversation
@@ -123,11 +124,14 @@ fun VkMessage.asPresentation(
showName = showName && extractShowName(prevMessage), showName = showName && extractShowName(prevMessage),
avatar = extractAvatar(), avatar = extractAvatar(),
isEdited = updateTime != null, isEdited = updateTime != null,
isRead = isRead(conversation) isRead = isRead(conversation),
sendingStatus = when {
id <= 0 -> SendingStatus.SENDING
else -> SendingStatus.SENT
}
) )
} }
fun VkMessage.extractShowAvatar(nextMessage: VkMessage?): Boolean { fun VkMessage.extractShowAvatar(nextMessage: VkMessage?): Boolean {
if (isOut) return false if (isOut) return false
return nextMessage == null || nextMessage.fromId != fromId return nextMessage == null || nextMessage.fromId != fromId