Merge pull request #624 from hjubb/ui

Failed Message Handling
This commit is contained in:
Niels Andriesse 2021-07-01 13:26:34 +10:00 committed by GitHub
commit d22704e264
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 105 additions and 27 deletions

View File

@ -149,9 +149,5 @@ public class AlbumThumbnailView extends FrameLayout {
} }
private void setSlide(@NonNull GlideRequests glideRequests, @NonNull Slide slide, @IdRes int id) { private void setSlide(@NonNull GlideRequests glideRequests, @NonNull Slide slide, @IdRes int id) {
KThumbnailView cell = findViewById(id);
cell.setImageResource(glideRequests, slide, false);
cell.setThumbnailClickListener(defaultThumbnailClickListener);
cell.setOnLongClickListener(defaultLongClickListener);
} }
} }

View File

@ -50,13 +50,17 @@ import org.session.libsession.messaging.mentions.MentionsManager
import org.session.libsession.messaging.messages.control.DataExtractionNotification import org.session.libsession.messaging.messages.control.DataExtractionNotification
import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage
import org.session.libsession.messaging.messages.signal.OutgoingTextMessage import org.session.libsession.messaging.messages.signal.OutgoingTextMessage
import org.session.libsession.messaging.messages.visible.LinkPreview.Companion.from
import org.session.libsession.messaging.messages.visible.OpenGroupInvitation import org.session.libsession.messaging.messages.visible.OpenGroupInvitation
import org.session.libsession.messaging.messages.visible.Quote.Companion.from
import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.messages.visible.VisibleMessage
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2 import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.messaging.sending_receiving.attachments.Attachment import org.session.libsession.messaging.sending_receiving.attachments.Attachment
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel
import org.session.libsession.messaging.utilities.UpdateMessageData
import org.session.libsession.messaging.utilities.UpdateMessageData.Companion.fromJSON
import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address
import org.session.libsession.utilities.Address.Companion.fromSerialized import org.session.libsession.utilities.Address.Companion.fromSerialized
import org.session.libsession.utilities.MediaTypes import org.session.libsession.utilities.MediaTypes
@ -1143,7 +1147,48 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
} }
override fun resendMessage(messages: Set<MessageRecord>) { override fun resendMessage(messages: Set<MessageRecord>) {
// TODO: Implement messages.forEach { messageRecord ->
val recipient: Recipient = messageRecord.recipient
val message = VisibleMessage()
message.id = messageRecord.getId()
if (messageRecord.isOpenGroupInvitation) {
val openGroupInvitation = OpenGroupInvitation()
fromJSON(messageRecord.body)?.let { updateMessageData ->
val kind = updateMessageData.kind
if (kind is UpdateMessageData.Kind.OpenGroupInvitation) {
openGroupInvitation.name = kind.groupName
openGroupInvitation.url = kind.groupUrl
}
}
message.openGroupInvitation = openGroupInvitation
} else {
message.text = messageRecord.body
}
message.sentTimestamp = messageRecord.timestamp
if (recipient.isGroupRecipient) {
message.groupPublicKey = recipient.address.toGroupString()
} else {
message.recipient = messageRecord.recipient.address.serialize()
}
message.threadID = messageRecord.threadId
if (messageRecord.isMms) {
val mmsMessageRecord = messageRecord as MmsMessageRecord
if (mmsMessageRecord.linkPreviews.isNotEmpty()) {
message.linkPreview = from(mmsMessageRecord.linkPreviews[0])
}
if (mmsMessageRecord.quote != null) {
message.quote = from(mmsMessageRecord.quote!!.quoteModel)
}
message.addSignalAttachments(mmsMessageRecord.slideDeck.asAttachments())
}
val sentTimestamp = message.sentTimestamp
val sender = MessagingModuleConfiguration.shared.storage.getUserPublicKey()
if (sentTimestamp != null && sender != null) {
MessagingModuleConfiguration.shared.storage.markAsSending(sentTimestamp, sender)
}
MessageSender.send(message, recipient.address)
}
endActionMode()
} }
override fun saveAttachment(messages: Set<MessageRecord>) { override fun saveAttachment(messages: Set<MessageRecord>) {

View File

@ -115,7 +115,7 @@ class AlbumThumbnailView : FrameLayout {
// iterate binding // iterate binding
slides.take(5).forEachIndexed { position, slide -> slides.take(5).forEachIndexed { position, slide ->
val thumbnailView = getThumbnailView(position) val thumbnailView = getThumbnailView(position)
thumbnailView.setImageResource(glideRequests, slide, isPreview = false) thumbnailView.setImageResource(glideRequests, slide, isPreview = false, mms = message)
} }
albumCellBodyParent.isVisible = message.body.isNotEmpty() albumCellBodyParent.isVisible = message.body.isNotEmpty()
albumCellBodyText.text = message.body albumCellBodyText.text = message.body

View File

@ -48,7 +48,7 @@ class LinkPreviewView : LinearLayout {
// Thumbnail // Thumbnail
if (linkPreview.getThumbnail().isPresent) { if (linkPreview.getThumbnail().isPresent) {
// This internally fetches the thumbnail // This internally fetches the thumbnail
thumbnailImageView.setImageResource(glide, ImageSlide(context, linkPreview.getThumbnail().get()), isPreview = false) thumbnailImageView.setImageResource(glide, ImageSlide(context, linkPreview.getThumbnail().get()), isPreview = false, message)
thumbnailImageView.loadIndicator.isVisible = false thumbnailImageView.loadIndicator.isVisible = false
} }
// Title // Title

View File

@ -131,9 +131,13 @@ class VisibleMessageView : LinearLayout {
val gravity = if (message.isOutgoing) Gravity.END else Gravity.START val gravity = if (message.isOutgoing) Gravity.END else Gravity.START
mainContainer.gravity = gravity or Gravity.BOTTOM mainContainer.gravity = gravity or Gravity.BOTTOM
// Message status indicator // Message status indicator
val iconID = getMessageStatusImage(message) val (iconID, iconColor) = getMessageStatusImage(message)
if (iconID != null) { if (iconID != null) {
messageStatusImageView.setImageResource(iconID) val drawable = ContextCompat.getDrawable(context, iconID)?.mutate()
if (iconColor != null) {
drawable?.setTint(iconColor)
}
messageStatusImageView.setImageDrawable(drawable)
} }
if (message.isOutgoing) { if (message.isOutgoing) {
val lastMessageID = DatabaseFactory.getMmsSmsDatabase(context).getLastMessageID(message.threadId) val lastMessageID = DatabaseFactory.getMmsSmsDatabase(context).getLastMessageID(message.threadId)
@ -179,13 +183,13 @@ class VisibleMessageView : LinearLayout {
} }
} }
private fun getMessageStatusImage(message: MessageRecord): Int? { private fun getMessageStatusImage(message: MessageRecord): Pair<Int?,Int?> {
when { return when {
!message.isOutgoing -> return null !message.isOutgoing -> null to null
message.isFailed -> return R.drawable.ic_error message.isFailed -> R.drawable.ic_error to resources.getColor(R.color.destructive, context.theme)
message.isPending -> return R.drawable.ic_circle_dot_dot_dot message.isPending -> R.drawable.ic_circle_dot_dot_dot to null
message.isRead -> return R.drawable.ic_filled_circle_check message.isRead -> R.drawable.ic_filled_circle_check to null
else -> return R.drawable.ic_circle_check else -> R.drawable.ic_circle_check to null
} }
} }

View File

@ -21,9 +21,9 @@ import org.session.libsignal.utilities.ListenableFuture
import org.session.libsignal.utilities.SettableFuture import org.session.libsignal.utilities.SettableFuture
import org.thoughtcrime.securesms.components.GlideBitmapListeningTarget import org.thoughtcrime.securesms.components.GlideBitmapListeningTarget
import org.thoughtcrime.securesms.components.GlideDrawableListeningTarget import org.thoughtcrime.securesms.components.GlideDrawableListeningTarget
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.mms.* import org.thoughtcrime.securesms.mms.*
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri
import org.thoughtcrime.securesms.util.MediaUtil
open class KThumbnailView: FrameLayout { open class KThumbnailView: FrameLayout {
@ -43,8 +43,6 @@ open class KThumbnailView: FrameLayout {
private val dimensDelegate = ThumbnailDimensDelegate() private val dimensDelegate = ThumbnailDimensDelegate()
var thumbnailClickListener: SlideClickListener? = null
private var slide: Slide? = null private var slide: Slide? = null
private var radius: Int = 0 private var radius: Int = 0
@ -84,13 +82,13 @@ open class KThumbnailView: FrameLayout {
// endregion // endregion
// region Interaction // region Interaction
fun setImageResource(glide: GlideRequests, slide: Slide, isPreview: Boolean): ListenableFuture<Boolean> { fun setImageResource(glide: GlideRequests, slide: Slide, isPreview: Boolean, mms: MmsMessageRecord): ListenableFuture<Boolean> {
return setImageResource(glide, slide, isPreview, 0, 0) return setImageResource(glide, slide, isPreview, 0, 0, mms)
} }
fun setImageResource(glide: GlideRequests, slide: Slide, fun setImageResource(glide: GlideRequests, slide: Slide,
isPreview: Boolean, naturalWidth: Int, isPreview: Boolean, naturalWidth: Int,
naturalHeight: Int): ListenableFuture<Boolean> { naturalHeight: Int, mms: MmsMessageRecord): ListenableFuture<Boolean> {
val currentSlide = this.slide val currentSlide = this.slide
@ -110,7 +108,7 @@ open class KThumbnailView: FrameLayout {
this.slide = slide this.slide = slide
loadIndicator.isVisible = slide.isInProgress loadIndicator.isVisible = slide.isInProgress && !mms.isFailed
dimensDelegate.setDimens(naturalWidth, naturalHeight) dimensDelegate.setDimens(naturalWidth, naturalHeight)
invalidate() invalidate()

View File

@ -27,6 +27,7 @@ import org.session.libsignal.utilities.KeyHelper
import org.session.libsignal.utilities.guava.Optional import org.session.libsignal.utilities.guava.Optional
import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob
import org.thoughtcrime.securesms.loki.api.OpenGroupManager import org.thoughtcrime.securesms.loki.api.OpenGroupManager
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase
@ -303,6 +304,19 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
} }
} }
override fun markAsSending(timestamp: Long, author: String) {
val database = DatabaseFactory.getMmsSmsDatabase(context)
val messageRecord = database.getMessageFor(timestamp, author) ?: return
if (messageRecord.isMms) {
val mmsDatabase = DatabaseFactory.getMmsDatabase(context)
mmsDatabase.markAsSending(messageRecord.getId())
} else {
val smsDatabase = DatabaseFactory.getSmsDatabase(context)
smsDatabase.markAsSending(messageRecord.getId())
messageRecord.isPending
}
}
override fun markUnidentified(timestamp: Long, author: String) { override fun markUnidentified(timestamp: Long, author: String) {
val database = DatabaseFactory.getMmsSmsDatabase(context) val database = DatabaseFactory.getMmsSmsDatabase(context)
val messageRecord = database.getMessageFor(timestamp, author) ?: return val messageRecord = database.getMessageFor(timestamp, author) ?: return

View File

@ -8,6 +8,7 @@ import android.util.TypedValue
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.view_conversation.view.* import kotlinx.android.synthetic.main.view_conversation.view.*
@ -73,7 +74,11 @@ class ConversationView : LinearLayout {
statusIndicatorImageView.visibility = View.VISIBLE statusIndicatorImageView.visibility = View.VISIBLE
when { when {
!thread.isOutgoing -> statusIndicatorImageView.visibility = View.GONE !thread.isOutgoing -> statusIndicatorImageView.visibility = View.GONE
thread.isFailed -> statusIndicatorImageView.setImageResource(R.drawable.ic_error) thread.isFailed -> {
val drawable = ContextCompat.getDrawable(context, R.drawable.ic_error)?.mutate()
drawable?.setTint(ContextCompat.getColor(context,R.color.destructive))
statusIndicatorImageView.setImageDrawable(drawable)
}
thread.isPending -> statusIndicatorImageView.setImageResource(R.drawable.ic_circle_dot_dot_dot) thread.isPending -> statusIndicatorImageView.setImageResource(R.drawable.ic_circle_dot_dot_dot)
thread.isRead -> statusIndicatorImageView.setImageResource(R.drawable.ic_filled_circle_check) thread.isRead -> statusIndicatorImageView.setImageResource(R.drawable.ic_filled_circle_check)
else -> statusIndicatorImageView.setImageResource(R.drawable.ic_circle_check) else -> statusIndicatorImageView.setImageResource(R.drawable.ic_circle_check)

View File

@ -89,6 +89,7 @@ interface StorageProtocol {
fun persistAttachments(messageID: Long, attachments: List<Attachment>): List<Long> fun persistAttachments(messageID: Long, attachments: List<Attachment>): List<Long>
fun getAttachmentsForMessage(messageID: Long): List<DatabaseAttachment> fun getAttachmentsForMessage(messageID: Long): List<DatabaseAttachment>
fun getMessageIdInDatabase(timestamp: Long, author: String): Long? // TODO: This is a weird name fun getMessageIdInDatabase(timestamp: Long, author: String): Long? // TODO: This is a weird name
fun markAsSending(timestamp: Long, author: String)
fun markAsSent(timestamp: Long, author: String) fun markAsSent(timestamp: Long, author: String)
fun markUnidentified(timestamp: Long, author: String) fun markUnidentified(timestamp: Long, author: String)
fun setErrorMessage(timestamp: Long, author: String, error: Exception) fun setErrorMessage(timestamp: Long, author: String, error: Exception)

View File

@ -122,7 +122,7 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
private fun handleFailure(e: Exception) { private fun handleFailure(e: Exception) {
Log.w(TAG, "Attachment upload failed due to error: $this.") Log.w(TAG, "Attachment upload failed due to error: $this.")
delegate?.handleJobFailed(this, e) delegate?.handleJobFailed(this, e)
if (failureCount + 1 == maxFailureCount) { if (failureCount + 1 >= maxFailureCount) {
failAssociatedMessageSendJob(e) failAssociatedMessageSendJob(e)
} }
} }

View File

@ -3,6 +3,8 @@ package org.session.libsession.messaging.jobs
import com.esotericsoftware.kryo.Kryo import com.esotericsoftware.kryo.Kryo
import com.esotericsoftware.kryo.io.Input import com.esotericsoftware.kryo.io.Input
import com.esotericsoftware.kryo.io.Output import com.esotericsoftware.kryo.io.Output
import nl.komponents.kovenant.FailedException
import nl.komponents.kovenant.Promise
import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.jobs.Job.Companion.MAX_BUFFER_SIZE import org.session.libsession.messaging.jobs.Job.Companion.MAX_BUFFER_SIZE
import org.session.libsession.messaging.messages.Destination import org.session.libsession.messaging.messages.Destination
@ -34,6 +36,14 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job {
override fun execute() { override fun execute() {
val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider
val message = message as? VisibleMessage val message = message as? VisibleMessage
val storage = MessagingModuleConfiguration.shared.storage
val sentTimestamp = this.message.sentTimestamp
val sender = storage.getUserPublicKey()
if (sentTimestamp != null && sender != null) {
storage.markAsSending(sentTimestamp, sender)
}
if (message != null) { if (message != null) {
if (!messageDataProvider.isOutgoingMessage(message.sentTimestamp!!)) return // The message has been deleted if (!messageDataProvider.isOutgoingMessage(message.sentTimestamp!!)) return // The message has been deleted
val attachmentIDs = mutableListOf<Long>() val attachmentIDs = mutableListOf<Long>()
@ -43,7 +53,7 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job {
val attachments = attachmentIDs.mapNotNull { messageDataProvider.getDatabaseAttachment(it) } val attachments = attachmentIDs.mapNotNull { messageDataProvider.getDatabaseAttachment(it) }
val attachmentsToUpload = attachments.filter { it.url.isNullOrEmpty() } val attachmentsToUpload = attachments.filter { it.url.isNullOrEmpty() }
attachmentsToUpload.forEach { attachmentsToUpload.forEach {
if (MessagingModuleConfiguration.shared.storage.getAttachmentUploadJob(it.attachmentId.rowId) != null) { if (storage.getAttachmentUploadJob(it.attachmentId.rowId) != null) {
// Wait for it to finish // Wait for it to finish
} else { } else {
val job = AttachmentUploadJob(it.attachmentId.rowId, message.threadID!!.toString(), message, id!!) val job = AttachmentUploadJob(it.attachmentId.rowId, message.threadID!!.toString(), message, id!!)
@ -55,7 +65,7 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job {
return return
} // Wait for all attachments to upload before continuing } // Wait for all attachments to upload before continuing
} }
MessageSender.send(this.message, this.destination).success { val promise = MessageSender.send(this.message, this.destination).success {
this.handleSuccess() this.handleSuccess()
}.fail { exception -> }.fail { exception ->
Log.e(TAG, "Couldn't send message due to error: $exception.") Log.e(TAG, "Couldn't send message due to error: $exception.")
@ -64,6 +74,11 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job {
} }
this.handleFailure(exception) this.handleFailure(exception)
} }
try {
promise.get()
} catch (e: Exception) {
Log.d(TAG, "Promise failed to resolve successfully", e)
}
} }
private fun handleSuccess() { private fun handleSuccess() {