diff --git a/core/network/src/main/kotlin/dev/meloda/fast/network/ResponseConverterFactory.kt b/core/network/src/main/kotlin/dev/meloda/fast/network/ResponseConverterFactory.kt index 3168077d..af40f0f3 100644 --- a/core/network/src/main/kotlin/dev/meloda/fast/network/ResponseConverterFactory.kt +++ b/core/network/src/main/kotlin/dev/meloda/fast/network/ResponseConverterFactory.kt @@ -43,6 +43,12 @@ class ResponseConverterFactory(private val converter: JsonConverter) : Converter converter.fromJson(successType, string) }.fold( onSuccess = { successModel -> + if (successModel is ApiResponse<*>) { + if (successModel.error != null) { + throw ApiException(successModel.error) + } + } + return successModel }, onFailure = { failure -> diff --git a/core/network/src/main/kotlin/dev/meloda/fast/network/VkErrorCode.kt b/core/network/src/main/kotlin/dev/meloda/fast/network/VkErrorCode.kt index b15d2734..7273e354 100644 --- a/core/network/src/main/kotlin/dev/meloda/fast/network/VkErrorCode.kt +++ b/core/network/src/main/kotlin/dev/meloda/fast/network/VkErrorCode.kt @@ -42,6 +42,8 @@ enum class VkErrorCode(val code: Int) { ACCESS_TO_DOC_DENIED(1153), SOME_AUTH_ERROR(104), + + CANNOT_SEND_MESSAGE_DUE_TO_PRIVACY_SETTINGS(902), ACCESS_TOKEN_EXPIRED(1117); companion object { diff --git a/core/ui/src/main/res/drawable/baseline_account_circle_24.xml b/core/ui/src/main/res/drawable/baseline_account_circle_24.xml index 1e24cf39..fd0b7042 100644 --- a/core/ui/src/main/res/drawable/baseline_account_circle_24.xml +++ b/core/ui/src/main/res/drawable/baseline_account_circle_24.xml @@ -1,5 +1,11 @@ - - - - + + + + diff --git a/core/ui/src/main/res/drawable/baseline_chat_24.xml b/core/ui/src/main/res/drawable/baseline_chat_24.xml index 7f6fda16..1ae140a8 100644 --- a/core/ui/src/main/res/drawable/baseline_chat_24.xml +++ b/core/ui/src/main/res/drawable/baseline_chat_24.xml @@ -1,5 +1,12 @@ - - - - + + + + diff --git a/core/ui/src/main/res/drawable/baseline_people_alt_24.xml b/core/ui/src/main/res/drawable/baseline_people_alt_24.xml index 318f73c5..3864d497 100644 --- a/core/ui/src/main/res/drawable/baseline_people_alt_24.xml +++ b/core/ui/src/main/res/drawable/baseline_people_alt_24.xml @@ -1,11 +1,27 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/core/ui/src/main/res/drawable/outline_account_circle_24.xml b/core/ui/src/main/res/drawable/outline_account_circle_24.xml index c85da5ee..00ae294f 100644 --- a/core/ui/src/main/res/drawable/outline_account_circle_24.xml +++ b/core/ui/src/main/res/drawable/outline_account_circle_24.xml @@ -1,7 +1,15 @@ - - - - - - + + + + + + diff --git a/core/ui/src/main/res/drawable/outline_chat_24.xml b/core/ui/src/main/res/drawable/outline_chat_24.xml index 7ce81fa5..161149c8 100644 --- a/core/ui/src/main/res/drawable/outline_chat_24.xml +++ b/core/ui/src/main/res/drawable/outline_chat_24.xml @@ -1,5 +1,12 @@ - - - - + + + + diff --git a/core/ui/src/main/res/drawable/outline_people_alt_24.xml b/core/ui/src/main/res/drawable/outline_people_alt_24.xml index f3e073ee..4732046f 100644 --- a/core/ui/src/main/res/drawable/outline_people_alt_24.xml +++ b/core/ui/src/main/res/drawable/outline_people_alt_24.xml @@ -1,11 +1,23 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/core/ui/src/main/res/drawable/round_access_time_24.xml b/core/ui/src/main/res/drawable/round_access_time_24.xml new file mode 100644 index 00000000..eef452fb --- /dev/null +++ b/core/ui/src/main/res/drawable/round_access_time_24.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/core/ui/src/main/res/drawable/round_error_outline_24.xml b/core/ui/src/main/res/drawable/round_error_outline_24.xml new file mode 100644 index 00000000..c61c59cb --- /dev/null +++ b/core/ui/src/main/res/drawable/round_error_outline_24.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/MessagesHistoryViewModel.kt b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/MessagesHistoryViewModel.kt index 9f910401..27f046df 100644 --- a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/MessagesHistoryViewModel.kt +++ b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/MessagesHistoryViewModel.kt @@ -24,6 +24,7 @@ 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 @@ -407,6 +408,16 @@ class MessagesHistoryViewModelImpl( state.processState( error = { error -> 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 -> sendingMessages -= newMessage @@ -419,7 +430,10 @@ class MessagesHistoryViewModelImpl( uiMessages.indexOfOrNull(newUiMessage)?.let { index -> (uiMessages[index] as? UiItem.Message)?.let { message -> uiMessages[index] = message - .copy(id = messageId) + .copy( + id = messageId, + sendingStatus = SendingStatus.SENT + ) .copy(isRead = newMessage.isRead(screenState.value.conversation)) } } diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/model/SendingStatus.kt b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/model/SendingStatus.kt new file mode 100644 index 00000000..48bd9d92 --- /dev/null +++ b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/model/SendingStatus.kt @@ -0,0 +1,5 @@ +package dev.meloda.fast.messageshistory.model + +enum class SendingStatus { + SENDING, SENT, FAILED +} diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/model/UiItem.kt b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/model/UiItem.kt index fbfbffb7..1eadb2d0 100644 --- a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/model/UiItem.kt +++ b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/model/UiItem.kt @@ -23,7 +23,8 @@ sealed class UiItem( val showName: Boolean, val avatar: UiImage, val isEdited: Boolean, - val isRead: Boolean + val isRead: Boolean, + val sendingStatus: SendingStatus = SendingStatus.SENT ) : UiItem(id, conversationMessageId) data class ActionMessage( diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/IncomingMessageBubble.kt b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/IncomingMessageBubble.kt index bb13bc7a..d01bdef3 100644 --- a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/IncomingMessageBubble.kt +++ b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/IncomingMessageBubble.kt @@ -82,7 +82,8 @@ fun IncomingMessageBubble( date = message.date, edited = message.isEdited, animate = animate, - isRead = message.isRead + isRead = message.isRead, + sendingStatus = message.sendingStatus ) } } diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessageBubble.kt b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessageBubble.kt index abb7d6e2..f4c1459c 100644 --- a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessageBubble.kt +++ b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessageBubble.kt @@ -15,6 +15,7 @@ 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 @@ -24,8 +25,10 @@ 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 @@ -36,7 +39,8 @@ fun MessageBubble( date: String?, edited: Boolean, animate: Boolean, - isRead: Boolean + isRead: Boolean, + sendingStatus: SendingStatus ) { val backgroundColor = if (!isOut) { MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp) @@ -109,9 +113,18 @@ fun MessageBubble( Icon( modifier = Modifier.size(14.dp), painter = painterResource( - if (isRead) UiR.drawable.round_done_all_24 - else UiR.drawable.ic_round_done_24 + 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 + } + + SendingStatus.FAILED -> UiR.drawable.round_error_outline_24 + } ), + tint = if (sendingStatus == SendingStatus.FAILED) Color.Red + else LocalContentColor.current, contentDescription = null ) } diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/OutgoingMessageBubble.kt b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/OutgoingMessageBubble.kt index 4ccade7c..f25a1bdb 100644 --- a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/OutgoingMessageBubble.kt +++ b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/OutgoingMessageBubble.kt @@ -37,7 +37,8 @@ fun OutgoingMessageBubble( date = message.date, edited = message.isEdited, animate = animate, - isRead = message.isRead + isRead = message.isRead, + sendingStatus = message.sendingStatus ) } } diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/util/MessageMapper.kt b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/util/MessageMapper.kt index 715082e2..94c8ac6f 100644 --- a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/util/MessageMapper.kt +++ b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/util/MessageMapper.kt @@ -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 @@ -123,11 +124,14 @@ fun VkMessage.asPresentation( showName = showName && extractShowName(prevMessage), avatar = extractAvatar(), isEdited = updateTime != null, - isRead = isRead(conversation) + 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