Add sync status message

This commit is contained in:
andrew 2023-05-04 14:50:51 +09:30
parent 99cb10f5be
commit 45eb3549f6
18 changed files with 248 additions and 63 deletions

View File

@ -1755,6 +1755,13 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
endActionMode() endActionMode()
} }
override fun resyncMessage(messages: Set<MessageRecord>) {
messages.iterator().forEach { messageRecord ->
ResendMessageUtilities.resend(this, messageRecord, viewModel.blindedPublicKey, isResync = true)
}
endActionMode()
}
override fun resendMessage(messages: Set<MessageRecord>) { override fun resendMessage(messages: Set<MessageRecord>) {
messages.iterator().forEach { messageRecord -> messages.iterator().forEach { messageRecord ->
ResendMessageUtilities.resend(this, messageRecord, viewModel.blindedPublicKey) ResendMessageUtilities.resend(this, messageRecord, viewModel.blindedPublicKey)
@ -1915,6 +1922,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
val selectedItems = setOf(message) val selectedItems = setOf(message)
when (action) { when (action) {
ConversationReactionOverlay.Action.REPLY -> reply(selectedItems) ConversationReactionOverlay.Action.REPLY -> reply(selectedItems)
ConversationReactionOverlay.Action.RESYNC -> resyncMessage(selectedItems)
ConversationReactionOverlay.Action.RESEND -> resendMessage(selectedItems) ConversationReactionOverlay.Action.RESEND -> resendMessage(selectedItems)
ConversationReactionOverlay.Action.DOWNLOAD -> saveAttachment(selectedItems) ConversationReactionOverlay.Action.DOWNLOAD -> saveAttachment(selectedItems)
ConversationReactionOverlay.Action.COPY_MESSAGE -> copyMessages(selectedItems) ConversationReactionOverlay.Action.COPY_MESSAGE -> copyMessages(selectedItems)

View File

@ -700,6 +700,10 @@ public final class ConversationReactionOverlay extends FrameLayout {
if (message.isFailed()) { if (message.isFailed()) {
items.add(new ActionItem(R.attr.menu_reply_icon, getContext().getResources().getString(R.string.conversation_context__menu_resend_message), () -> handleActionItemClicked(Action.RESEND))); items.add(new ActionItem(R.attr.menu_reply_icon, getContext().getResources().getString(R.string.conversation_context__menu_resend_message), () -> handleActionItemClicked(Action.RESEND)));
} }
// Resync
if (message.isSyncFailed()) {
items.add(new ActionItem(R.attr.menu_reply_icon, getContext().getResources().getString(R.string.conversation_context__menu_resync_message), () -> handleActionItemClicked(Action.RESYNC)));
}
// Save media // Save media
if (message.isMms() && ((MediaMmsMessageRecord)message).containsMediaSlide()) { if (message.isMms() && ((MediaMmsMessageRecord)message).containsMediaSlide()) {
items.add(new ActionItem(R.attr.menu_save_icon, getContext().getResources().getString(R.string.conversation_context_image__save_attachment), () -> handleActionItemClicked(Action.DOWNLOAD), items.add(new ActionItem(R.attr.menu_save_icon, getContext().getResources().getString(R.string.conversation_context_image__save_attachment), () -> handleActionItemClicked(Action.DOWNLOAD),
@ -885,6 +889,7 @@ public final class ConversationReactionOverlay extends FrameLayout {
public enum Action { public enum Action {
REPLY, REPLY,
RESEND, RESEND,
RESYNC,
DOWNLOAD, DOWNLOAD,
COPY_MESSAGE, COPY_MESSAGE,
COPY_SESSION_ID, COPY_SESSION_ID,

View File

@ -70,6 +70,8 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p
menu.findItem(R.id.menu_message_details).isVisible = (selectedItems.size == 1 && firstMessage.isOutgoing) menu.findItem(R.id.menu_message_details).isVisible = (selectedItems.size == 1 && firstMessage.isOutgoing)
// Resend // Resend
menu.findItem(R.id.menu_context_resend).isVisible = (selectedItems.size == 1 && firstMessage.isFailed) menu.findItem(R.id.menu_context_resend).isVisible = (selectedItems.size == 1 && firstMessage.isFailed)
// Resync
menu.findItem(R.id.menu_context_resync).isVisible = (selectedItems.size == 1 && firstMessage.isSyncFailed)
// Save media // Save media
menu.findItem(R.id.menu_context_save_attachment).isVisible = (selectedItems.size == 1 menu.findItem(R.id.menu_context_save_attachment).isVisible = (selectedItems.size == 1
&& firstMessage.isMms && (firstMessage as MediaMmsMessageRecord).containsMediaSlide()) && firstMessage.isMms && (firstMessage as MediaMmsMessageRecord).containsMediaSlide())
@ -90,6 +92,7 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p
R.id.menu_context_ban_and_delete_all -> delegate?.banAndDeleteAll(selectedItems) R.id.menu_context_ban_and_delete_all -> delegate?.banAndDeleteAll(selectedItems)
R.id.menu_context_copy -> delegate?.copyMessages(selectedItems) R.id.menu_context_copy -> delegate?.copyMessages(selectedItems)
R.id.menu_context_copy_public_key -> delegate?.copySessionID(selectedItems) R.id.menu_context_copy_public_key -> delegate?.copySessionID(selectedItems)
R.id.menu_context_resync -> delegate?.resyncMessage(selectedItems)
R.id.menu_context_resend -> delegate?.resendMessage(selectedItems) R.id.menu_context_resend -> delegate?.resendMessage(selectedItems)
R.id.menu_message_details -> delegate?.showMessageDetail(selectedItems) R.id.menu_message_details -> delegate?.showMessageDetail(selectedItems)
R.id.menu_context_save_attachment -> delegate?.saveAttachment(selectedItems) R.id.menu_context_save_attachment -> delegate?.saveAttachment(selectedItems)
@ -113,6 +116,7 @@ interface ConversationActionModeCallbackDelegate {
fun banAndDeleteAll(messages: Set<MessageRecord>) fun banAndDeleteAll(messages: Set<MessageRecord>)
fun copyMessages(messages: Set<MessageRecord>) fun copyMessages(messages: Set<MessageRecord>)
fun copySessionID(messages: Set<MessageRecord>) fun copySessionID(messages: Set<MessageRecord>)
fun resyncMessage(messages: Set<MessageRecord>)
fun resendMessage(messages: Set<MessageRecord>) fun resendMessage(messages: Set<MessageRecord>)
fun showMessageDetail(messages: Set<MessageRecord>) fun showMessageDetail(messages: Set<MessageRecord>)
fun saveAttachment(messages: Set<MessageRecord>) fun saveAttachment(messages: Set<MessageRecord>)

View File

@ -292,12 +292,7 @@ class VisibleMessageView : LinearLayout {
@StringRes val messageText: Int?, @StringRes val messageText: Int?,
val contentDescription: String?) val contentDescription: String?)
private fun getMessageStatusImage(message: MessageRecord): MessageStatusInfo { private fun getMessageStatusImage(message: MessageRecord): MessageStatusInfo = when {
return when {
!message.isOutgoing -> MessageStatusInfo(null,
null,
null,
null)
message.isFailed -> message.isFailed ->
MessageStatusInfo( MessageStatusInfo(
R.drawable.ic_delivery_status_failed, R.drawable.ic_delivery_status_failed,
@ -305,12 +300,25 @@ class VisibleMessageView : LinearLayout {
R.string.delivery_status_failed, R.string.delivery_status_failed,
null null
) )
message.isSyncFailed ->
MessageStatusInfo(
R.drawable.ic_delivery_status_failed,
resources.getColor(R.color.destructive, context.theme),
R.string.delivery_status_sync_failed,
null
)
message.isPending -> message.isPending ->
MessageStatusInfo( MessageStatusInfo(
R.drawable.ic_delivery_status_sending, R.drawable.ic_delivery_status_sending,
context.getColorFromAttr(R.attr.message_status_color), R.string.delivery_status_sending, context.getColorFromAttr(R.attr.message_status_color), R.string.delivery_status_sending,
context.getString(R.string.AccessibilityId_message_sent_status_pending) context.getString(R.string.AccessibilityId_message_sent_status_pending)
) )
message.isResyncing ->
MessageStatusInfo(
R.drawable.ic_delivery_status_sending,
context.getColorFromAttr(R.attr.message_status_color), R.string.delivery_status_syncing,
context.getString(R.string.AccessibilityId_message_sent_status_syncing)
)
message.isRead -> message.isRead ->
MessageStatusInfo( MessageStatusInfo(
R.drawable.ic_delivery_status_read, R.drawable.ic_delivery_status_read,
@ -325,7 +333,6 @@ class VisibleMessageView : LinearLayout {
context.getString(R.string.AccessibilityId_message_sent_status_tick) context.getString(R.string.AccessibilityId_message_sent_status_tick)
) )
} }
}
private fun updateExpirationTimer(message: MessageRecord) { private fun updateExpirationTimer(message: MessageRecord) {
val container = binding.messageInnerContainer val container = binding.messageInnerContainer

View File

@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.conversation.v2.utilities
import android.content.Context import android.content.Context
import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.messages.Destination
import org.session.libsession.messaging.messages.visible.LinkPreview import org.session.libsession.messaging.messages.visible.LinkPreview
import org.session.libsession.messaging.messages.visible.OpenGroupInvitation import org.session.libsession.messaging.messages.visible.OpenGroupInvitation
import org.session.libsession.messaging.messages.visible.Quote import org.session.libsession.messaging.messages.visible.Quote
@ -10,12 +11,16 @@ import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.messaging.utilities.UpdateMessageData import org.session.libsession.messaging.utilities.UpdateMessageData
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord
private val TAG = ResendMessageUtilities.javaClass.simpleName
object ResendMessageUtilities { object ResendMessageUtilities {
fun resend(context: Context, messageRecord: MessageRecord, userBlindedKey: String?) { fun resend(context: Context, messageRecord: MessageRecord, userBlindedKey: String?, isResync: Boolean = false) {
Log.d(TAG, "resend() called with: context = $context, messageRecord = $messageRecord, userBlindedKey = $userBlindedKey, isResync = $isResync")
val recipient: Recipient = messageRecord.recipient val recipient: Recipient = messageRecord.recipient
val message = VisibleMessage() val message = VisibleMessage()
message.id = messageRecord.getId() message.id = messageRecord.getId()
@ -55,8 +60,13 @@ object ResendMessageUtilities {
val sentTimestamp = message.sentTimestamp val sentTimestamp = message.sentTimestamp
val sender = MessagingModuleConfiguration.shared.storage.getUserPublicKey() val sender = MessagingModuleConfiguration.shared.storage.getUserPublicKey()
if (sentTimestamp != null && sender != null) { if (sentTimestamp != null && sender != null) {
if (isResync) {
MessagingModuleConfiguration.shared.storage.markAsResyncing(sentTimestamp, sender)
MessageSender.send(message, Destination.from(recipient.address), isSyncMessage = true)
} else {
MessagingModuleConfiguration.shared.storage.markAsSending(sentTimestamp, sender) MessagingModuleConfiguration.shared.storage.markAsSending(sentTimestamp, sender)
}
MessageSender.send(message, recipient.address) MessageSender.send(message, recipient.address)
} }
}
}
} }

View File

@ -37,6 +37,13 @@ public abstract class MessagingDatabase extends Database implements MmsSmsColumn
public abstract void markExpireStarted(long messageId, long startTime); public abstract void markExpireStarted(long messageId, long startTime);
public abstract void markAsSent(long messageId, boolean secure); public abstract void markAsSent(long messageId, boolean secure);
public abstract void markAsSyncing(long id);
public abstract void markAsResyncing(long id);
public abstract void markAsSyncFailed(long id);
public abstract void markUnidentified(long messageId, boolean unidentified); public abstract void markUnidentified(long messageId, boolean unidentified);
public abstract void markAsDeleted(long messageId, boolean read, boolean hasMention); public abstract void markAsDeleted(long messageId, boolean read, boolean hasMention);
@ -199,7 +206,6 @@ public abstract class MessagingDatabase extends Database implements MmsSmsColumn
contentValues.put(THREAD_ID, newThreadId); contentValues.put(THREAD_ID, newThreadId);
db.update(getTableName(), contentValues, where, args); db.update(getTableName(), contentValues, where, args);
} }
public static class SyncMessageId { public static class SyncMessageId {
private final Address address; private final Address address;

View File

@ -276,6 +276,16 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
notifyConversationListeners(threadId) notifyConversationListeners(threadId)
} }
override fun markAsSyncing(messageId: Long) {
markAs(messageId, MmsSmsColumns.Types.BASE_SYNCING_TYPE)
}
override fun markAsResyncing(messageId: Long) {
markAs(messageId, MmsSmsColumns.Types.BASE_RESYNCING_TYPE)
}
override fun markAsSyncFailed(messageId: Long) {
markAs(messageId, MmsSmsColumns.Types.BASE_SYNC_FAILED_TYPE)
}
fun markAsSending(messageId: Long) { fun markAsSending(messageId: Long) {
markAs(messageId, MmsSmsColumns.Types.BASE_SENDING_TYPE) markAs(messageId, MmsSmsColumns.Types.BASE_SENDING_TYPE)
} }

View File

@ -47,8 +47,13 @@ public interface MmsSmsColumns {
protected static final long BASE_PENDING_INSECURE_SMS_FALLBACK = 26; protected static final long BASE_PENDING_INSECURE_SMS_FALLBACK = 26;
public static final long BASE_DRAFT_TYPE = 27; public static final long BASE_DRAFT_TYPE = 27;
protected static final long BASE_DELETED_TYPE = 28; protected static final long BASE_DELETED_TYPE = 28;
protected static final long BASE_SYNCING_TYPE = 29;
protected static final long BASE_RESYNCING_TYPE = 30;
protected static final long BASE_SYNC_FAILED_TYPE = 31;
protected static final long[] OUTGOING_MESSAGE_TYPES = {BASE_OUTBOX_TYPE, BASE_SENT_TYPE, protected static final long[] OUTGOING_MESSAGE_TYPES = {BASE_OUTBOX_TYPE, BASE_SENT_TYPE,
BASE_SYNCING_TYPE, BASE_RESYNCING_TYPE,
BASE_SYNC_FAILED_TYPE,
BASE_SENDING_TYPE, BASE_SENT_FAILED_TYPE, BASE_SENDING_TYPE, BASE_SENT_FAILED_TYPE,
BASE_PENDING_SECURE_SMS_FALLBACK, BASE_PENDING_SECURE_SMS_FALLBACK,
BASE_PENDING_INSECURE_SMS_FALLBACK, BASE_PENDING_INSECURE_SMS_FALLBACK,
@ -109,6 +114,18 @@ public interface MmsSmsColumns {
return (type & BASE_TYPE_MASK) == BASE_DRAFT_TYPE; return (type & BASE_TYPE_MASK) == BASE_DRAFT_TYPE;
} }
public static boolean isResyncingType(long type) {
return (type & BASE_TYPE_MASK) == BASE_RESYNCING_TYPE;
}
public static boolean isSyncingType(long type) {
return (type & BASE_TYPE_MASK) == BASE_SYNCING_TYPE;
}
public static boolean isSyncFailedMessageType(long type) {
return (type & BASE_TYPE_MASK) == BASE_SYNC_FAILED_TYPE;
}
public static boolean isFailedMessageType(long type) { public static boolean isFailedMessageType(long type) {
return (type & BASE_TYPE_MASK) == BASE_SENT_FAILED_TYPE; return (type & BASE_TYPE_MASK) == BASE_SENT_FAILED_TYPE;
} }

View File

@ -202,6 +202,21 @@ public class SmsDatabase extends MessagingDatabase {
updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_SENDING_TYPE); updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_SENDING_TYPE);
} }
@Override
public void markAsSyncing(long id) {
updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_SYNCING_TYPE);
}
@Override
public void markAsResyncing(long id) {
updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_RESYNCING_TYPE);
}
@Override
public void markAsSyncFailed(long id) {
updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_SYNC_FAILED_TYPE);
}
@Override @Override
public void markUnidentified(long id, boolean unidentified) { public void markUnidentified(long id, boolean unidentified) {
ContentValues contentValues = new ContentValues(1); ContentValues contentValues = new ContentValues(1);

View File

@ -41,6 +41,7 @@ import org.session.libsignal.messages.SignalServiceAttachmentPointer
import org.session.libsignal.messages.SignalServiceGroup import org.session.libsignal.messages.SignalServiceGroup
import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.IdPrefix
import org.session.libsignal.utilities.KeyHelper import org.session.libsignal.utilities.KeyHelper
import org.session.libsignal.utilities.Log
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
@ -53,6 +54,8 @@ import org.thoughtcrime.securesms.mms.PartAuthority
import org.thoughtcrime.securesms.util.SessionMetaProtocol import org.thoughtcrime.securesms.util.SessionMetaProtocol
import java.security.MessageDigest import java.security.MessageDigest
private val TAG = Storage::class.java.simpleName
class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), StorageProtocol { class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), StorageProtocol {
override fun getUserPublicKey(): String? { override fun getUserPublicKey(): String? {
@ -356,6 +359,8 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
openGroupSentTimestamp: Long, openGroupSentTimestamp: Long,
threadId: Long threadId: Long
) { ) {
Log.d(TAG, "updateSentTimestamp() called with: messageID = $messageID, isMms = $isMms, openGroupSentTimestamp = $openGroupSentTimestamp, threadId = $threadId")
if (isMms) { if (isMms) {
val mmsDb = DatabaseComponent.get(context).mmsDatabase() val mmsDb = DatabaseComponent.get(context).mmsDatabase()
mmsDb.updateSentTimestamp(messageID, openGroupSentTimestamp, threadId) mmsDb.updateSentTimestamp(messageID, openGroupSentTimestamp, threadId)
@ -366,6 +371,8 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
} }
override fun markAsSent(timestamp: Long, author: String) { override fun markAsSent(timestamp: Long, author: String) {
Log.d(TAG, "markAsSent() called with: timestamp = $timestamp, author = $author")
val database = DatabaseComponent.get(context).mmsSmsDatabase() val database = DatabaseComponent.get(context).mmsSmsDatabase()
val messageRecord = database.getMessageFor(timestamp, author) ?: return val messageRecord = database.getMessageFor(timestamp, author) ?: return
if (messageRecord.isMms) { if (messageRecord.isMms) {
@ -377,7 +384,29 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
} }
} }
override fun markAsSyncing(timestamp: Long, author: String) {
Log.d(TAG, "markAsSyncing() called with: timestamp = $timestamp, author = $author")
DatabaseComponent.get(context).mmsSmsDatabase()
.getMessageFor(timestamp, author)
?.run { getMmsDatabaseElseSms(isMms).markAsSyncing(id) }
}
private fun getMmsDatabaseElseSms(isMms: Boolean) =
if (isMms) DatabaseComponent.get(context).mmsDatabase()
else DatabaseComponent.get(context).smsDatabase()
override fun markAsResyncing(timestamp: Long, author: String) {
Log.d(TAG, "markAsResyncing() called with: timestamp = $timestamp, author = $author")
DatabaseComponent.get(context).mmsSmsDatabase()
.getMessageFor(timestamp, author)
?.run { getMmsDatabaseElseSms(isMms).markAsResyncing(id) }
}
override fun markAsSending(timestamp: Long, author: String) { override fun markAsSending(timestamp: Long, author: String) {
Log.d(TAG, "markAsSending() called with: timestamp = $timestamp, author = $author")
val database = DatabaseComponent.get(context).mmsSmsDatabase() val database = DatabaseComponent.get(context).mmsSmsDatabase()
val messageRecord = database.getMessageFor(timestamp, author) ?: return val messageRecord = database.getMessageFor(timestamp, author) ?: return
if (messageRecord.isMms) { if (messageRecord.isMms) {
@ -402,7 +431,9 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
} }
} }
override fun setErrorMessage(timestamp: Long, author: String, error: Exception) { override fun markAsSentFailed(timestamp: Long, author: String, error: Exception) {
Log.d(TAG, "markAsSentFailed() called with: timestamp = $timestamp, author = $author, error = $error")
val database = DatabaseComponent.get(context).mmsSmsDatabase() val database = DatabaseComponent.get(context).mmsSmsDatabase()
val messageRecord = database.getMessageFor(timestamp, author) ?: return val messageRecord = database.getMessageFor(timestamp, author) ?: return
if (messageRecord.isMms) { if (messageRecord.isMms) {
@ -425,6 +456,28 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
} }
} }
override fun markAsSyncFailed(timestamp: Long, author: String, error: Exception) {
Log.d(TAG, "markAsSyncFailed() called with: timestamp = $timestamp, author = $author, error = $error")
val database = DatabaseComponent.get(context).mmsSmsDatabase()
val messageRecord = database.getMessageFor(timestamp, author) ?: return
database.getMessageFor(timestamp, author)
?.run { getMmsDatabaseElseSms(isMms).markAsSyncFailed(id) }
if (error.localizedMessage != null) {
val message: String
if (error is OnionRequestAPI.HTTPRequestFailedAtDestinationException && error.statusCode == 429) {
message = "429: Rate limited."
} else {
message = error.localizedMessage!!
}
DatabaseComponent.get(context).lokiMessageDatabase().setErrorMessage(messageRecord.getId(), message)
} else {
DatabaseComponent.get(context).lokiMessageDatabase().setErrorMessage(messageRecord.getId(), error.javaClass.simpleName)
}
}
override fun clearErrorMessage(messageID: Long) { override fun clearErrorMessage(messageID: Long) {
val db = DatabaseComponent.get(context).lokiMessageDatabase() val db = DatabaseComponent.get(context).lokiMessageDatabase()
db.clearErrorMessage(messageID) db.clearErrorMessage(messageID)
@ -983,5 +1036,4 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
val recipientDb = DatabaseComponent.get(context).recipientDatabase() val recipientDb = DatabaseComponent.get(context).recipientDatabase()
return recipientDb.blockedContacts return recipientDb.blockedContacts
} }
} }

View File

@ -80,6 +80,18 @@ public abstract class DisplayRecord {
return !isFailed() && !isPending(); return !isFailed() && !isPending();
} }
public boolean isSyncing() {
return MmsSmsColumns.Types.isSyncingType(type);
}
public boolean isResyncing() {
return MmsSmsColumns.Types.isResyncingType(type);
}
public boolean isSyncFailed() {
return MmsSmsColumns.Types.isSyncFailedMessageType(type);
}
public boolean isFailed() { public boolean isFailed() {
return MmsSmsColumns.Types.isFailedMessageType(type) return MmsSmsColumns.Types.isFailedMessageType(type)
|| MmsSmsColumns.Types.isPendingSecureSmsFallbackType(type) || MmsSmsColumns.Types.isPendingSecureSmsFallbackType(type)

View File

@ -32,6 +32,11 @@
android:icon="?menu_copy_icon" android:icon="?menu_copy_icon"
app:showAsAction="always" /> app:showAsAction="always" />
<item
android:title="@string/conversation_context__menu_resync_message"
android:id="@+id/menu_context_resync"
app:showAsAction="never" />
<item <item
android:title="@string/conversation_context__menu_resend_message" android:title="@string/conversation_context__menu_resend_message"
android:id="@+id/menu_context_resend" android:id="@+id/menu_context_resend"

View File

@ -112,6 +112,7 @@
<!-- Conversation View--> <!-- Conversation View-->
<string name="AccessibilityId_message_sent_status_tick">Message sent status: Sent</string> <string name="AccessibilityId_message_sent_status_tick">Message sent status: Sent</string>
<string name="AccessibilityId_message_sent_status_pending">Message sent status pending</string> <string name="AccessibilityId_message_sent_status_pending">Message sent status pending</string>
<string name="AccessibilityId_message_sent_status_syncing">Message sent status syncing</string>
<string name="AccessibilityId_message_request_config_message">Message request has been accepted</string> <string name="AccessibilityId_message_request_config_message">Message request has been accepted</string>
<string name="AccessibilityId_message_body">Message Body</string> <string name="AccessibilityId_message_body">Message Body</string>
<string name="AccessibilityId_voice_message">Voice message</string> <string name="AccessibilityId_voice_message">Voice message</string>
@ -627,6 +628,7 @@
<string name="conversation_context__menu_delete_message">Delete message</string> <string name="conversation_context__menu_delete_message">Delete message</string>
<string name="conversation_context__menu_ban_user">Ban user</string> <string name="conversation_context__menu_ban_user">Ban user</string>
<string name="conversation_context__menu_ban_and_delete_all">Ban and delete all</string> <string name="conversation_context__menu_ban_and_delete_all">Ban and delete all</string>
<string name="conversation_context__menu_resync_message">Resync message</string>
<string name="conversation_context__menu_resend_message">Resend message</string> <string name="conversation_context__menu_resend_message">Resend message</string>
<string name="conversation_context__menu_reply">Reply</string> <string name="conversation_context__menu_reply">Reply</string>
<string name="conversation_context__menu_reply_to_message">Reply to message</string> <string name="conversation_context__menu_reply_to_message">Reply to message</string>
@ -1007,9 +1009,11 @@
<string name="new_conversation_dialog_close_button_content_description">Close Dialog</string> <string name="new_conversation_dialog_close_button_content_description">Close Dialog</string>
<string name="ErrorNotifier_migration">Database Upgrade Failed</string> <string name="ErrorNotifier_migration">Database Upgrade Failed</string>
<string name="ErrorNotifier_migration_downgrade">Please contact support to report the error.</string> <string name="ErrorNotifier_migration_downgrade">Please contact support to report the error.</string>
<string name="delivery_status_syncing">Syncing</string>
<string name="delivery_status_sending">Sending</string> <string name="delivery_status_sending">Sending</string>
<string name="delivery_status_read">Read</string> <string name="delivery_status_read">Read</string>
<string name="delivery_status_sent">Sent</string> <string name="delivery_status_sent">Sent</string>
<string name="delivery_status_sync_failed">Failed to sync</string>
<string name="delivery_status_failed">Failed to send</string> <string name="delivery_status_failed">Failed to send</string>
<string name="giphy_permission_title">Search GIFs?</string> <string name="giphy_permission_title">Search GIFs?</string>
<string name="giphy_permission_message">Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.</string> <string name="giphy_permission_message">Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs.</string>

View File

@ -106,10 +106,13 @@ interface StorageProtocol {
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 updateSentTimestamp(messageID: Long, isMms: Boolean, openGroupSentTimestamp: Long, threadId: Long) fun updateSentTimestamp(messageID: Long, isMms: Boolean, openGroupSentTimestamp: Long, threadId: Long)
fun markAsResyncing(timestamp: Long, author: String)
fun markAsSyncing(timestamp: Long, author: String)
fun markAsSending(timestamp: Long, author: String) 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 markAsSyncFailed(timestamp: Long, author: String, error: Exception)
fun markAsSentFailed(timestamp: Long, author: String, error: Exception)
fun clearErrorMessage(messageID: Long) fun clearErrorMessage(messageID: Long)
fun setMessageServerHash(messageID: Long, serverHash: String) fun setMessageServerHash(messageID: Long, serverHash: String)

View File

@ -34,6 +34,8 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job {
} }
override fun execute(dispatcherName: String) { override fun execute(dispatcherName: String) {
Log.d(TAG, "MessageSendJob#execute() called with: dispatcherName = $dispatcherName")
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 storage = MessagingModuleConfiguration.shared.storage

View File

@ -7,13 +7,13 @@ import org.session.libsignal.utilities.toHexString
sealed class Destination { sealed class Destination {
class Contact(var publicKey: String) : Destination() { data class Contact(var publicKey: String) : Destination() {
internal constructor(): this("") internal constructor(): this("")
} }
class ClosedGroup(var groupPublicKey: String) : Destination() { data class ClosedGroup(var groupPublicKey: String) : Destination() {
internal constructor(): this("") internal constructor(): this("")
} }
class LegacyOpenGroup(var roomToken: String, var server: String) : Destination() { data class LegacyOpenGroup(var roomToken: String, var server: String) : Destination() {
internal constructor(): this("", "") internal constructor(): this("", "")
} }

View File

@ -11,20 +11,22 @@ import org.session.libsignal.protos.SignalServiceProtos
import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Log
import org.session.libsession.messaging.sending_receiving.attachments.Attachment as SignalAttachment import org.session.libsession.messaging.sending_receiving.attachments.Attachment as SignalAttachment
class VisibleMessage : Message() { /**
/** In the case of a sync message, the public key of the person the message was targeted at. * @param syncTarget In the case of a sync message, the public key of the person the message was targeted at.
* *
* **Note:** `nil` if this isn't a sync message. * **Note:** `nil` if this isn't a sync message.
*/ */
var syncTarget: String? = null class VisibleMessage(
var text: String? = null var syncTarget: String? = null,
val attachmentIDs: MutableList<Long> = mutableListOf() var text: String? = null,
var quote: Quote? = null val attachmentIDs: MutableList<Long> = mutableListOf(),
var linkPreview: LinkPreview? = null var quote: Quote? = null,
var profile: Profile? = null var linkPreview: LinkPreview? = null,
var openGroupInvitation: OpenGroupInvitation? = null var profile: Profile? = null,
var reaction: Reaction? = null var openGroupInvitation: OpenGroupInvitation? = null,
var reaction: Reaction? = null,
var hasMention: Boolean = false var hasMention: Boolean = false
) : Message() {
override val isSelfSendValid: Boolean = true override val isSelfSendValid: Boolean = true

View File

@ -39,6 +39,8 @@ import org.session.libsession.messaging.sending_receiving.attachments.Attachment
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview as SignalLinkPreview import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview as SignalLinkPreview
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel as SignalQuote import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel as SignalQuote
private val TAG = MessageSender::class.java.simpleName
object MessageSender { object MessageSender {
// Error // Error
@ -61,16 +63,20 @@ object MessageSender {
} }
// Convenience // Convenience
fun send(message: Message, destination: Destination): Promise<Unit, Exception> { fun send(message: Message, destination: Destination, isSyncMessage: Boolean = false): Promise<Unit, Exception> {
Log.d(TAG, "send() called with: message = $message, destination = $destination, isSyncMessage = $isSyncMessage")
return if (destination is Destination.LegacyOpenGroup || destination is Destination.OpenGroup || destination is Destination.OpenGroupInbox) { return if (destination is Destination.LegacyOpenGroup || destination is Destination.OpenGroup || destination is Destination.OpenGroupInbox) {
sendToOpenGroupDestination(destination, message) sendToOpenGroupDestination(destination, message)
} else { } else {
sendToSnodeDestination(destination, message) sendToSnodeDestination(destination, message, isSyncMessage)
} }
} }
// One-on-One Chats & Closed Groups // One-on-One Chats & Closed Groups
private fun sendToSnodeDestination(destination: Destination, message: Message, isSyncMessage: Boolean = false): Promise<Unit, Exception> { private fun sendToSnodeDestination(destination: Destination, message: Message, isSyncMessage: Boolean = false): Promise<Unit, Exception> {
Log.d(TAG, "sendToSnodeDestination() called with: destination = $destination, message = $message, isSyncMessage = $isSyncMessage")
val deferred = deferred<Unit, Exception>() val deferred = deferred<Unit, Exception>()
val promise = deferred.promise val promise = deferred.promise
val storage = MessagingModuleConfiguration.shared.storage val storage = MessagingModuleConfiguration.shared.storage
@ -86,7 +92,7 @@ object MessageSender {
val isSelfSend = (message.recipient == userPublicKey) val isSelfSend = (message.recipient == userPublicKey)
// Set the failure handler (need it here already for precondition failure handling) // Set the failure handler (need it here already for precondition failure handling)
fun handleFailure(error: Exception) { fun handleFailure(error: Exception) {
handleFailedMessageSend(message, error) handleFailedMessageSend(message, error, isSyncMessage)
if (destination is Destination.Contact && message is VisibleMessage && !isSelfSend) { if (destination is Destination.Contact && message is VisibleMessage && !isSelfSend) {
SnodeModule.shared.broadcaster.broadcast("messageFailed", message.sentTimestamp!!) SnodeModule.shared.broadcaster.broadcast("messageFailed", message.sentTimestamp!!)
} }
@ -220,6 +226,8 @@ object MessageSender {
// Open Groups // Open Groups
private fun sendToOpenGroupDestination(destination: Destination, message: Message): Promise<Unit, Exception> { private fun sendToOpenGroupDestination(destination: Destination, message: Message): Promise<Unit, Exception> {
Log.d(TAG, "sendToOpenGroupDestination() called with: destination = $destination, message = $message")
val deferred = deferred<Unit, Exception>() val deferred = deferred<Unit, Exception>()
val storage = MessagingModuleConfiguration.shared.storage val storage = MessagingModuleConfiguration.shared.storage
if (message.sentTimestamp == null) { if (message.sentTimestamp == null) {
@ -318,6 +326,8 @@ object MessageSender {
// Result Handling // Result Handling
fun handleSuccessfulMessageSend(message: Message, destination: Destination, isSyncMessage: Boolean = false, openGroupSentTimestamp: Long = -1) { fun handleSuccessfulMessageSend(message: Message, destination: Destination, isSyncMessage: Boolean = false, openGroupSentTimestamp: Long = -1) {
Log.d(TAG, "handleSuccessfulMessageSend() called with: message = $message, destination = $destination, isSyncMessage = $isSyncMessage, openGroupSentTimestamp = $openGroupSentTimestamp")
val storage = MessagingModuleConfiguration.shared.storage val storage = MessagingModuleConfiguration.shared.storage
val userPublicKey = storage.getUserPublicKey()!! val userPublicKey = storage.getUserPublicKey()!!
// Ignore future self-sends // Ignore future self-sends
@ -374,21 +384,32 @@ object MessageSender {
// • the destination was a contact // • the destination was a contact
// • we didn't sync it already // • we didn't sync it already
if (destination is Destination.Contact && !isSyncMessage) { if (destination is Destination.Contact && !isSyncMessage) {
if (message is VisibleMessage) { message.syncTarget = destination.publicKey } if (message is VisibleMessage) message.syncTarget = destination.publicKey
if (message is ExpirationTimerUpdate) { message.syncTarget = destination.publicKey } if (message is ExpirationTimerUpdate) message.syncTarget = destination.publicKey
storage.markAsSyncing(message.sentTimestamp!!, userPublicKey)
sendToSnodeDestination(Destination.Contact(userPublicKey), message, true) sendToSnodeDestination(Destination.Contact(userPublicKey), message, true)
} }
} }
fun handleFailedMessageSend(message: Message, error: Exception) { fun handleFailedMessageSend(message: Message, error: Exception, isSyncMessage: Boolean = false) {
Log.d(TAG, "handleFailedMessageSend() called with: message = $message, error = $error, isSyncMessage = $isSyncMessage")
val storage = MessagingModuleConfiguration.shared.storage val storage = MessagingModuleConfiguration.shared.storage
val userPublicKey = storage.getUserPublicKey()!! val userPublicKey = storage.getUserPublicKey()!!
storage.setErrorMessage(message.sentTimestamp!!, message.sender?:userPublicKey, error)
val timestamp = message.sentTimestamp!!
val author = message.sender ?: userPublicKey
if (isSyncMessage) storage.markAsSyncFailed(timestamp, author, error)
else storage.markAsSentFailed(timestamp, author, error)
} }
// Convenience // Convenience
@JvmStatic @JvmStatic
fun send(message: VisibleMessage, address: Address, attachments: List<SignalAttachment>, quote: SignalQuote?, linkPreview: SignalLinkPreview?) { fun send(message: VisibleMessage, address: Address, attachments: List<SignalAttachment>, quote: SignalQuote?, linkPreview: SignalLinkPreview?) {
Log.d(TAG, "send() called with: message = $message, address = $address, attachments = $attachments, quote = $quote, linkPreview = $linkPreview")
val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider
val attachmentIDs = messageDataProvider.getAttachmentIDsFor(message.id!!) val attachmentIDs = messageDataProvider.getAttachmentIDsFor(message.id!!)
message.attachmentIDs.addAll(attachmentIDs) message.attachmentIDs.addAll(attachmentIDs)
@ -407,6 +428,8 @@ object MessageSender {
@JvmStatic @JvmStatic
fun send(message: Message, address: Address) { fun send(message: Message, address: Address) {
Log.d(TAG, "send() called with: message = $message, address = $address")
val threadID = MessagingModuleConfiguration.shared.storage.getOrCreateThreadIdFor(address) val threadID = MessagingModuleConfiguration.shared.storage.getOrCreateThreadIdFor(address)
message.threadID = threadID message.threadID = threadID
val destination = Destination.from(address) val destination = Destination.from(address)