show x when sending failed

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
Marcel Hibbe 2024-12-10 14:35:56 +01:00
parent a78c9e1c08
commit e1c1574d6c
No known key found for this signature in database
GPG Key ID: C793F8B59F43CE7B
8 changed files with 115 additions and 32 deletions

View File

@ -114,7 +114,19 @@ class OutcomingTextMessageViewHolder(itemView: View) :
binding.messageQuote.quotedChatMessageView.visibility = View.GONE binding.messageQuote.quotedChatMessageView.visibility = View.GONE
} }
setReadStatus(message.readStatus)
when (message.readStatus) {
ReadStatus.READ -> updateReadStatus(R.drawable.ic_check_all, context.resources?.getString(R.string.nc_message_read))
ReadStatus.SENT -> updateReadStatus(R.drawable.ic_check, context.resources?.getString(R.string.nc_message_sent))
ReadStatus.SENDING -> updateSendingStatus()
ReadStatus.FAILED -> updateReadStatus(
R.drawable.ic_baseline_close_24,
"failed"
)
else -> null
}
itemView.setTag(R.string.replyable_message_view_tag, message.replyable) itemView.setTag(R.string.replyable_message_view_tag, message.replyable)
@ -129,29 +141,27 @@ class OutcomingTextMessageViewHolder(itemView: View) :
) )
} }
private fun setReadStatus(readStatus: Enum<ReadStatus>) { private fun updateReadStatus(readStatusDrawableInt: Int, description: String?) {
val readStatusDrawableInt = when (readStatus) { binding.sendingProgress.visibility = View.GONE
ReadStatus.READ -> R.drawable.ic_check_all binding.checkMark.visibility = View.VISIBLE
ReadStatus.SENT -> R.drawable.ic_check readStatusDrawableInt.let { drawableInt ->
else -> null
}
val readStatusContentDescriptionString = when (readStatus) {
ReadStatus.READ -> context.resources?.getString(R.string.nc_message_read)
ReadStatus.SENT -> context.resources?.getString(R.string.nc_message_sent)
else -> null
}
readStatusDrawableInt?.let { drawableInt ->
ResourcesCompat.getDrawable(context.resources, drawableInt, null)?.let { ResourcesCompat.getDrawable(context.resources, drawableInt, null)?.let {
binding.checkMark.setImageDrawable(it) binding.checkMark.setImageDrawable(it)
viewThemeUtils.talk.themeMessageCheckMark(binding.checkMark) viewThemeUtils.talk.themeMessageCheckMark(binding.checkMark)
} }
} }
binding.checkMark.contentDescription = description
binding.checkMark.contentDescription = readStatusContentDescriptionString
} }
private fun updateSendingStatus() {
binding.sendingProgress.visibility = View.VISIBLE
binding.checkMark.visibility = View.GONE
viewThemeUtils.material.colorProgressBar(binding.sendingProgress)
}
private fun longClickOnReaction(chatMessage: ChatMessage) { private fun longClickOnReaction(chatMessage: ChatMessage) {
commonMessageInterface.onLongClickReactions(chatMessage) commonMessageInterface.onLongClickReactions(chatMessage)
} }

View File

