Fix approved references and visible message setting flags for approval (#854)

* fix: set approved on new outgoing threads, use approved more deeply and invalidate the options menu on recipient modified. Add approvedMe flag toggles for visible message receive

* fix: add name update in action bar on modified, change where approvedMe is set

* fix: text colours and attachment approve requests

* refactor: text color for share screen

* Restart conversation loader on message request approval

Co-authored-by: ceokot <ceokot@gmail.com>
This commit is contained in:
Harris 2022-03-10 09:54:18 +11:00 committed by GitHub
parent a67195c6cf
commit c458d4a359
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 100 additions and 46 deletions

View File

@ -151,7 +151,7 @@ import kotlin.math.sqrt
class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDelegate, class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDelegate,
InputBarRecordingViewDelegate, AttachmentManager.AttachmentListener, ActivityDispatcher, InputBarRecordingViewDelegate, AttachmentManager.AttachmentListener, ActivityDispatcher,
ConversationActionModeCallbackDelegate, VisibleMessageContentViewDelegate, RecipientModifiedListener, ConversationActionModeCallbackDelegate, VisibleMessageContentViewDelegate, RecipientModifiedListener,
SearchBottomBar.EventListener, VoiceMessageViewDelegate { SearchBottomBar.EventListener, VoiceMessageViewDelegate, LoaderManager.LoaderCallbacks<Cursor> {
private var binding: ActivityConversationV2Binding? = null private var binding: ActivityConversationV2Binding? = null
private var actionBarBinding: ActivityConversationV2ActionBarBinding? = null private var actionBarBinding: ActivityConversationV2ActionBarBinding? = null
@ -350,34 +350,32 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
baseDialog.show(supportFragmentManager, tag) baseDialog.show(supportFragmentManager, tag)
} }
override fun onCreateLoader(id: Int, bundle: Bundle?): Loader<Cursor> {
return ConversationLoader(viewModel.threadId, !isIncomingMessageRequestThread(), this@ConversationActivityV2)
}
override fun onLoadFinished(loader: Loader<Cursor>, cursor: Cursor?) {
adapter.changeCursor(cursor)
if (cursor != null) {
val messageTimestamp = messageToScrollTimestamp.getAndSet(-1)
val author = messageToScrollAuthor.getAndSet(null)
if (author != null && messageTimestamp >= 0) {
jumpToMessage(author, messageTimestamp, null)
}
}
}
override fun onLoaderReset(cursor: Loader<Cursor>) {
adapter.changeCursor(null)
}
// called from onCreate // called from onCreate
private fun setUpRecyclerView() { private fun setUpRecyclerView() {
binding!!.conversationRecyclerView.adapter = adapter binding!!.conversationRecyclerView.adapter = adapter
val reverseLayout = !isIncomingMessageRequestThread() val layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, !isIncomingMessageRequestThread())
val layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, reverseLayout)
binding!!.conversationRecyclerView.layoutManager = layoutManager binding!!.conversationRecyclerView.layoutManager = layoutManager
// Workaround for the fact that CursorRecyclerViewAdapter doesn't auto-update automatically (even though it says it will) // Workaround for the fact that CursorRecyclerViewAdapter doesn't auto-update automatically (even though it says it will)
LoaderManager.getInstance(this).restartLoader(0, null, object : LoaderManager.LoaderCallbacks<Cursor> { LoaderManager.getInstance(this).restartLoader(0, null, this)
override fun onCreateLoader(id: Int, bundle: Bundle?): Loader<Cursor> {
return ConversationLoader(viewModel.threadId, reverseLayout, this@ConversationActivityV2)
}
override fun onLoadFinished(loader: Loader<Cursor>, cursor: Cursor?) {
adapter.changeCursor(cursor)
if (cursor != null) {
val messageTimestamp = messageToScrollTimestamp.getAndSet(-1)
val author = messageToScrollAuthor.getAndSet(null)
if (author != null && messageTimestamp >= 0) {
jumpToMessage(author, messageTimestamp, null)
}
}
}
override fun onLoaderReset(cursor: Loader<Cursor>) {
adapter.changeCursor(null)
}
})
binding!!.conversationRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { binding!!.conversationRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
@ -388,7 +386,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
// called from onCreate // called from onCreate
private fun setUpToolBar() { private fun setUpToolBar() {
val actionBar = supportActionBar!! val actionBar = supportActionBar ?: return
actionBarBinding = ActivityConversationV2ActionBarBinding.inflate(layoutInflater) actionBarBinding = ActivityConversationV2ActionBarBinding.inflate(layoutInflater)
actionBar.title = "" actionBar.title = ""
actionBar.customView = actionBarBinding!!.root actionBar.customView = actionBarBinding!!.root
@ -581,9 +579,11 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
if (viewModel.recipient.isContactRecipient) { if (viewModel.recipient.isContactRecipient) {
binding?.blockedBanner?.isVisible = viewModel.recipient.isBlocked binding?.blockedBanner?.isVisible = viewModel.recipient.isBlocked
} }
invalidateOptionsMenu()
updateSubtitle() updateSubtitle()
showOrHideInputIfNeeded() showOrHideInputIfNeeded()
actionBarBinding?.profilePictureView?.update(recipient) actionBarBinding?.profilePictureView?.update(recipient)
actionBarBinding?.conversationTitleView?.text = recipient.toShortString()
} }
} }
@ -616,16 +616,16 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
binding?.messageRequestBar?.isVisible = false binding?.messageRequestBar?.isVisible = false
binding?.conversationRecyclerView?.layoutManager = binding?.conversationRecyclerView?.layoutManager =
LinearLayoutManager(this, LinearLayoutManager.VERTICAL, true) LinearLayoutManager(this, LinearLayoutManager.VERTICAL, true)
adapter.notifyDataSetChanged()
viewModel.acceptMessageRequest() viewModel.acceptMessageRequest()
LoaderManager.getInstance(this).restartLoader(0, null, this)
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(this@ConversationActivityV2) ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(this@ConversationActivityV2)
} }
} }
private fun isMessageRequestThread(): Boolean { private fun isMessageRequestThread(): Boolean {
val hasSent = threadDb.getLastSeenAndHasSent(viewModel.threadId).second() return !viewModel.recipient.isGroupRecipient && !viewModel.recipient.isApproved
return (!viewModel.recipient.isGroupRecipient && !hasSent) ||
(!viewModel.recipient.isGroupRecipient && hasSent && !(viewModel.recipient.hasApprovedMe() || viewModel.hasReceived()))
} }
private fun isOutgoingMessageRequestThread(): Boolean { private fun isOutgoingMessageRequestThread(): Boolean {
@ -999,9 +999,6 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
} }
override fun sendMessage() { override fun sendMessage() {
if (isIncomingMessageRequestThread()) {
acceptMessageRequest()
}
if (viewModel.recipient.isContactRecipient && viewModel.recipient.isBlocked) { if (viewModel.recipient.isContactRecipient && viewModel.recipient.isBlocked) {
BlockedDialog(viewModel.recipient).show(supportFragmentManager, "Blocked Dialog") BlockedDialog(viewModel.recipient).show(supportFragmentManager, "Blocked Dialog")
return return
@ -1019,7 +1016,17 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
startActivityForResult(MediaSendActivity.buildEditorIntent(this, listOf( media ), viewModel.recipient, getMessageBody()), PICK_FROM_LIBRARY) startActivityForResult(MediaSendActivity.buildEditorIntent(this, listOf( media ), viewModel.recipient, getMessageBody()), PICK_FROM_LIBRARY)
} }
private fun processMessageRequestApproval() {
if (isIncomingMessageRequestThread()) {
acceptMessageRequest()
} else if (!viewModel.recipient.isApproved) {
// edge case for new outgoing thread on new recipient without sending approval messages
viewModel.setRecipientApproved()
}
}
private fun sendTextOnlyMessage(hasPermissionToSendSeed: Boolean = false) { private fun sendTextOnlyMessage(hasPermissionToSendSeed: Boolean = false) {
processMessageRequestApproval()
val text = getMessageBody() val text = getMessageBody()
val userPublicKey = textSecurePreferences.getLocalNumber() val userPublicKey = textSecurePreferences.getLocalNumber()
val isNoteToSelf = (viewModel.recipient.isContactRecipient && viewModel.recipient.address.toString() == userPublicKey) val isNoteToSelf = (viewModel.recipient.isContactRecipient && viewModel.recipient.address.toString() == userPublicKey)
@ -1049,6 +1056,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
} }
private fun sendAttachments(attachments: List<Attachment>, body: String?, quotedMessage: MessageRecord? = null, linkPreview: LinkPreview? = null) { private fun sendAttachments(attachments: List<Attachment>, body: String?, quotedMessage: MessageRecord? = null, linkPreview: LinkPreview? = null) {
processMessageRequestApproval()
// Create the message // Create the message
val message = VisibleMessage() val message = VisibleMessage()
message.sentTimestamp = System.currentTimeMillis() message.sentTimestamp = System.currentTimeMillis()

View File

@ -53,6 +53,10 @@ class ConversationViewModel(
repository.deleteLocally(recipient, message) repository.deleteLocally(recipient, message)
} }
fun setRecipientApproved() {
repository.setApproved(recipient, true)
}
fun deleteForEveryone(message: MessageRecord) = viewModelScope.launch { fun deleteForEveryone(message: MessageRecord) = viewModelScope.launch {
repository.deleteForEveryone(threadId, recipient, message) repository.deleteForEveryone(threadId, recipient, message)
.onFailure { .onFailure {

View File

@ -19,7 +19,6 @@ import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities
import org.thoughtcrime.securesms.conversation.v2.utilities.TextUtilities import org.thoughtcrime.securesms.conversation.v2.utilities.TextUtilities
import org.thoughtcrime.securesms.database.SessionContactDatabase import org.thoughtcrime.securesms.database.SessionContactDatabase
import org.thoughtcrime.securesms.database.model.Quote import org.thoughtcrime.securesms.database.model.Quote
import org.thoughtcrime.securesms.database.model.ThreadRecord
import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.mms.SlideDeck import org.thoughtcrime.securesms.mms.SlideDeck
import org.thoughtcrime.securesms.util.MediaUtil import org.thoughtcrime.securesms.util.MediaUtil
@ -184,10 +183,10 @@ class QuoteView : LinearLayout {
@ColorInt private fun getTextColor(isOutgoingMessage: Boolean): Int { @ColorInt private fun getTextColor(isOutgoingMessage: Boolean): Int {
if (mode == Mode.Draft) { return ResourcesCompat.getColor(resources, R.color.text, context.theme) } if (mode == Mode.Draft) { return ResourcesCompat.getColor(resources, R.color.text, context.theme) }
val isLightMode = UiModeUtilities.isDayUiMode(context) val isLightMode = UiModeUtilities.isDayUiMode(context)
return if ((isOutgoingMessage && !isLightMode) || (!isOutgoingMessage && isLightMode)) { return if (!isOutgoingMessage && !isLightMode) {
ResourcesCompat.getColor(resources, R.color.black, context.theme)
} else {
ResourcesCompat.getColor(resources, R.color.white, context.theme) ResourcesCompat.getColor(resources, R.color.white, context.theme)
} else {
ResourcesCompat.getColor(resources, R.color.black, context.theme)
} }
} }

View File

@ -296,7 +296,7 @@ class VisibleMessageContentView : LinearLayout {
fun getTextColor(context: Context, message: MessageRecord): Int { fun getTextColor(context: Context, message: MessageRecord): Int {
val isDayUiMode = UiModeUtilities.isDayUiMode(context) val isDayUiMode = UiModeUtilities.isDayUiMode(context)
val colorID = if (message.isOutgoing) { val colorID = if (message.isOutgoing) {
if (isDayUiMode) R.color.white else R.color.black R.color.black
} else { } else {
if (isDayUiMode) R.color.black else R.color.white if (isDayUiMode) R.color.black else R.color.white
} }

View File

@ -238,10 +238,15 @@ public class RecipientDatabase extends Database {
public void setApproved(@NonNull Recipient recipient, boolean approved) { public void setApproved(@NonNull Recipient recipient, boolean approved) {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(APPROVED, approved ? 1 : 0); values.put(APPROVED, approved ? 1 : 0);
values.put(APPROVED_ME, approved ? 1 : 0);
updateOrInsert(recipient.getAddress(), values); updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setApproved(approved); recipient.resolve().setApproved(approved);
recipient.resolve().setHasApprovedMe(approved); }
public void setApprovedMe(@NonNull Recipient recipient, boolean approvedMe) {
ContentValues values = new ContentValues();
values.put(APPROVED_ME, approvedMe ? 1 : 0);
updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setHasApprovedMe(approvedMe);
} }
public void setBlocked(@NonNull Recipient recipient, boolean blocked) { public void setBlocked(@NonNull Recipient recipient, boolean blocked) {

View File

@ -4,11 +4,22 @@ import android.content.Context
import android.net.Uri import android.net.Uri
import org.session.libsession.database.StorageProtocol import org.session.libsession.database.StorageProtocol
import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.messaging.jobs.* import org.session.libsession.messaging.jobs.AttachmentUploadJob
import org.session.libsession.messaging.jobs.GroupAvatarDownloadJob
import org.session.libsession.messaging.jobs.Job
import org.session.libsession.messaging.jobs.JobQueue
import org.session.libsession.messaging.jobs.MessageReceiveJob
import org.session.libsession.messaging.jobs.MessageSendJob
import org.session.libsession.messaging.jobs.TrimThreadJob
import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.messages.control.ConfigurationMessage
import org.session.libsession.messaging.messages.control.MessageRequestResponse import org.session.libsession.messaging.messages.control.MessageRequestResponse
import org.session.libsession.messaging.messages.signal.* import org.session.libsession.messaging.messages.signal.IncomingEncryptedMessage
import org.session.libsession.messaging.messages.signal.IncomingGroupMessage
import org.session.libsession.messaging.messages.signal.IncomingMediaMessage
import org.session.libsession.messaging.messages.signal.IncomingTextMessage import org.session.libsession.messaging.messages.signal.IncomingTextMessage
import org.session.libsession.messaging.messages.signal.OutgoingGroupMediaMessage
import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage
import org.session.libsession.messaging.messages.signal.OutgoingTextMessage
import org.session.libsession.messaging.messages.visible.Attachment import org.session.libsession.messaging.messages.visible.Attachment
import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.messages.visible.VisibleMessage
import org.session.libsession.messaging.open_groups.OpenGroupV2 import org.session.libsession.messaging.open_groups.OpenGroupV2
@ -19,14 +30,17 @@ import org.session.libsession.messaging.sending_receiving.link_preview.LinkPrevi
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
import org.session.libsession.snode.OnionRequestAPI import org.session.libsession.snode.OnionRequestAPI
import org.session.libsession.utilities.* 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.GroupRecord
import org.session.libsession.utilities.GroupUtil
import org.session.libsession.utilities.ProfileKeyUtil
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.crypto.ecc.ECKeyPair import org.session.libsignal.crypto.ecc.ECKeyPair
import org.session.libsignal.messages.SignalServiceAttachmentPointer import org.session.libsignal.messages.SignalServiceAttachmentPointer
import org.session.libsignal.messages.SignalServiceGroup import org.session.libsignal.messages.SignalServiceGroup
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
@ -110,6 +124,14 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
senderAddress senderAddress
} }
val targetRecipient = Recipient.from(context, targetAddress, false) val targetRecipient = Recipient.from(context, targetAddress, false)
if (!targetRecipient.isGroupRecipient) {
val recipientDb = DatabaseComponent.get(context).recipientDatabase()
if (isUserSender) {
recipientDb.setApproved(targetRecipient, true)
} else {
recipientDb.setApprovedMe(targetRecipient, true)
}
}
if (message.isMediaMessage() || attachments.isNotEmpty()) { if (message.isMediaMessage() || attachments.isNotEmpty()) {
val quote: Optional<QuoteModel> = if (quotes != null) Optional.of(quotes) else Optional.absent() val quote: Optional<QuoteModel> = if (quotes != null) Optional.of(quotes) else Optional.absent()
val linkPreviews: Optional<List<LinkPreview>> = if (linkPreview.isEmpty()) Optional.absent() else Optional.of(linkPreview.mapNotNull { it!! }) val linkPreviews: Optional<List<LinkPreview>> = if (linkPreview.isEmpty()) Optional.absent() else Optional.of(linkPreview.mapNotNull { it!! })
@ -585,7 +607,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
// create Thread if needed // create Thread if needed
val threadId = threadDatabase.getOrCreateThreadIdFor(recipient) val threadId = threadDatabase.getOrCreateThreadIdFor(recipient)
if (contact.didApproveMe == true) { if (contact.didApproveMe == true) {
recipientDatabase.setApproved(recipient, true) recipientDatabase.setApprovedMe(recipient, true)
threadDatabase.setHasSent(threadId, true) threadDatabase.setHasSent(threadId, true)
} }
if (contact.isApproved == true) { if (contact.isApproved == true) {
@ -663,7 +685,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
val mmsDb = DatabaseComponent.get(context).mmsDatabase() val mmsDb = DatabaseComponent.get(context).mmsDatabase()
val senderAddress = fromSerialized(senderPublicKey) val senderAddress = fromSerialized(senderPublicKey)
val requestSender = Recipient.from(context, senderAddress, false) val requestSender = Recipient.from(context, senderAddress, false)
recipientDb.setApproved(requestSender, true) recipientDb.setApprovedMe(requestSender, true)
val message = IncomingMediaMessage( val message = IncomingMediaMessage(
senderAddress, senderAddress,
@ -686,4 +708,12 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
} }
} }
override fun setRecipientApproved(recipient: Recipient, approved: Boolean) {
DatabaseComponent.get(context).recipientDatabase().setApproved(recipient, approved)
}
override fun setRecipientApprovedMe(recipient: Recipient, approvedMe: Boolean) {
DatabaseComponent.get(context).recipientDatabase().setApprovedMe(recipient, approvedMe)
}
} }

View File

@ -39,6 +39,7 @@ interface ConversationRepository {
fun inviteContacts(threadId: Long, contacts: List<Recipient>) fun inviteContacts(threadId: Long, contacts: List<Recipient>)
fun unblock(recipient: Recipient) fun unblock(recipient: Recipient)
fun deleteLocally(recipient: Recipient, message: MessageRecord) fun deleteLocally(recipient: Recipient, message: MessageRecord)
fun setApproved(recipient: Recipient, isApproved: Boolean)
suspend fun deleteForEveryone( suspend fun deleteForEveryone(
threadId: Long, threadId: Long,
@ -138,6 +139,10 @@ class DefaultConversationRepository @Inject constructor(
messageDataProvider.deleteMessage(message.id, !message.isMms) messageDataProvider.deleteMessage(message.id, !message.isMms)
} }
override fun setApproved(recipient: Recipient, isApproved: Boolean) {
recipientDb.setApproved(recipient, isApproved)
}
override suspend fun deleteForEveryone( override suspend fun deleteForEveryone(
threadId: Long, threadId: Long,
recipient: Recipient, recipient: Recipient,

View File

@ -11,7 +11,7 @@
android:paddingStart="16dp" android:paddingStart="16dp"
android:paddingTop="10dp" android:paddingTop="10dp"
android:paddingBottom="10dp" android:paddingBottom="10dp"
android:textColor="@color/signal_primary_dark" android:textColor="@color/text"
tools:text="Recent chats"/> tools:text="Recent chats"/>
<View <View

View File

@ -18,6 +18,7 @@
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<RelativeLayout <RelativeLayout
android:padding="4dp"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">

View File

@ -75,6 +75,7 @@
<org.thoughtcrime.securesms.conversation.v2.components.ExpirationTimerView <org.thoughtcrime.securesms.conversation.v2.components.ExpirationTimerView
android:id="@+id/expirationTimerView" android:id="@+id/expirationTimerView"
android:layout_marginHorizontal="@dimen/small_spacing"
android:layout_width="12dp" android:layout_width="12dp"
android:layout_height="12dp" android:layout_height="12dp"
android:layout_gravity="center_vertical" /> android:layout_gravity="center_vertical" />

View File

@ -4,7 +4,6 @@ import android.content.Context
import android.net.Uri import android.net.Uri
import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.messaging.jobs.AttachmentUploadJob import org.session.libsession.messaging.jobs.AttachmentUploadJob
import org.session.libsession.messaging.jobs.GroupAvatarDownloadJob
import org.session.libsession.messaging.jobs.Job import org.session.libsession.messaging.jobs.Job
import org.session.libsession.messaging.jobs.MessageSendJob import org.session.libsession.messaging.jobs.MessageSendJob
import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.messages.control.ConfigurationMessage
@ -158,4 +157,6 @@ interface StorageProtocol {
fun persist(message: VisibleMessage, quotes: QuoteModel?, linkPreview: List<LinkPreview?>, groupPublicKey: String?, openGroupID: String?, attachments: List<Attachment>): Long? fun persist(message: VisibleMessage, quotes: QuoteModel?, linkPreview: List<LinkPreview?>, groupPublicKey: String?, openGroupID: String?, attachments: List<Attachment>): Long?
fun insertDataExtractionNotificationMessage(senderPublicKey: String, message: DataExtractionNotificationInfoMessage, sentTimestamp: Long) fun insertDataExtractionNotificationMessage(senderPublicKey: String, message: DataExtractionNotificationInfoMessage, sentTimestamp: Long)
fun insertMessageRequestResponse(response: MessageRequestResponse) fun insertMessageRequestResponse(response: MessageRequestResponse)
fun setRecipientApproved(recipient: Recipient, approved: Boolean)
fun setRecipientApprovedMe(recipient: Recipient, approvedMe: Boolean)
} }