@ -587,7 +587,7 @@ class ChatActivity :
list.forEachIndexed { _, qMsg -> list.forEachIndexed { _, qMsg ->
val temporaryChatMessage = ChatMessage() val temporaryChatMessage = ChatMessage()
temporaryChatMessage.jsonMessageId = TEMPORARY_MESSAGE_ID_INT temporaryChatMessage.jsonMessageId = TEMPORARY_MESSAGE_ID_INT
temporaryChatMessage.actorId = "-3" temporaryChatMessage.actorId = TEMPORARY_MESSAGE_ID_STRING
temporaryChatMessage.timestamp = System.currentTimeMillis() / ONE_SECOND_IN_MILLIS temporaryChatMessage.timestamp = System.currentTimeMillis() / ONE_SECOND_IN_MILLIS
temporaryChatMessage.message = qMsg.message.toString() temporaryChatMessage.message = qMsg.message.toString()
temporaryChatMessage.tempMessageId = qMsg.id temporaryChatMessage.tempMessageId = qMsg.id
@ -813,6 +813,8 @@ class ChatActivity :
} }
is MessageInputViewModel.SendChatMessageErrorState -> { is MessageInputViewModel.SendChatMessageErrorState -> {
binding.messagesListView.smoothScrollToPosition(0)
// if (state.e is HttpException) { // if (state.e is HttpException) {
// val code = state.e.code() // val code = state.e.code()
// if (code.toString().startsWith("2")) { // if (code.toString().startsWith("2")) {
@ -2933,7 +2935,11 @@ class ChatActivity :
if (message.item is ChatMessage) { if (message.item is ChatMessage) {
val chatMessage = message.item as ChatMessage val chatMessage = message.item as ChatMessage
if (chatMessage.jsonMessageId <= xChatLastCommonRead) { if (chatMessage.sendingFailed) {
chatMessage.readStatus = ReadStatus.FAILED
} else if (chatMessage.isTempMessage) {
chatMessage.readStatus = ReadStatus.SENDING
} else if (chatMessage.jsonMessageId <= xChatLastCommonRead) {
chatMessage.readStatus = ReadStatus.READ chatMessage.readStatus = ReadStatus.READ
} else { } else {
chatMessage.readStatus = ReadStatus.SENT chatMessage.readStatus = ReadStatus.SENT
@ -3439,7 +3445,7 @@ class ChatActivity :
val message = iMessage as ChatMessage val message = iMessage as ChatMessage
if (hasVisibleItems(message) && if (hasVisibleItems(message) &&
!isSystemMessage(message) && !isSystemMessage(message) &&
message.id != "-3" message.id != TEMPORARY_MESSAGE_ID_STRING
) { ) {
MessageActionsDialog( MessageActionsDialog(
this, this,
@ -3863,7 +3869,7 @@ class ChatActivity :
CONTENT_TYPE_SYSTEM_MESSAGE -> !TextUtils.isEmpty(message.systemMessage) CONTENT_TYPE_SYSTEM_MESSAGE -> !TextUtils.isEmpty(message.systemMessage)
CONTENT_TYPE_UNREAD_NOTICE_MESSAGE -> message.id == UNREAD_MESSAGES_MARKER_ID.toString() CONTENT_TYPE_UNREAD_NOTICE_MESSAGE -> message.id == UNREAD_MESSAGES_MARKER_ID.toString()
CONTENT_TYPE_CALL_STARTED -> message.id == "-2" CONTENT_TYPE_CALL_STARTED -> message.id == "-2"
CONTENT_TYPE_TEMP -> message.id == "-3" CONTENT_TYPE_TEMP -> message.id == TEMPORARY_MESSAGE_ID_STRING
CONTENT_TYPE_DECK_CARD -> message.isDeckCard() CONTENT_TYPE_DECK_CARD -> message.isDeckCard()
else -> false else -> false

View File

@ -115,11 +115,13 @@ data class ChatMessage(
var openWhenDownloaded: Boolean = true, var openWhenDownloaded: Boolean = true,
var isTempMessage: Boolean = false, var isTempMessage: Boolean = false, // TODO: replace logic from message drafts with logic from temp message sending
var tempMessageId: Int = -1, var tempMessageId: Int = -1, // TODO: replace logic from message drafts with logic from temp message sending
var referenceId: String? = null var referenceId: String? = null,
var sendingFailed: Boolean = true
) : MessageContentType, ) : MessageContentType,
MessageContentType.Image { MessageContentType.Image {

View File

@ -186,7 +186,7 @@ class OfflineFirstChatRepository @Inject constructor(
) )
if (list.isNotEmpty()) { if (list.isNotEmpty()) {
updateUiMessages( updateUiMessages(
chatMessages = list, receivedChatMessages = list,
lookIntoFuture = false, lookIntoFuture = false,
showUnreadMessagesMarker = false showUnreadMessagesMarker = false
) )
@ -308,7 +308,7 @@ class OfflineFirstChatRepository @Inject constructor(
showUnreadMessagesMarker = showUnreadMessagesMarker && !weHaveMessagesFromOurself showUnreadMessagesMarker = showUnreadMessagesMarker && !weHaveMessagesFromOurself
updateUiMessages( updateUiMessages(
chatMessages = chatMessages, receivedChatMessages = chatMessages,
lookIntoFuture = true, lookIntoFuture = true,
showUnreadMessagesMarker = showUnreadMessagesMarker showUnreadMessagesMarker = showUnreadMessagesMarker
) )
@ -335,7 +335,7 @@ class OfflineFirstChatRepository @Inject constructor(
} }
private suspend fun updateUiMessages( private suspend fun updateUiMessages(
chatMessages : List<ChatMessage>, receivedChatMessages : List<ChatMessage>,
lookIntoFuture: Boolean, lookIntoFuture: Boolean,
showUnreadMessagesMarker: Boolean showUnreadMessagesMarker: Boolean
) { ) {
@ -346,11 +346,11 @@ class OfflineFirstChatRepository @Inject constructor(
oldTempMessages.forEach { _removeMessageFlow.emit(it) } oldTempMessages.forEach { _removeMessageFlow.emit(it) }
// add new messages to UI // add new messages to UI
val tripleChatMessages = Triple(lookIntoFuture, showUnreadMessagesMarker, chatMessages) val tripleChatMessages = Triple(lookIntoFuture, showUnreadMessagesMarker, receivedChatMessages)
_messageFlow.emit(tripleChatMessages) _messageFlow.emit(tripleChatMessages)
// remove temp messages from DB that are now found in the new messages // remove temp messages from DB that are now found in the new messages
val chatMessagesReferenceIds = chatMessages.mapTo(HashSet(chatMessages.size)) { it.referenceId } val chatMessagesReferenceIds = receivedChatMessages.mapTo(HashSet(receivedChatMessages.size)) { it.referenceId }
val tempChatMessagesThatCanBeReplaced = oldTempMessages.filter { it.referenceId in chatMessagesReferenceIds } val tempChatMessagesThatCanBeReplaced = oldTempMessages.filter { it.referenceId in chatMessagesReferenceIds }
chatDao.deleteTempChatMessages( chatDao.deleteTempChatMessages(
internalConversationId, internalConversationId,
@ -815,6 +815,17 @@ class OfflineFirstChatRepository @Inject constructor(
emit(Result.success(chatMessageModel)) emit(Result.success(chatMessageModel))
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Error when sending message", e) Log.e(TAG, "Error when sending message", e)
val failedMessage = chatDao.getTempMessageForConversation(internalConversationId, referenceId).first()
failedMessage.sendingFailed = true
chatDao.updateChatMessage(failedMessage)
val failedMessageModel = failedMessage.asModel()
_removeMessageFlow.emit(failedMessageModel)
val tripleChatMessages = Triple(true, false, listOf(failedMessageModel))
_messageFlow.emit(tripleChatMessages)
emit(Result.failure(e)) emit(Result.failure(e))
} }
} }
@ -877,7 +888,7 @@ class OfflineFirstChatRepository @Inject constructor(
internalId = internalConversationId + "@_temp_" + currentTimeMillies, internalId = internalConversationId + "@_temp_" + currentTimeMillies,
internalConversationId = internalConversationId, internalConversationId = internalConversationId,
id = currentTimeMillies, id = currentTimeMillies,
message = message + " (temp)", message = message,
deleted = false, deleted = false,
token = conversationModel.token, token = conversationModel.token,
actorId = currentUser.userId!!, actorId = currentUser.userId!!,

View File

@ -48,6 +48,18 @@ interface ChatMessagesDao {
) )
fun getTempMessagesForConversation(internalConversationId: String): Flow<List<ChatMessageEntity>> fun getTempMessagesForConversation(internalConversationId: String): Flow<List<ChatMessageEntity>>
@Query(
"""
SELECT *
FROM ChatMessages
WHERE internalConversationId = :internalConversationId
AND referenceId = :referenceId
AND isTemporary = 1
ORDER BY timestamp DESC, id DESC
"""
)
fun getTempMessageForConversation(internalConversationId: String, referenceId: String): Flow<ChatMessageEntity>
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsertChatMessages(chatMessages: List<ChatMessageEntity>) suspend fun upsertChatMessages(chatMessages: List<ChatMessageEntity>)

View File

@ -10,6 +10,7 @@ package com.nextcloud.talk.data.database.mappers
import com.nextcloud.talk.models.json.chat.ChatMessageJson import com.nextcloud.talk.models.json.chat.ChatMessageJson
import com.nextcloud.talk.data.database.model.ChatMessageEntity import com.nextcloud.talk.data.database.model.ChatMessageEntity
import com.nextcloud.talk.chat.data.model.ChatMessage import com.nextcloud.talk.chat.data.model.ChatMessage
import com.nextcloud.talk.models.json.chat.ReadStatus
fun ChatMessageJson.asEntity(accountId: Long) = fun ChatMessageJson.asEntity(accountId: Long) =
ChatMessageEntity( ChatMessageEntity(
@ -64,9 +65,22 @@ fun ChatMessageEntity.asModel() =
lastEditActorType = lastEditActorType, lastEditActorType = lastEditActorType,
lastEditTimestamp = lastEditTimestamp, lastEditTimestamp = lastEditTimestamp,
isDeleted = deleted, isDeleted = deleted,
referenceId = referenceId referenceId = referenceId,
isTempMessage = isTemporary,
sendingFailed = sendingFailed,
readStatus = setStatus(isTemporary, sendingFailed)
) )
fun setStatus(isTemporary: Boolean, sendingFailed: Boolean): ReadStatus {
return if (sendingFailed) {
ReadStatus.FAILED
} else if (isTemporary) {
ReadStatus.SENDING
} else {
ReadStatus.NONE
}
}
fun ChatMessageJson.asModel() = fun ChatMessageJson.asModel() =
ChatMessage( ChatMessage(
jsonMessageId = id.toInt(), jsonMessageId = id.toInt(),

View File

@ -9,5 +9,7 @@ package com.nextcloud.talk.models.json.chat
enum class ReadStatus { enum class ReadStatus {
NONE, NONE,
SENT, SENT,
READ READ,
SENDING,
FAILED
} }

View File

@ -84,6 +84,32 @@
app:layout_alignSelf="center" app:layout_alignSelf="center"
app:tint="@color/high_emphasis_text" /> app:tint="@color/high_emphasis_text" />
<ImageView
android:id="@+id/sending_failed"
android:layout_width="wrap_content"
android:layout_height="@dimen/message_bubble_checkmark_height"
android:layout_below="@id/messageTime"
android:layout_marginStart="8dp"
android:contentDescription="@null"
app:layout_alignSelf="center"
app:tint="@color/high_emphasis_text" />
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/sending_progress"
android:layout_width="wrap_content"
android:layout_height="@dimen/message_bubble_checkmark_height"
android:layout_below="@id/messageTime"
android:layout_marginStart="8dp"
android:contentDescription="@null"
android:indeterminate="true"
android:visibility="gone"
app:indicatorColor="@color/colorPrimary"
app:indicatorSize="14dp"
app:trackColor="@color/colorPrimary"
app:trackThickness="2dp"
tools:visibility="visible"
/>
<include <include
android:id="@+id/reactions" android:id="@+id/reactions"
layout="@layout/reactions_inside_message" /> layout="@layout/reactions_inside_message" />