mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-21 15:05:19 +00:00
SES-1156 - Ban and delete functionality fix (#1428)
* WIP * Investigation in progress * End of day push * WIP * Fixes #1416 * Cleanup * Added code to remove zombie messages caught in limbo during a ban & delete - still chock full o' debug while finding root cause * Root cause debug WIP * Push prior to cleanup * Cleaned up for PR * fix: mms delete, remove unnecessary values from sms * Addressed PR feedback * fix: fix unit tests * Added '.run' folder with test setup * Update README.md Test commit for CI * Re-added accidentally removed closing brace --------- Co-authored-by: alansley <aclansley@gmail.com> Co-authored-by: Al Lansley <alansley@users.noreply.github.com> Co-authored-by: 0x330a <92654767+0x330a@users.noreply.github.com>
This commit is contained in:
parent
9ad5bd2374
commit
a8a257a1a6
24
.run/Run Tests.run.xml
Normal file
24
.run/Run Tests.run.xml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Run Tests" type="GradleRunConfiguration" factoryName="Gradle">
|
||||||
|
<ExternalSystemSettings>
|
||||||
|
<option name="executionName" />
|
||||||
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
|
<option name="externalSystemIdString" value="GRADLE" />
|
||||||
|
<option name="scriptParameters" value="" />
|
||||||
|
<option name="taskDescriptions">
|
||||||
|
<list />
|
||||||
|
</option>
|
||||||
|
<option name="taskNames">
|
||||||
|
<list>
|
||||||
|
<option value="testPlayDebugUnitTestCoverageReport" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="vmOptions" />
|
||||||
|
</ExternalSystemSettings>
|
||||||
|
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
|
||||||
|
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||||
|
<DebugAllEnabled>false</DebugAllEnabled>
|
||||||
|
<RunAsTest>false</RunAsTest>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
@ -184,16 +184,21 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper)
|
|||||||
override fun deleteMessage(messageID: Long, isSms: Boolean) {
|
override fun deleteMessage(messageID: Long, isSms: Boolean) {
|
||||||
val messagingDatabase: MessagingDatabase = if (isSms) DatabaseComponent.get(context).smsDatabase()
|
val messagingDatabase: MessagingDatabase = if (isSms) DatabaseComponent.get(context).smsDatabase()
|
||||||
else DatabaseComponent.get(context).mmsDatabase()
|
else DatabaseComponent.get(context).mmsDatabase()
|
||||||
|
|
||||||
messagingDatabase.deleteMessage(messageID)
|
messagingDatabase.deleteMessage(messageID)
|
||||||
DatabaseComponent.get(context).lokiMessageDatabase().deleteMessage(messageID, isSms)
|
DatabaseComponent.get(context).lokiMessageDatabase().deleteMessage(messageID, isSms)
|
||||||
DatabaseComponent.get(context).lokiMessageDatabase().deleteMessageServerHash(messageID, mms = !isSms)
|
DatabaseComponent.get(context).lokiMessageDatabase().deleteMessageServerHash(messageID, mms = !isSms)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun deleteMessages(messageIDs: List<Long>, threadId: Long, isSms: Boolean) {
|
override fun deleteMessages(messageIDs: List<Long>, threadId: Long, isSms: Boolean) {
|
||||||
|
|
||||||
val messagingDatabase: MessagingDatabase = if (isSms) DatabaseComponent.get(context).smsDatabase()
|
val messagingDatabase: MessagingDatabase = if (isSms) DatabaseComponent.get(context).smsDatabase()
|
||||||
else DatabaseComponent.get(context).mmsDatabase()
|
else DatabaseComponent.get(context).mmsDatabase()
|
||||||
|
|
||||||
|
// Perform local delete
|
||||||
messagingDatabase.deleteMessages(messageIDs.toLongArray(), threadId)
|
messagingDatabase.deleteMessages(messageIDs.toLongArray(), threadId)
|
||||||
|
|
||||||
|
// Perform online delete
|
||||||
DatabaseComponent.get(context).lokiMessageDatabase().deleteMessages(messageIDs)
|
DatabaseComponent.get(context).lokiMessageDatabase().deleteMessages(messageIDs)
|
||||||
DatabaseComponent.get(context).lokiMessageDatabase().deleteMessageServerHashes(messageIDs, mms = !isSms)
|
DatabaseComponent.get(context).lokiMessageDatabase().deleteMessageServerHashes(messageIDs, mms = !isSms)
|
||||||
}
|
}
|
||||||
|
@ -132,7 +132,8 @@ class ProfilePictureView @JvmOverloads constructor(
|
|||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
.circleCrop()
|
.circleCrop()
|
||||||
.into(imageView)
|
.into(imageView)
|
||||||
} else if (recipient.isOpenGroupRecipient && recipient.groupAvatarId == null) {
|
} else if (recipient.isCommunityRecipient && recipient.groupAvatarId == null) {
|
||||||
|
glide.clear(imageView)
|
||||||
glide.load(unknownOpenGroupDrawable)
|
glide.load(unknownOpenGroupDrawable)
|
||||||
.centerCrop()
|
.centerCrop()
|
||||||
.circleCrop()
|
.circleCrop()
|
||||||
|
@ -58,7 +58,7 @@ class ContactSelectionListLoader(context: Context, val mode: Int, val filter: St
|
|||||||
|
|
||||||
private fun getOpenGroups(contacts: List<Recipient>): List<ContactSelectionListItem> {
|
private fun getOpenGroups(contacts: List<Recipient>): List<ContactSelectionListItem> {
|
||||||
return getItems(contacts, context.getString(R.string.fragment_contact_selection_open_groups_title)) {
|
return getItems(contacts, context.getString(R.string.fragment_contact_selection_open_groups_title)) {
|
||||||
it.address.isOpenGroup
|
it.address.isCommunity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,7 +116,7 @@ class ConversationActionBarView @JvmOverloads constructor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (recipient.isGroupRecipient) {
|
if (recipient.isGroupRecipient) {
|
||||||
val title = if (recipient.isOpenGroupRecipient) {
|
val title = if (recipient.isCommunityRecipient) {
|
||||||
val userCount = openGroup?.let { lokiApiDb.getUserCount(it.room, it.server) } ?: 0
|
val userCount = openGroup?.let { lokiApiDb.getUserCount(it.room, it.server) } ?: 0
|
||||||
context.getString(R.string.ConversationActivity_active_member_count, userCount)
|
context.getString(R.string.ConversationActivity_active_member_count, userCount)
|
||||||
} else {
|
} else {
|
||||||
|
@ -395,7 +395,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
messageToScrollAuthor.set(intent.getParcelableExtra(SCROLL_MESSAGE_AUTHOR))
|
messageToScrollAuthor.set(intent.getParcelableExtra(SCROLL_MESSAGE_AUTHOR))
|
||||||
val recipient = viewModel.recipient
|
val recipient = viewModel.recipient
|
||||||
val openGroup = recipient.let { viewModel.openGroup }
|
val openGroup = recipient.let { viewModel.openGroup }
|
||||||
if (recipient == null || (recipient.isOpenGroupRecipient && openGroup == null)) {
|
if (recipient == null || (recipient.isCommunityRecipient && openGroup == null)) {
|
||||||
Toast.makeText(this, "This thread has been deleted.", Toast.LENGTH_LONG).show()
|
Toast.makeText(this, "This thread has been deleted.", Toast.LENGTH_LONG).show()
|
||||||
return finish()
|
return finish()
|
||||||
}
|
}
|
||||||
@ -976,11 +976,11 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
view.glide = glide
|
view.glide = glide
|
||||||
view.onCandidateSelected = { handleMentionSelected(it) }
|
view.onCandidateSelected = { handleMentionSelected(it) }
|
||||||
additionalContentContainer.addView(view)
|
additionalContentContainer.addView(view)
|
||||||
val candidates = MentionsManager.getMentionCandidates(query, viewModel.threadId, recipient.isOpenGroupRecipient)
|
val candidates = MentionsManager.getMentionCandidates(query, viewModel.threadId, recipient.isCommunityRecipient)
|
||||||
this.mentionCandidatesView = view
|
this.mentionCandidatesView = view
|
||||||
view.show(candidates, viewModel.threadId)
|
view.show(candidates, viewModel.threadId)
|
||||||
} else {
|
} else {
|
||||||
val candidates = MentionsManager.getMentionCandidates(query, viewModel.threadId, recipient.isOpenGroupRecipient)
|
val candidates = MentionsManager.getMentionCandidates(query, viewModel.threadId, recipient.isCommunityRecipient)
|
||||||
this.mentionCandidatesView!!.setMentionCandidates(candidates)
|
this.mentionCandidatesView!!.setMentionCandidates(candidates)
|
||||||
}
|
}
|
||||||
isShowingMentionCandidatesView = true
|
isShowingMentionCandidatesView = true
|
||||||
@ -1197,7 +1197,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun copyOpenGroupUrl(thread: Recipient) {
|
override fun copyOpenGroupUrl(thread: Recipient) {
|
||||||
if (!thread.isOpenGroupRecipient) { return }
|
if (!thread.isCommunityRecipient) { return }
|
||||||
|
|
||||||
val threadId = threadDb.getThreadIdIfExistsFor(thread) ?: return
|
val threadId = threadDb.getThreadIdIfExistsFor(thread) ?: return
|
||||||
val openGroup = lokiThreadDb.getOpenGroupChat(threadId) ?: return
|
val openGroup = lokiThreadDb.getOpenGroupChat(threadId) ?: return
|
||||||
@ -1361,7 +1361,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
} else originalMessage.individualRecipient.address
|
} else originalMessage.individualRecipient.address
|
||||||
// Send it
|
// Send it
|
||||||
reactionMessage.reaction = Reaction.from(originalMessage.timestamp, originalAuthor.serialize(), emoji, true)
|
reactionMessage.reaction = Reaction.from(originalMessage.timestamp, originalAuthor.serialize(), emoji, true)
|
||||||
if (recipient.isOpenGroupRecipient) {
|
if (recipient.isCommunityRecipient) {
|
||||||
val messageServerId = lokiMessageDb.getServerID(originalMessage.id, !originalMessage.isMms) ?: return
|
val messageServerId = lokiMessageDb.getServerID(originalMessage.id, !originalMessage.isMms) ?: return
|
||||||
viewModel.openGroup?.let {
|
viewModel.openGroup?.let {
|
||||||
OpenGroupApi.addReaction(it.room, it.server, messageServerId, emoji)
|
OpenGroupApi.addReaction(it.room, it.server, messageServerId, emoji)
|
||||||
@ -1385,7 +1385,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
} else originalMessage.individualRecipient.address
|
} else originalMessage.individualRecipient.address
|
||||||
|
|
||||||
message.reaction = Reaction.from(originalMessage.timestamp, originalAuthor.serialize(), emoji, false)
|
message.reaction = Reaction.from(originalMessage.timestamp, originalAuthor.serialize(), emoji, false)
|
||||||
if (recipient.isOpenGroupRecipient) {
|
if (recipient.isCommunityRecipient) {
|
||||||
val messageServerId = lokiMessageDb.getServerID(originalMessage.id, !originalMessage.isMms) ?: return
|
val messageServerId = lokiMessageDb.getServerID(originalMessage.id, !originalMessage.isMms) ?: return
|
||||||
viewModel.openGroup?.let {
|
viewModel.openGroup?.let {
|
||||||
OpenGroupApi.deleteReaction(it.room, it.server, messageServerId, emoji)
|
OpenGroupApi.deleteReaction(it.room, it.server, messageServerId, emoji)
|
||||||
@ -1782,7 +1782,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
sendAttachments(slideDeck.asAttachments(), body)
|
sendAttachments(slideDeck.asAttachments(), body)
|
||||||
}
|
}
|
||||||
INVITE_CONTACTS -> {
|
INVITE_CONTACTS -> {
|
||||||
if (viewModel.recipient?.isOpenGroupRecipient != true) { return }
|
if (viewModel.recipient?.isCommunityRecipient != true) { return }
|
||||||
val extras = intent?.extras ?: return
|
val extras = intent?.extras ?: return
|
||||||
if (!intent.hasExtra(selectedContactsKey)) { return }
|
if (!intent.hasExtra(selectedContactsKey)) { return }
|
||||||
val selectedContacts = extras.getStringArray(selectedContactsKey)!!
|
val selectedContacts = extras.getStringArray(selectedContactsKey)!!
|
||||||
@ -1848,19 +1848,62 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
handleLongPress(messages.first(), 0) //TODO: begin selection mode
|
handleLongPress(messages.first(), 0) //TODO: begin selection mode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The option to "Delete just for me" or "Delete for everyone"
|
||||||
|
private fun showDeleteOrDeleteForEveryoneInCommunityUI(messages: Set<MessageRecord>) {
|
||||||
|
val bottomSheet = DeleteOptionsBottomSheet()
|
||||||
|
bottomSheet.recipient = viewModel.recipient!!
|
||||||
|
bottomSheet.onDeleteForMeTapped = {
|
||||||
|
messages.forEach(viewModel::deleteLocally)
|
||||||
|
bottomSheet.dismiss()
|
||||||
|
endActionMode()
|
||||||
|
}
|
||||||
|
bottomSheet.onDeleteForEveryoneTapped = {
|
||||||
|
messages.forEach(viewModel::deleteForEveryone)
|
||||||
|
bottomSheet.dismiss()
|
||||||
|
endActionMode()
|
||||||
|
}
|
||||||
|
bottomSheet.onCancelTapped = {
|
||||||
|
bottomSheet.dismiss()
|
||||||
|
endActionMode()
|
||||||
|
}
|
||||||
|
bottomSheet.show(supportFragmentManager, bottomSheet.tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showDeleteLocallyUI(messages: Set<MessageRecord>) {
|
||||||
|
val messageCount = 1
|
||||||
|
showSessionDialog {
|
||||||
|
title(resources.getQuantityString(R.plurals.ConversationFragment_delete_selected_messages, messageCount, messageCount))
|
||||||
|
text(resources.getQuantityString(R.plurals.ConversationFragment_this_will_permanently_delete_all_n_selected_messages, messageCount, messageCount))
|
||||||
|
button(R.string.delete) { messages.forEach(viewModel::deleteLocally); endActionMode() }
|
||||||
|
cancelButton(::endActionMode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: The messages in the provided set may be a single message, or multiple if there are a
|
||||||
|
// group of selected messages.
|
||||||
override fun deleteMessages(messages: Set<MessageRecord>) {
|
override fun deleteMessages(messages: Set<MessageRecord>) {
|
||||||
val recipient = viewModel.recipient ?: return
|
val recipient = viewModel.recipient
|
||||||
|
if (recipient == null) {
|
||||||
|
Log.w("ConversationActivityV2", "Asked to delete messages but could not obtain viewModel recipient - aborting.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
val allSentByCurrentUser = messages.all { it.isOutgoing }
|
val allSentByCurrentUser = messages.all { it.isOutgoing }
|
||||||
val allHasHash = messages.all { lokiMessageDb.getMessageServerHash(it.id, it.isMms) != null }
|
val allHasHash = messages.all { lokiMessageDb.getMessageServerHash(it.id, it.isMms) != null }
|
||||||
if (recipient.isOpenGroupRecipient) {
|
|
||||||
val messageCount = 1
|
// If the recipient is a community then we delete the message for everyone
|
||||||
|
if (recipient.isCommunityRecipient) {
|
||||||
|
val messageCount = 1 // Only used for plurals string
|
||||||
|
|
||||||
showSessionDialog {
|
showSessionDialog {
|
||||||
title(resources.getQuantityString(R.plurals.ConversationFragment_delete_selected_messages, messageCount, messageCount))
|
title(resources.getQuantityString(R.plurals.ConversationFragment_delete_selected_messages, messageCount, messageCount))
|
||||||
text(resources.getQuantityString(R.plurals.ConversationFragment_this_will_permanently_delete_all_n_selected_messages, messageCount, messageCount))
|
text(resources.getQuantityString(R.plurals.ConversationFragment_this_will_permanently_delete_all_n_selected_messages, messageCount, messageCount))
|
||||||
button(R.string.delete) { messages.forEach(viewModel::deleteForEveryone); endActionMode() }
|
button(R.string.delete) {
|
||||||
|
messages.forEach(viewModel::deleteForEveryone); endActionMode()
|
||||||
|
}
|
||||||
cancelButton { endActionMode() }
|
cancelButton { endActionMode() }
|
||||||
}
|
}
|
||||||
|
// Otherwise if this is a 1-on-1 conversation we may decided to delete just for ourselves or delete for everyone
|
||||||
} else if (allSentByCurrentUser && allHasHash) {
|
} else if (allSentByCurrentUser && allHasHash) {
|
||||||
val bottomSheet = DeleteOptionsBottomSheet()
|
val bottomSheet = DeleteOptionsBottomSheet()
|
||||||
bottomSheet.recipient = recipient
|
bottomSheet.recipient = recipient
|
||||||
@ -1879,13 +1922,17 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
endActionMode()
|
endActionMode()
|
||||||
}
|
}
|
||||||
bottomSheet.show(supportFragmentManager, bottomSheet.tag)
|
bottomSheet.show(supportFragmentManager, bottomSheet.tag)
|
||||||
} else {
|
}
|
||||||
|
else // Finally, if this is a closed group and you are deleting someone else's message(s)
|
||||||
|
// then we can only delete locally.
|
||||||
|
{
|
||||||
val messageCount = 1
|
val messageCount = 1
|
||||||
|
|
||||||
showSessionDialog {
|
showSessionDialog {
|
||||||
title(resources.getQuantityString(R.plurals.ConversationFragment_delete_selected_messages, messageCount, messageCount))
|
title(resources.getQuantityString(R.plurals.ConversationFragment_delete_selected_messages, messageCount, messageCount))
|
||||||
text(resources.getQuantityString(R.plurals.ConversationFragment_this_will_permanently_delete_all_n_selected_messages, messageCount, messageCount))
|
text(resources.getQuantityString(R.plurals.ConversationFragment_this_will_permanently_delete_all_n_selected_messages, messageCount, messageCount))
|
||||||
button(R.string.delete) { messages.forEach(viewModel::deleteLocally); endActionMode() }
|
button(R.string.delete) {
|
||||||
|
messages.forEach(viewModel::deleteLocally); endActionMode()
|
||||||
|
}
|
||||||
cancelButton(::endActionMode)
|
cancelButton(::endActionMode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1904,7 +1951,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
showSessionDialog {
|
showSessionDialog {
|
||||||
title(R.string.ConversationFragment_ban_selected_user)
|
title(R.string.ConversationFragment_ban_selected_user)
|
||||||
text("This will ban the selected user from this room and delete all messages sent by them. It won't ban them from other rooms or delete the messages they sent there.")
|
text("This will ban the selected user from this room and delete all messages sent by them. It won't ban them from other rooms or delete the messages they sent there.")
|
||||||
button(R.string.ban) { viewModel.banAndDeleteAll(messages.first().individualRecipient); endActionMode() }
|
button(R.string.ban) { viewModel.banAndDeleteAll(messages.first()); endActionMode() }
|
||||||
cancelButton(::endActionMode)
|
cancelButton(::endActionMode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -541,7 +541,7 @@ class ConversationReactionOverlay : FrameLayout {
|
|||||||
items += ActionItem(R.attr.menu_copy_icon, R.string.copy, { handleActionItemClicked(Action.COPY_MESSAGE) })
|
items += ActionItem(R.attr.menu_copy_icon, R.string.copy, { handleActionItemClicked(Action.COPY_MESSAGE) })
|
||||||
}
|
}
|
||||||
// Copy Session ID
|
// Copy Session ID
|
||||||
if (recipient.isGroupRecipient && !recipient.isOpenGroupRecipient && message.recipient.address.toString() != userPublicKey) {
|
if (recipient.isGroupRecipient && !recipient.isCommunityRecipient && message.recipient.address.toString() != userPublicKey) {
|
||||||
items += ActionItem(R.attr.menu_copy_icon, R.string.activity_conversation_menu_copy_session_id, { handleActionItemClicked(Action.COPY_SESSION_ID) })
|
items += ActionItem(R.attr.menu_copy_icon, R.string.activity_conversation_menu_copy_session_id, { handleActionItemClicked(Action.COPY_SESSION_ID) })
|
||||||
}
|
}
|
||||||
// Delete message
|
// Delete message
|
||||||
|
@ -1,18 +1,23 @@
|
|||||||
package org.thoughtcrime.securesms.conversation.v2
|
package org.thoughtcrime.securesms.conversation.v2
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
|
||||||
import com.goterl.lazysodium.utils.KeyPair
|
import com.goterl.lazysodium.utils.KeyPair
|
||||||
|
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
import org.session.libsession.messaging.messages.ExpirationConfiguration
|
import org.session.libsession.messaging.messages.ExpirationConfiguration
|
||||||
import org.session.libsession.messaging.open_groups.OpenGroup
|
import org.session.libsession.messaging.open_groups.OpenGroup
|
||||||
import org.session.libsession.messaging.open_groups.OpenGroupApi
|
import org.session.libsession.messaging.open_groups.OpenGroupApi
|
||||||
@ -22,9 +27,12 @@ import org.session.libsession.utilities.Address
|
|||||||
import org.session.libsession.utilities.recipients.Recipient
|
import org.session.libsession.utilities.recipients.Recipient
|
||||||
import org.session.libsignal.utilities.IdPrefix
|
import org.session.libsignal.utilities.IdPrefix
|
||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
|
import org.thoughtcrime.securesms.database.MmsSmsDatabase
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.database.Storage
|
import org.thoughtcrime.securesms.database.Storage
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||||
import org.thoughtcrime.securesms.repository.ConversationRepository
|
import org.thoughtcrime.securesms.repository.ConversationRepository
|
||||||
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
class ConversationViewModel(
|
class ConversationViewModel(
|
||||||
@ -144,9 +152,14 @@ class ConversationViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun deleteForEveryone(message: MessageRecord) = viewModelScope.launch {
|
fun deleteForEveryone(message: MessageRecord) = viewModelScope.launch {
|
||||||
val recipient = recipient ?: return@launch
|
val recipient = recipient ?: return@launch Log.w("Loki", "Recipient was null for delete for everyone - aborting delete operation.")
|
||||||
|
|
||||||
repository.deleteForEveryone(threadId, recipient, message)
|
repository.deleteForEveryone(threadId, recipient, message)
|
||||||
|
.onSuccess {
|
||||||
|
Log.d("Loki", "Deleted message ${message.id} ")
|
||||||
|
}
|
||||||
.onFailure {
|
.onFailure {
|
||||||
|
Log.w("Loki", "FAILED TO delete message ${message.id} ")
|
||||||
showMessage("Couldn't delete message due to error: $it")
|
showMessage("Couldn't delete message due to error: $it")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -168,10 +181,15 @@ class ConversationViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun banAndDeleteAll(recipient: Recipient) = viewModelScope.launch {
|
fun banAndDeleteAll(messageRecord: MessageRecord) = viewModelScope.launch {
|
||||||
repository.banAndDeleteAll(threadId, recipient)
|
|
||||||
|
repository.banAndDeleteAll(threadId, messageRecord.individualRecipient)
|
||||||
.onSuccess {
|
.onSuccess {
|
||||||
|
// At this point the server side messages have been successfully deleted..
|
||||||
showMessage("Successfully banned user and deleted all their messages")
|
showMessage("Successfully banned user and deleted all their messages")
|
||||||
|
|
||||||
|
// ..so we can now delete all their messages in this thread from local storage & remove the views.
|
||||||
|
repository.deleteAllLocalMessagesInThreadFromSenderOfMessage(messageRecord)
|
||||||
}
|
}
|
||||||
.onFailure {
|
.onFailure {
|
||||||
showMessage("Couldn't execute request due to error: $it")
|
showMessage("Couldn't execute request due to error: $it")
|
||||||
|
@ -65,7 +65,7 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p
|
|||||||
menu.findItem(R.id.menu_context_copy).isVisible = !containsControlMessage && hasText
|
menu.findItem(R.id.menu_context_copy).isVisible = !containsControlMessage && hasText
|
||||||
// Copy Session ID
|
// Copy Session ID
|
||||||
menu.findItem(R.id.menu_context_copy_public_key).isVisible =
|
menu.findItem(R.id.menu_context_copy_public_key).isVisible =
|
||||||
(thread.isGroupRecipient && !thread.isOpenGroupRecipient && selectedItems.size == 1 && firstMessage.individualRecipient.address.toString() != userPublicKey)
|
(thread.isGroupRecipient && !thread.isCommunityRecipient && selectedItems.size == 1 && firstMessage.individualRecipient.address.toString() != userPublicKey)
|
||||||
// Message detail
|
// Message detail
|
||||||
menu.findItem(R.id.menu_message_details).isVisible = selectedItems.size == 1
|
menu.findItem(R.id.menu_message_details).isVisible = selectedItems.size == 1
|
||||||
// Resend
|
// Resend
|
||||||
|
@ -50,7 +50,7 @@ object ConversationMenuHelper {
|
|||||||
) {
|
) {
|
||||||
// Prepare
|
// Prepare
|
||||||
menu.clear()
|
menu.clear()
|
||||||
val isOpenGroup = thread.isOpenGroupRecipient
|
val isOpenGroup = thread.isCommunityRecipient
|
||||||
// Base menu (options that should always be present)
|
// Base menu (options that should always be present)
|
||||||
inflater.inflate(R.menu.menu_conversation, menu)
|
inflater.inflate(R.menu.menu_conversation, menu)
|
||||||
// Expiring messages
|
// Expiring messages
|
||||||
@ -253,7 +253,7 @@ object ConversationMenuHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun copyOpenGroupUrl(context: Context, thread: Recipient) {
|
private fun copyOpenGroupUrl(context: Context, thread: Recipient) {
|
||||||
if (!thread.isOpenGroupRecipient) { return }
|
if (!thread.isCommunityRecipient) { return }
|
||||||
val listener = context as? ConversationMenuListener ?: return
|
val listener = context as? ConversationMenuListener ?: return
|
||||||
listener.copyOpenGroupUrl(thread)
|
listener.copyOpenGroupUrl(thread)
|
||||||
}
|
}
|
||||||
@ -300,7 +300,7 @@ object ConversationMenuHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun inviteContacts(context: Context, thread: Recipient) {
|
private fun inviteContacts(context: Context, thread: Recipient) {
|
||||||
if (!thread.isOpenGroupRecipient) { return }
|
if (!thread.isCommunityRecipient) { return }
|
||||||
val intent = Intent(context, SelectContactsActivity::class.java)
|
val intent = Intent(context, SelectContactsActivity::class.java)
|
||||||
val activity = context as AppCompatActivity
|
val activity = context as AppCompatActivity
|
||||||
activity.startActivityForResult(intent, ConversationActivityV2.INVITE_CONTACTS)
|
activity.startActivityForResult(intent, ConversationActivityV2.INVITE_CONTACTS)
|
||||||
|
@ -166,7 +166,7 @@ class VisibleMessageView : LinearLayout {
|
|||||||
binding.profilePictureView.publicKey = senderSessionID
|
binding.profilePictureView.publicKey = senderSessionID
|
||||||
binding.profilePictureView.update(message.individualRecipient)
|
binding.profilePictureView.update(message.individualRecipient)
|
||||||
binding.profilePictureView.setOnClickListener {
|
binding.profilePictureView.setOnClickListener {
|
||||||
if (thread.isOpenGroupRecipient) {
|
if (thread.isCommunityRecipient) {
|
||||||
val openGroup = lokiThreadDb.getOpenGroupChat(threadID)
|
val openGroup = lokiThreadDb.getOpenGroupChat(threadID)
|
||||||
if (IdPrefix.fromValue(senderSessionID) == IdPrefix.BLINDED && openGroup?.canWrite == true) {
|
if (IdPrefix.fromValue(senderSessionID) == IdPrefix.BLINDED && openGroup?.canWrite == true) {
|
||||||
// TODO: support v2 soon
|
// TODO: support v2 soon
|
||||||
@ -179,7 +179,7 @@ class VisibleMessageView : LinearLayout {
|
|||||||
maybeShowUserDetails(senderSessionID, threadID)
|
maybeShowUserDetails(senderSessionID, threadID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (thread.isOpenGroupRecipient) {
|
if (thread.isCommunityRecipient) {
|
||||||
val openGroup = lokiThreadDb.getOpenGroupChat(threadID) ?: return
|
val openGroup = lokiThreadDb.getOpenGroupChat(threadID) ?: return
|
||||||
var standardPublicKey = ""
|
var standardPublicKey = ""
|
||||||
var blindedPublicKey: String? = null
|
var blindedPublicKey: String? = null
|
||||||
@ -195,7 +195,7 @@ class VisibleMessageView : LinearLayout {
|
|||||||
}
|
}
|
||||||
binding.senderNameTextView.isVisible = !message.isOutgoing && (isStartOfMessageCluster && (isGroupThread || snIsSelected))
|
binding.senderNameTextView.isVisible = !message.isOutgoing && (isStartOfMessageCluster && (isGroupThread || snIsSelected))
|
||||||
val contactContext =
|
val contactContext =
|
||||||
if (thread.isOpenGroupRecipient) ContactContext.OPEN_GROUP else ContactContext.REGULAR
|
if (thread.isCommunityRecipient) ContactContext.OPEN_GROUP else ContactContext.REGULAR
|
||||||
binding.senderNameTextView.text = contact?.displayName(contactContext) ?: senderSessionID
|
binding.senderNameTextView.text = contact?.displayName(contactContext) ?: senderSessionID
|
||||||
|
|
||||||
// Unread marker
|
// Unread marker
|
||||||
|
@ -26,6 +26,7 @@ import androidx.annotation.NonNull;
|
|||||||
import net.zetetic.database.sqlcipher.SQLiteDatabase;
|
import net.zetetic.database.sqlcipher.SQLiteDatabase;
|
||||||
|
|
||||||
import org.session.libsession.utilities.WindowDebouncer;
|
import org.session.libsession.utilities.WindowDebouncer;
|
||||||
|
import org.session.libsignal.utilities.Log;
|
||||||
import org.thoughtcrime.securesms.ApplicationContext;
|
import org.thoughtcrime.securesms.ApplicationContext;
|
||||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||||
|
|
||||||
@ -77,11 +78,11 @@ public abstract class Database {
|
|||||||
notifyConversationListListeners();
|
notifyConversationListListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setNotifyConverationListeners(Cursor cursor, long threadId) {
|
protected void setNotifyConversationListeners(Cursor cursor, long threadId) {
|
||||||
cursor.setNotificationUri(context.getContentResolver(), DatabaseContentProviders.Conversation.getUriForThread(threadId));
|
cursor.setNotificationUri(context.getContentResolver(), DatabaseContentProviders.Conversation.getUriForThread(threadId));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setNotifyConverationListListeners(Cursor cursor) {
|
protected void setNotifyConversationListListeners(Cursor cursor) {
|
||||||
cursor.setNotificationUri(context.getContentResolver(), DatabaseContentProviders.ConversationList.CONTENT_URI);
|
cursor.setNotificationUri(context.getContentResolver(), DatabaseContentProviders.ConversationList.CONTENT_URI);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,8 +6,8 @@ import android.database.Cursor
|
|||||||
import org.session.libsession.messaging.messages.ExpirationConfiguration
|
import org.session.libsession.messaging.messages.ExpirationConfiguration
|
||||||
import org.session.libsession.messaging.messages.ExpirationDatabaseMetadata
|
import org.session.libsession.messaging.messages.ExpirationDatabaseMetadata
|
||||||
import org.session.libsession.utilities.GroupUtil.CLOSED_GROUP_PREFIX
|
import org.session.libsession.utilities.GroupUtil.CLOSED_GROUP_PREFIX
|
||||||
import org.session.libsession.utilities.GroupUtil.OPEN_GROUP_INBOX_PREFIX
|
import org.session.libsession.utilities.GroupUtil.COMMUNITY_INBOX_PREFIX
|
||||||
import org.session.libsession.utilities.GroupUtil.OPEN_GROUP_PREFIX
|
import org.session.libsession.utilities.GroupUtil.COMMUNITY_PREFIX
|
||||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
||||||
|
|
||||||
class ExpirationConfigurationDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper) {
|
class ExpirationConfigurationDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper) {
|
||||||
@ -38,8 +38,8 @@ class ExpirationConfigurationDatabase(context: Context, helper: SQLCipherOpenHel
|
|||||||
INSERT INTO $TABLE_NAME ($THREAD_ID) SELECT ${ThreadDatabase.TABLE_NAME}.${ThreadDatabase.ID}
|
INSERT INTO $TABLE_NAME ($THREAD_ID) SELECT ${ThreadDatabase.TABLE_NAME}.${ThreadDatabase.ID}
|
||||||
FROM ${ThreadDatabase.TABLE_NAME}, ${RecipientDatabase.TABLE_NAME}
|
FROM ${ThreadDatabase.TABLE_NAME}, ${RecipientDatabase.TABLE_NAME}
|
||||||
WHERE ${ThreadDatabase.TABLE_NAME}.${ThreadDatabase.ADDRESS} NOT LIKE '$CLOSED_GROUP_PREFIX%'
|
WHERE ${ThreadDatabase.TABLE_NAME}.${ThreadDatabase.ADDRESS} NOT LIKE '$CLOSED_GROUP_PREFIX%'
|
||||||
AND ${ThreadDatabase.TABLE_NAME}.${ThreadDatabase.ADDRESS} NOT LIKE '$OPEN_GROUP_PREFIX%'
|
AND ${ThreadDatabase.TABLE_NAME}.${ThreadDatabase.ADDRESS} NOT LIKE '$COMMUNITY_PREFIX%'
|
||||||
AND ${ThreadDatabase.TABLE_NAME}.${ThreadDatabase.ADDRESS} NOT LIKE '$OPEN_GROUP_INBOX_PREFIX%'
|
AND ${ThreadDatabase.TABLE_NAME}.${ThreadDatabase.ADDRESS} NOT LIKE '$COMMUNITY_INBOX_PREFIX%'
|
||||||
AND EXISTS (SELECT ${RecipientDatabase.EXPIRE_MESSAGES} FROM ${RecipientDatabase.TABLE_NAME} WHERE ${ThreadDatabase.TABLE_NAME}.${ThreadDatabase.ADDRESS} = ${RecipientDatabase.TABLE_NAME}.${RecipientDatabase.ADDRESS} AND ${RecipientDatabase.EXPIRE_MESSAGES} > 0)
|
AND EXISTS (SELECT ${RecipientDatabase.EXPIRE_MESSAGES} FROM ${RecipientDatabase.TABLE_NAME} WHERE ${ThreadDatabase.TABLE_NAME}.${ThreadDatabase.ADDRESS} = ${RecipientDatabase.TABLE_NAME}.${RecipientDatabase.ADDRESS} AND ${RecipientDatabase.EXPIRE_MESSAGES} > 0)
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import android.content.ContentValues
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import net.zetetic.database.sqlcipher.SQLiteDatabase.CONFLICT_REPLACE
|
import net.zetetic.database.sqlcipher.SQLiteDatabase.CONFLICT_REPLACE
|
||||||
import org.session.libsignal.database.LokiMessageDatabaseProtocol
|
import org.session.libsignal.database.LokiMessageDatabaseProtocol
|
||||||
|
import org.session.libsignal.utilities.Log
|
||||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
||||||
|
|
||||||
class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiMessageDatabaseProtocol {
|
class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiMessageDatabaseProtocol {
|
||||||
@ -72,7 +73,12 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab
|
|||||||
"${Companion.messageID} = ? AND $messageType = ?",
|
"${Companion.messageID} = ? AND $messageType = ?",
|
||||||
arrayOf(messageID.toString(), (if (isSms) SMS_TYPE else MMS_TYPE).toString())) { cursor ->
|
arrayOf(messageID.toString(), (if (isSms) SMS_TYPE else MMS_TYPE).toString())) { cursor ->
|
||||||
cursor.getInt(serverID).toLong()
|
cursor.getInt(serverID).toLong()
|
||||||
} ?: return
|
}
|
||||||
|
|
||||||
|
if (serverID == null) {
|
||||||
|
Log.w(this::class.simpleName, "Could not get server ID to delete message with ID: $messageID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
database.beginTransaction()
|
database.beginTransaction()
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ public class MediaDatabase extends Database {
|
|||||||
public Cursor getGalleryMediaForThread(long threadId) {
|
public Cursor getGalleryMediaForThread(long threadId) {
|
||||||
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||||
Cursor cursor = database.rawQuery(GALLERY_MEDIA_QUERY, new String[]{threadId+""});
|
Cursor cursor = database.rawQuery(GALLERY_MEDIA_QUERY, new String[]{threadId+""});
|
||||||
setNotifyConverationListeners(cursor, threadId);
|
setNotifyConversationListeners(cursor, threadId);
|
||||||
return cursor;
|
return cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,7 +83,7 @@ public class MediaDatabase extends Database {
|
|||||||
public Cursor getDocumentMediaForThread(long threadId) {
|
public Cursor getDocumentMediaForThread(long threadId) {
|
||||||
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||||
Cursor cursor = database.rawQuery(DOCUMENT_MEDIA_QUERY, new String[]{threadId+""});
|
Cursor cursor = database.rawQuery(DOCUMENT_MEDIA_QUERY, new String[]{threadId+""});
|
||||||
setNotifyConverationListeners(cursor, threadId);
|
setNotifyConversationListeners(cursor, threadId);
|
||||||
return cursor;
|
return cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,9 +19,9 @@ package org.thoughtcrime.securesms.database
|
|||||||
import android.content.ContentValues
|
import android.content.ContentValues
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.provider.ContactsContract.CommonDataKinds.BaseTypes
|
|
||||||
import com.annimon.stream.Stream
|
import com.annimon.stream.Stream
|
||||||
import com.google.android.mms.pdu_alt.PduHeaders
|
import com.google.android.mms.pdu_alt.PduHeaders
|
||||||
|
import org.apache.commons.lang3.StringUtils
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
import org.json.JSONException
|
import org.json.JSONException
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
@ -214,7 +214,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
|
|||||||
|
|
||||||
fun getMessage(messageId: Long): Cursor {
|
fun getMessage(messageId: Long): Cursor {
|
||||||
val cursor = rawQuery(RAW_ID_WHERE, arrayOf(messageId.toString()))
|
val cursor = rawQuery(RAW_ID_WHERE, arrayOf(messageId.toString()))
|
||||||
setNotifyConverationListeners(cursor, getThreadIdForMessage(messageId))
|
setNotifyConversationListeners(cursor, getThreadIdForMessage(messageId))
|
||||||
return cursor
|
return cursor
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -859,8 +859,10 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
|
|||||||
*/
|
*/
|
||||||
private fun deleteMessages(messageIds: Array<String?>) {
|
private fun deleteMessages(messageIds: Array<String?>) {
|
||||||
if (messageIds.isEmpty()) {
|
if (messageIds.isEmpty()) {
|
||||||
|
Log.w(TAG, "No message Ids provided to MmsDatabase.deleteMessages - aborting delete operation!")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't need thread IDs
|
// don't need thread IDs
|
||||||
val queryBuilder = StringBuilder()
|
val queryBuilder = StringBuilder()
|
||||||
for (i in messageIds.indices) {
|
for (i in messageIds.indices) {
|
||||||
@ -883,6 +885,8 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
|
|||||||
notifyStickerPackListeners()
|
notifyStickerPackListeners()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Caution: The bool returned from `deleteMessage` is NOT "Was the message successfully deleted?"
|
||||||
|
// - it is "Was the thread deleted because removing that message resulted in an empty thread"!
|
||||||
override fun deleteMessage(messageId: Long): Boolean {
|
override fun deleteMessage(messageId: Long): Boolean {
|
||||||
val threadId = getThreadIdForMessage(messageId)
|
val threadId = getThreadIdForMessage(messageId)
|
||||||
val attachmentDatabase = get(context).attachmentDatabase()
|
val attachmentDatabase = get(context).attachmentDatabase()
|
||||||
@ -899,14 +903,15 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun deleteMessages(messageIds: LongArray, threadId: Long): Boolean {
|
override fun deleteMessages(messageIds: LongArray, threadId: Long): Boolean {
|
||||||
val attachmentDatabase = get(context).attachmentDatabase()
|
val argsArray = messageIds.map { "?" }
|
||||||
val groupReceiptDatabase = get(context).groupReceiptDatabase()
|
val argValues = messageIds.map { it.toString() }.toTypedArray()
|
||||||
|
|
||||||
queue(Runnable { attachmentDatabase.deleteAttachmentsForMessages(messageIds) })
|
val db = databaseHelper.writableDatabase
|
||||||
groupReceiptDatabase.deleteRowsForMessages(messageIds)
|
db.delete(
|
||||||
|
TABLE_NAME,
|
||||||
val database = databaseHelper.writableDatabase
|
ID + " IN (" + StringUtils.join(argsArray, ',') + ")",
|
||||||
database!!.delete(TABLE_NAME, ID_IN, arrayOf(messageIds.joinToString(",")))
|
argValues
|
||||||
|
)
|
||||||
|
|
||||||
val threadDeleted = get(context).threadDatabase().update(threadId, false, true)
|
val threadDeleted = get(context).threadDatabase().update(threadId, false, true)
|
||||||
notifyConversationListeners(threadId)
|
notifyConversationListeners(threadId)
|
||||||
|
@ -183,7 +183,7 @@ public class MmsSmsDatabase extends Database {
|
|||||||
String limitStr = limit > 0 || offset > 0 ? offset + ", " + limit : null;
|
String limitStr = limit > 0 || offset > 0 ? offset + ", " + limit : null;
|
||||||
|
|
||||||
Cursor cursor = queryTables(PROJECTION, selection, order, limitStr);
|
Cursor cursor = queryTables(PROJECTION, selection, order, limitStr);
|
||||||
setNotifyConverationListeners(cursor, threadId);
|
setNotifyConversationListeners(cursor, threadId);
|
||||||
|
|
||||||
return cursor;
|
return cursor;
|
||||||
}
|
}
|
||||||
@ -209,6 +209,44 @@ public class MmsSmsDatabase extends Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Builds up and returns a list of all all the messages sent by this user in the given thread.
|
||||||
|
// Used to do a pass through our local database to remove records when a user has "Ban & Delete"
|
||||||
|
// called on them in a Community.
|
||||||
|
public Set<MessageRecord> getAllMessageRecordsFromSenderInThread(long threadId, String serializedAuthor) {
|
||||||
|
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId + " AND " + MmsSmsColumns.ADDRESS + " = \"" + serializedAuthor + "\"";
|
||||||
|
Set<MessageRecord> identifiedMessages = new HashSet<MessageRecord>();
|
||||||
|
|
||||||
|
// Try everything with resources so that they auto-close on end of scope
|
||||||
|
try (Cursor cursor = queryTables(PROJECTION, selection, null, null)) {
|
||||||
|
try (MmsSmsDatabase.Reader reader = readerFor(cursor)) {
|
||||||
|
MessageRecord messageRecord;
|
||||||
|
while ((messageRecord = reader.getNext()) != null) {
|
||||||
|
identifiedMessages.add(messageRecord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return identifiedMessages;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version of the above `getAllMessageRecordsFromSenderInThread` method that returns the message
|
||||||
|
// Ids rather than the set of MessageRecords - currently unused by potentially useful in the future.
|
||||||
|
public Set<Long> getAllMessageIdsFromSenderInThread(long threadId, String serializedAuthor) {
|
||||||
|
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId + " AND " + MmsSmsColumns.ADDRESS + " = \"" + serializedAuthor + "\"";
|
||||||
|
|
||||||
|
Set<Long> identifiedMessages = new HashSet<Long>();
|
||||||
|
|
||||||
|
// Try everything with resources so that they auto-close on end of scope
|
||||||
|
try (Cursor cursor = queryTables(PROJECTION, selection, null, null)) {
|
||||||
|
try (MmsSmsDatabase.Reader reader = readerFor(cursor)) {
|
||||||
|
MessageRecord messageRecord;
|
||||||
|
while ((messageRecord = reader.getNext()) != null) {
|
||||||
|
identifiedMessages.add(messageRecord.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return identifiedMessages;
|
||||||
|
}
|
||||||
|
|
||||||
public long getLastSentMessageFromSender(long threadId, String serializedAuthor) {
|
public long getLastSentMessageFromSender(long threadId, String serializedAuthor) {
|
||||||
String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " DESC";
|
String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " DESC";
|
||||||
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;
|
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package org.thoughtcrime.securesms.database;
|
package org.thoughtcrime.securesms.database;
|
||||||
|
|
||||||
import static org.session.libsession.utilities.GroupUtil.OPEN_GROUP_PREFIX;
|
import static org.session.libsession.utilities.GroupUtil.COMMUNITY_PREFIX;
|
||||||
|
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@ -123,18 +123,18 @@ public class RecipientDatabase extends Database {
|
|||||||
public static String getUpdateApprovedCommand() {
|
public static String getUpdateApprovedCommand() {
|
||||||
return "UPDATE "+ TABLE_NAME + " " +
|
return "UPDATE "+ TABLE_NAME + " " +
|
||||||
"SET " + APPROVED + " = 1, " + APPROVED_ME + " = 1 " +
|
"SET " + APPROVED + " = 1, " + APPROVED_ME + " = 1 " +
|
||||||
"WHERE " + ADDRESS + " NOT LIKE '" + OPEN_GROUP_PREFIX + "%'";
|
"WHERE " + ADDRESS + " NOT LIKE '" + COMMUNITY_PREFIX + "%'";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getUpdateResetApprovedCommand() {
|
public static String getUpdateResetApprovedCommand() {
|
||||||
return "UPDATE "+ TABLE_NAME + " " +
|
return "UPDATE "+ TABLE_NAME + " " +
|
||||||
"SET " + APPROVED + " = 0, " + APPROVED_ME + " = 0 " +
|
"SET " + APPROVED + " = 0, " + APPROVED_ME + " = 0 " +
|
||||||
"WHERE " + ADDRESS + " NOT LIKE '" + OPEN_GROUP_PREFIX + "%'";
|
"WHERE " + ADDRESS + " NOT LIKE '" + COMMUNITY_PREFIX + "%'";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getUpdateApprovedSelectConversations() {
|
public static String getUpdateApprovedSelectConversations() {
|
||||||
return "UPDATE "+ TABLE_NAME + " SET "+APPROVED+" = 1, "+APPROVED_ME+" = 1 "+
|
return "UPDATE "+ TABLE_NAME + " SET "+APPROVED+" = 1, "+APPROVED_ME+" = 1 "+
|
||||||
"WHERE "+ADDRESS+ " NOT LIKE '"+OPEN_GROUP_PREFIX+"%' " +
|
"WHERE "+ADDRESS+ " NOT LIKE '"+ COMMUNITY_PREFIX +"%' " +
|
||||||
"AND ("+ADDRESS+" IN (SELECT "+ThreadDatabase.TABLE_NAME+"."+ThreadDatabase.ADDRESS+" FROM "+ThreadDatabase.TABLE_NAME+" WHERE ("+ThreadDatabase.MESSAGE_COUNT+" != 0) "+
|
"AND ("+ADDRESS+" IN (SELECT "+ThreadDatabase.TABLE_NAME+"."+ThreadDatabase.ADDRESS+" FROM "+ThreadDatabase.TABLE_NAME+" WHERE ("+ThreadDatabase.MESSAGE_COUNT+" != 0) "+
|
||||||
"OR "+ADDRESS+" IN (SELECT "+GroupDatabase.TABLE_NAME+"."+GroupDatabase.ADMINS+" FROM "+GroupDatabase.TABLE_NAME+")))";
|
"OR "+ADDRESS+" IN (SELECT "+GroupDatabase.TABLE_NAME+"."+GroupDatabase.ADMINS+" FROM "+GroupDatabase.TABLE_NAME+")))";
|
||||||
}
|
}
|
||||||
|
@ -119,7 +119,7 @@ public class SearchDatabase extends Database {
|
|||||||
int queryLimit = Math.min(query.length()*50,500);
|
int queryLimit = Math.min(query.length()*50,500);
|
||||||
|
|
||||||
Cursor cursor = db.rawQuery(MESSAGES_QUERY, new String[] { prefixQuery, prefixQuery, String.valueOf(queryLimit) });
|
Cursor cursor = db.rawQuery(MESSAGES_QUERY, new String[] { prefixQuery, prefixQuery, String.valueOf(queryLimit) });
|
||||||
setNotifyConverationListListeners(cursor);
|
setNotifyConversationListListeners(cursor);
|
||||||
return cursor;
|
return cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,7 +128,7 @@ public class SearchDatabase extends Database {
|
|||||||
String prefixQuery = adjustQuery(query);
|
String prefixQuery = adjustQuery(query);
|
||||||
|
|
||||||
Cursor cursor = db.rawQuery(MESSAGES_FOR_THREAD_QUERY, new String[] { prefixQuery, String.valueOf(threadId), prefixQuery, String.valueOf(threadId) });
|
Cursor cursor = db.rawQuery(MESSAGES_FOR_THREAD_QUERY, new String[] { prefixQuery, String.valueOf(threadId), prefixQuery, String.valueOf(threadId) });
|
||||||
setNotifyConverationListListeners(cursor);
|
setNotifyConversationListListeners(cursor);
|
||||||
return cursor;
|
return cursor;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -621,10 +621,12 @@ public class SmsDatabase extends MessagingDatabase {
|
|||||||
public Cursor getMessageCursor(long messageId) {
|
public Cursor getMessageCursor(long messageId) {
|
||||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||||
Cursor cursor = db.query(TABLE_NAME, MESSAGE_PROJECTION, ID_WHERE, new String[] {messageId + ""}, null, null, null);
|
Cursor cursor = db.query(TABLE_NAME, MESSAGE_PROJECTION, ID_WHERE, new String[] {messageId + ""}, null, null, null);
|
||||||
setNotifyConverationListeners(cursor, getThreadIdForMessage(messageId));
|
setNotifyConversationListeners(cursor, getThreadIdForMessage(messageId));
|
||||||
return cursor;
|
return cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Caution: The bool returned from `deleteMessage` is NOT "Was the message successfully deleted?"
|
||||||
|
// - it is "Was the thread deleted because removing that message resulted in an empty thread"!
|
||||||
@Override
|
@Override
|
||||||
public boolean deleteMessage(long messageId) {
|
public boolean deleteMessage(long messageId) {
|
||||||
Log.i("MessageDatabase", "Deleting: " + messageId);
|
Log.i("MessageDatabase", "Deleting: " + messageId);
|
||||||
@ -645,9 +647,6 @@ public class SmsDatabase extends MessagingDatabase {
|
|||||||
argValues[i] = (messageIds[i] + "");
|
argValues[i] = (messageIds[i] + "");
|
||||||
}
|
}
|
||||||
|
|
||||||
String combinedMessageIdArgss = StringUtils.join(messageIds, ',');
|
|
||||||
String combinedMessageIds = StringUtils.join(messageIds, ',');
|
|
||||||
Log.i("MessageDatabase", "Deleting: " + combinedMessageIds);
|
|
||||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||||
db.delete(
|
db.delete(
|
||||||
TABLE_NAME,
|
TABLE_NAME,
|
||||||
|
@ -121,7 +121,7 @@ open class Storage(
|
|||||||
)
|
)
|
||||||
volatile.set(newVolatileParams)
|
volatile.set(newVolatileParams)
|
||||||
}
|
}
|
||||||
} else if (address.isOpenGroup) {
|
} else if (address.isCommunity) {
|
||||||
// these should be added on the group join / group info fetch
|
// these should be added on the group join / group info fetch
|
||||||
Log.w("Loki", "Thread created called for open group address, not adding any extra information")
|
Log.w("Loki", "Thread created called for open group address, not adding any extra information")
|
||||||
}
|
}
|
||||||
@ -152,7 +152,7 @@ open class Storage(
|
|||||||
val sessionId = GroupUtil.doubleDecodeGroupId(address.serialize())
|
val sessionId = GroupUtil.doubleDecodeGroupId(address.serialize())
|
||||||
volatile.eraseLegacyClosedGroup(sessionId)
|
volatile.eraseLegacyClosedGroup(sessionId)
|
||||||
groups.eraseLegacyGroup(sessionId)
|
groups.eraseLegacyGroup(sessionId)
|
||||||
} else if (address.isOpenGroup) {
|
} else if (address.isCommunity) {
|
||||||
// these should be removed in the group leave / handling new configs
|
// these should be removed in the group leave / handling new configs
|
||||||
Log.w("Loki", "Thread delete called for open group address, expecting to be handled elsewhere")
|
Log.w("Loki", "Thread delete called for open group address, expecting to be handled elsewhere")
|
||||||
}
|
}
|
||||||
@ -257,7 +257,7 @@ open class Storage(
|
|||||||
// recipient closed group
|
// recipient closed group
|
||||||
recipient.isClosedGroupRecipient -> config.getOrConstructLegacyGroup(GroupUtil.doubleDecodeGroupId(recipient.address.serialize()))
|
recipient.isClosedGroupRecipient -> config.getOrConstructLegacyGroup(GroupUtil.doubleDecodeGroupId(recipient.address.serialize()))
|
||||||
// recipient is open group
|
// recipient is open group
|
||||||
recipient.isOpenGroupRecipient -> {
|
recipient.isCommunityRecipient -> {
|
||||||
val openGroupJoinUrl = getOpenGroup(threadId)?.joinURL ?: return
|
val openGroupJoinUrl = getOpenGroup(threadId)?.joinURL ?: return
|
||||||
BaseCommunityInfo.parseFullUrl(openGroupJoinUrl)?.let { (base, room, pubKey) ->
|
BaseCommunityInfo.parseFullUrl(openGroupJoinUrl)?.let { (base, room, pubKey) ->
|
||||||
config.getOrConstructCommunity(base, room, pubKey)
|
config.getOrConstructCommunity(base, room, pubKey)
|
||||||
@ -327,7 +327,7 @@ open class Storage(
|
|||||||
setRecipientApprovedMe(targetRecipient, true)
|
setRecipientApprovedMe(targetRecipient, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (message.threadID == null && !targetRecipient.isOpenGroupRecipient) {
|
if (message.threadID == null && !targetRecipient.isCommunityRecipient) {
|
||||||
// open group recipients should explicitly create threads
|
// open group recipients should explicitly create threads
|
||||||
message.threadID = getOrCreateThreadIdFor(targetAddress)
|
message.threadID = getOrCreateThreadIdFor(targetAddress)
|
||||||
}
|
}
|
||||||
@ -1289,7 +1289,7 @@ open class Storage(
|
|||||||
priority = if (isPinned) PRIORITY_PINNED else ConfigBase.PRIORITY_VISIBLE
|
priority = if (isPinned) PRIORITY_PINNED else ConfigBase.PRIORITY_VISIBLE
|
||||||
)
|
)
|
||||||
groups.set(newGroupInfo)
|
groups.set(newGroupInfo)
|
||||||
} else if (threadRecipient.isOpenGroupRecipient) {
|
} else if (threadRecipient.isCommunityRecipient) {
|
||||||
val openGroup = getOpenGroup(threadID) ?: return
|
val openGroup = getOpenGroup(threadID) ?: return
|
||||||
val (baseUrl, room, pubKeyHex) = BaseCommunityInfo.parseFullUrl(openGroup.joinURL) ?: return
|
val (baseUrl, room, pubKeyHex) = BaseCommunityInfo.parseFullUrl(openGroup.joinURL) ?: return
|
||||||
val newGroupInfo = groups.getOrConstructCommunityInfo(baseUrl, room, Hex.toStringCondensed(pubKeyHex)).copy (
|
val newGroupInfo = groups.getOrConstructCommunityInfo(baseUrl, room, Hex.toStringCondensed(pubKeyHex)).copy (
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
package org.thoughtcrime.securesms.database;
|
package org.thoughtcrime.securesms.database;
|
||||||
|
|
||||||
import static org.session.libsession.utilities.GroupUtil.CLOSED_GROUP_PREFIX;
|
import static org.session.libsession.utilities.GroupUtil.CLOSED_GROUP_PREFIX;
|
||||||
import static org.session.libsession.utilities.GroupUtil.OPEN_GROUP_PREFIX;
|
import static org.session.libsession.utilities.GroupUtil.COMMUNITY_PREFIX;
|
||||||
import static org.thoughtcrime.securesms.database.GroupDatabase.GROUP_ID;
|
import static org.thoughtcrime.securesms.database.GroupDatabase.GROUP_ID;
|
||||||
|
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
@ -427,7 +427,7 @@ public class ThreadDatabase extends Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Cursor cursor = cursors.size() > 1 ? new MergeCursor(cursors.toArray(new Cursor[cursors.size()])) : cursors.get(0);
|
Cursor cursor = cursors.size() > 1 ? new MergeCursor(cursors.toArray(new Cursor[cursors.size()])) : cursors.get(0);
|
||||||
setNotifyConverationListListeners(cursor);
|
setNotifyConversationListListeners(cursor);
|
||||||
return cursor;
|
return cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -491,7 +491,7 @@ public class ThreadDatabase extends Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Cursor getConversationList() {
|
public Cursor getConversationList() {
|
||||||
String where = "(" + MESSAGE_COUNT + " != 0 OR " + GroupDatabase.TABLE_NAME + "." + GROUP_ID + " LIKE '" + OPEN_GROUP_PREFIX + "%') " +
|
String where = "(" + MESSAGE_COUNT + " != 0 OR " + GroupDatabase.TABLE_NAME + "." + GROUP_ID + " LIKE '" + COMMUNITY_PREFIX + "%') " +
|
||||||
"AND " + ARCHIVED + " = 0 ";
|
"AND " + ARCHIVED + " = 0 ";
|
||||||
return getConversationList(where);
|
return getConversationList(where);
|
||||||
}
|
}
|
||||||
@ -502,7 +502,7 @@ public class ThreadDatabase extends Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Cursor getApprovedConversationList() {
|
public Cursor getApprovedConversationList() {
|
||||||
String where = "((" + HAS_SENT + " = 1 OR " + RecipientDatabase.APPROVED + " = 1 OR "+ GroupDatabase.TABLE_NAME +"."+GROUP_ID+" LIKE '"+CLOSED_GROUP_PREFIX+"%') OR " + GroupDatabase.TABLE_NAME + "." + GROUP_ID + " LIKE '" + OPEN_GROUP_PREFIX + "%') " +
|
String where = "((" + HAS_SENT + " = 1 OR " + RecipientDatabase.APPROVED + " = 1 OR "+ GroupDatabase.TABLE_NAME +"."+GROUP_ID+" LIKE '"+CLOSED_GROUP_PREFIX+"%') OR " + GroupDatabase.TABLE_NAME + "." + GROUP_ID + " LIKE '" + COMMUNITY_PREFIX + "%') " +
|
||||||
"AND " + ARCHIVED + " = 0 ";
|
"AND " + ARCHIVED + " = 0 ";
|
||||||
return getConversationList(where);
|
return getConversationList(where);
|
||||||
}
|
}
|
||||||
@ -516,7 +516,7 @@ public class ThreadDatabase extends Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Cursor getArchivedConversationList() {
|
public Cursor getArchivedConversationList() {
|
||||||
String where = "(" + MESSAGE_COUNT + " != 0 OR " + GroupDatabase.TABLE_NAME + "." + GROUP_ID + " LIKE '" + OPEN_GROUP_PREFIX + "%') " +
|
String where = "(" + MESSAGE_COUNT + " != 0 OR " + GroupDatabase.TABLE_NAME + "." + GROUP_ID + " LIKE '" + COMMUNITY_PREFIX + "%') " +
|
||||||
"AND " + ARCHIVED + " = 1 ";
|
"AND " + ARCHIVED + " = 1 ";
|
||||||
return getConversationList(where);
|
return getConversationList(where);
|
||||||
}
|
}
|
||||||
@ -526,7 +526,7 @@ public class ThreadDatabase extends Database {
|
|||||||
String query = createQuery(where, 0);
|
String query = createQuery(where, 0);
|
||||||
Cursor cursor = db.rawQuery(query, null);
|
Cursor cursor = db.rawQuery(query, null);
|
||||||
|
|
||||||
setNotifyConverationListListeners(cursor);
|
setNotifyConversationListListeners(cursor);
|
||||||
|
|
||||||
return cursor;
|
return cursor;
|
||||||
}
|
}
|
||||||
@ -547,7 +547,7 @@ public class ThreadDatabase extends Database {
|
|||||||
// edge case where we set the last seen time for a conversation before it loads messages (joining community for example)
|
// edge case where we set the last seen time for a conversation before it loads messages (joining community for example)
|
||||||
MmsSmsDatabase mmsSmsDatabase = DatabaseComponent.get(context).mmsSmsDatabase();
|
MmsSmsDatabase mmsSmsDatabase = DatabaseComponent.get(context).mmsSmsDatabase();
|
||||||
Recipient forThreadId = getRecipientForThreadId(threadId);
|
Recipient forThreadId = getRecipientForThreadId(threadId);
|
||||||
if (mmsSmsDatabase.getConversationCount(threadId) <= 0 && forThreadId != null && forThreadId.isOpenGroupRecipient()) return false;
|
if (mmsSmsDatabase.getConversationCount(threadId) <= 0 && forThreadId != null && forThreadId.isCommunityRecipient()) return false;
|
||||||
|
|
||||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||||
|
|
||||||
@ -822,7 +822,7 @@ public class ThreadDatabase extends Database {
|
|||||||
|
|
||||||
private boolean deleteThreadOnEmpty(long threadId) {
|
private boolean deleteThreadOnEmpty(long threadId) {
|
||||||
Recipient threadRecipient = getRecipientForThreadId(threadId);
|
Recipient threadRecipient = getRecipientForThreadId(threadId);
|
||||||
return threadRecipient != null && !threadRecipient.isOpenGroupRecipient();
|
return threadRecipient != null && !threadRecipient.isCommunityRecipient();
|
||||||
}
|
}
|
||||||
|
|
||||||
private @NonNull String getFormattedBodyFor(@NonNull MessageRecord messageRecord) {
|
private @NonNull String getFormattedBodyFor(@NonNull MessageRecord messageRecord) {
|
||||||
|
@ -11,7 +11,6 @@ import dagger.hilt.android.AndroidEntryPoint
|
|||||||
import network.loki.messenger.databinding.FragmentConversationBottomSheetBinding
|
import network.loki.messenger.databinding.FragmentConversationBottomSheetBinding
|
||||||
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
||||||
import org.thoughtcrime.securesms.dependencies.ConfigFactory
|
import org.thoughtcrime.securesms.dependencies.ConfigFactory
|
||||||
import org.thoughtcrime.securesms.util.UiModeUtilities
|
|
||||||
import org.thoughtcrime.securesms.util.getConversationUnread
|
import org.thoughtcrime.securesms.util.getConversationUnread
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -75,7 +74,7 @@ class ConversationOptionsBottomSheet(private val parentContext: Context) : Botto
|
|||||||
}
|
}
|
||||||
binding.copyConversationId.visibility = if (!recipient.isGroupRecipient && !recipient.isLocalNumber) View.VISIBLE else View.GONE
|
binding.copyConversationId.visibility = if (!recipient.isGroupRecipient && !recipient.isLocalNumber) View.VISIBLE else View.GONE
|
||||||
binding.copyConversationId.setOnClickListener(this)
|
binding.copyConversationId.setOnClickListener(this)
|
||||||
binding.copyCommunityUrl.visibility = if (recipient.isOpenGroupRecipient) View.VISIBLE else View.GONE
|
binding.copyCommunityUrl.visibility = if (recipient.isCommunityRecipient) View.VISIBLE else View.GONE
|
||||||
binding.copyCommunityUrl.setOnClickListener(this)
|
binding.copyCommunityUrl.setOnClickListener(this)
|
||||||
binding.unMuteNotificationsTextView.isVisible = recipient.isMuted && !recipient.isLocalNumber
|
binding.unMuteNotificationsTextView.isVisible = recipient.isMuted && !recipient.isLocalNumber
|
||||||
binding.muteNotificationsTextView.isVisible = !recipient.isMuted && !recipient.isLocalNumber
|
binding.muteNotificationsTextView.isVisible = !recipient.isMuted && !recipient.isLocalNumber
|
||||||
|
@ -496,7 +496,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||||||
manager.setPrimaryClip(clip)
|
manager.setPrimaryClip(clip)
|
||||||
Toast.makeText(this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
else if (thread.recipient.isOpenGroupRecipient) {
|
else if (thread.recipient.isCommunityRecipient) {
|
||||||
val threadId = threadDb.getThreadIdIfExistsFor(thread.recipient) ?: return@onCopyConversationId Unit
|
val threadId = threadDb.getThreadIdIfExistsFor(thread.recipient) ?: return@onCopyConversationId Unit
|
||||||
val openGroup = DatabaseComponent.get(this@HomeActivity).lokiThreadDatabase().getOpenGroupChat(threadId) ?: return@onCopyConversationId Unit
|
val openGroup = DatabaseComponent.get(this@HomeActivity).lokiThreadDatabase().getOpenGroupChat(threadId) ?: return@onCopyConversationId Unit
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ import network.loki.messenger.R
|
|||||||
import network.loki.messenger.databinding.ActivityPathBinding
|
import network.loki.messenger.databinding.ActivityPathBinding
|
||||||
import org.session.libsession.snode.OnionRequestAPI
|
import org.session.libsession.snode.OnionRequestAPI
|
||||||
import org.session.libsession.utilities.getColorFromAttr
|
import org.session.libsession.utilities.getColorFromAttr
|
||||||
|
import org.session.libsignal.utilities.Log
|
||||||
import org.session.libsignal.utilities.Snode
|
import org.session.libsignal.utilities.Snode
|
||||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||||
import org.thoughtcrime.securesms.util.GlowViewUtilities
|
import org.thoughtcrime.securesms.util.GlowViewUtilities
|
||||||
|
@ -91,10 +91,10 @@ class UserDetailsBottomSheet: BottomSheetDialogFragment() {
|
|||||||
&& !threadRecipient.isOpenGroupInboxRecipient
|
&& !threadRecipient.isOpenGroupInboxRecipient
|
||||||
&& !threadRecipient.isOpenGroupOutboxRecipient
|
&& !threadRecipient.isOpenGroupOutboxRecipient
|
||||||
|
|
||||||
publicKeyTextView.isVisible = !threadRecipient.isOpenGroupRecipient
|
publicKeyTextView.isVisible = !threadRecipient.isCommunityRecipient
|
||||||
&& !threadRecipient.isOpenGroupInboxRecipient
|
&& !threadRecipient.isOpenGroupInboxRecipient
|
||||||
&& !threadRecipient.isOpenGroupOutboxRecipient
|
&& !threadRecipient.isOpenGroupOutboxRecipient
|
||||||
messageButton.isVisible = !threadRecipient.isOpenGroupRecipient || IdPrefix.fromValue(publicKey)?.isBlinded() == true
|
messageButton.isVisible = !threadRecipient.isCommunityRecipient || IdPrefix.fromValue(publicKey)?.isBlinded() == true
|
||||||
publicKeyTextView.text = publicKey
|
publicKeyTextView.text = publicKey
|
||||||
publicKeyTextView.setOnLongClickListener {
|
publicKeyTextView.setOnLongClickListener {
|
||||||
val clipboard =
|
val clipboard =
|
||||||
|
@ -53,7 +53,7 @@ public class MultipleRecipientNotificationBuilder extends AbstractNotificationBu
|
|||||||
public void setMostRecentSender(Recipient recipient, Recipient threadRecipient) {
|
public void setMostRecentSender(Recipient recipient, Recipient threadRecipient) {
|
||||||
String displayName = recipient.toShortString();
|
String displayName = recipient.toShortString();
|
||||||
if (threadRecipient.isGroupRecipient()) {
|
if (threadRecipient.isGroupRecipient()) {
|
||||||
displayName = getGroupDisplayName(recipient, threadRecipient.isOpenGroupRecipient());
|
displayName = getGroupDisplayName(recipient, threadRecipient.isCommunityRecipient());
|
||||||
}
|
}
|
||||||
if (privacy.isDisplayContact()) {
|
if (privacy.isDisplayContact()) {
|
||||||
setContentText(context.getString(R.string.MessageNotifier_most_recent_from_s, displayName));
|
setContentText(context.getString(R.string.MessageNotifier_most_recent_from_s, displayName));
|
||||||
@ -79,7 +79,7 @@ public class MultipleRecipientNotificationBuilder extends AbstractNotificationBu
|
|||||||
public void addMessageBody(@NonNull Recipient sender, Recipient threadRecipient, @Nullable CharSequence body) {
|
public void addMessageBody(@NonNull Recipient sender, Recipient threadRecipient, @Nullable CharSequence body) {
|
||||||
String displayName = sender.toShortString();
|
String displayName = sender.toShortString();
|
||||||
if (threadRecipient.isGroupRecipient()) {
|
if (threadRecipient.isGroupRecipient()) {
|
||||||
displayName = getGroupDisplayName(sender, threadRecipient.isOpenGroupRecipient());
|
displayName = getGroupDisplayName(sender, threadRecipient.isCommunityRecipient());
|
||||||
}
|
}
|
||||||
if (privacy.isDisplayMessage()) {
|
if (privacy.isDisplayMessage()) {
|
||||||
SpannableStringBuilder builder = new SpannableStringBuilder();
|
SpannableStringBuilder builder = new SpannableStringBuilder();
|
||||||
|
@ -125,7 +125,7 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
|
|||||||
SpannableStringBuilder stringBuilder = new SpannableStringBuilder();
|
SpannableStringBuilder stringBuilder = new SpannableStringBuilder();
|
||||||
|
|
||||||
if (privacy.isDisplayContact() && threadRecipient.isGroupRecipient()) {
|
if (privacy.isDisplayContact() && threadRecipient.isGroupRecipient()) {
|
||||||
String displayName = getGroupDisplayName(individualRecipient, threadRecipient.isOpenGroupRecipient());
|
String displayName = getGroupDisplayName(individualRecipient, threadRecipient.isCommunityRecipient());
|
||||||
stringBuilder.append(Util.getBoldedString(displayName + ": "));
|
stringBuilder.append(Util.getBoldedString(displayName + ": "));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,7 +215,7 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
|
|||||||
SpannableStringBuilder stringBuilder = new SpannableStringBuilder();
|
SpannableStringBuilder stringBuilder = new SpannableStringBuilder();
|
||||||
|
|
||||||
if (privacy.isDisplayContact() && threadRecipient.isGroupRecipient()) {
|
if (privacy.isDisplayContact() && threadRecipient.isGroupRecipient()) {
|
||||||
String displayName = getGroupDisplayName(individualRecipient, threadRecipient.isOpenGroupRecipient());
|
String displayName = getGroupDisplayName(individualRecipient, threadRecipient.isCommunityRecipient());
|
||||||
stringBuilder.append(Util.getBoldedString(displayName + ": "));
|
stringBuilder.append(Util.getBoldedString(displayName + ": "));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,13 +1,22 @@
|
|||||||
package org.thoughtcrime.securesms.repository
|
package org.thoughtcrime.securesms.repository
|
||||||
|
|
||||||
import network.loki.messenger.libsession_util.util.ExpiryMode
|
import network.loki.messenger.libsession_util.util.ExpiryMode
|
||||||
|
|
||||||
import android.content.ContentResolver
|
import android.content.ContentResolver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
|
||||||
import app.cash.copper.Query
|
import app.cash.copper.Query
|
||||||
import app.cash.copper.flow.observeQuery
|
import app.cash.copper.flow.observeQuery
|
||||||
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
import kotlin.coroutines.resumeWithException
|
||||||
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
import org.session.libsession.database.MessageDataProvider
|
import org.session.libsession.database.MessageDataProvider
|
||||||
import org.session.libsession.messaging.messages.Destination
|
import org.session.libsession.messaging.messages.Destination
|
||||||
import org.session.libsession.messaging.messages.control.MessageRequestResponse
|
import org.session.libsession.messaging.messages.control.MessageRequestResponse
|
||||||
@ -22,7 +31,10 @@ import org.session.libsession.utilities.Address
|
|||||||
import org.session.libsession.utilities.GroupUtil
|
import org.session.libsession.utilities.GroupUtil
|
||||||
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.session.libsignal.utilities.toHexString
|
import org.session.libsignal.utilities.toHexString
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseContentProviders
|
import org.thoughtcrime.securesms.database.DatabaseContentProviders
|
||||||
import org.thoughtcrime.securesms.database.DraftDatabase
|
import org.thoughtcrime.securesms.database.DraftDatabase
|
||||||
import org.thoughtcrime.securesms.database.ExpirationConfigurationDatabase
|
import org.thoughtcrime.securesms.database.ExpirationConfigurationDatabase
|
||||||
@ -39,10 +51,8 @@ import org.thoughtcrime.securesms.database.model.MessageRecord
|
|||||||
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
||||||
import org.thoughtcrime.securesms.dependencies.ConfigFactory
|
import org.thoughtcrime.securesms.dependencies.ConfigFactory
|
||||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||||
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.coroutines.resume
|
|
||||||
import kotlin.coroutines.resumeWithException
|
|
||||||
import kotlin.coroutines.suspendCoroutine
|
|
||||||
|
|
||||||
interface ConversationRepository {
|
interface ConversationRepository {
|
||||||
fun maybeGetRecipientForThreadId(threadId: Long): Recipient?
|
fun maybeGetRecipientForThreadId(threadId: Long): Recipient?
|
||||||
@ -55,37 +65,19 @@ interface ConversationRepository {
|
|||||||
fun inviteContacts(threadId: Long, contacts: List<Recipient>)
|
fun inviteContacts(threadId: Long, contacts: List<Recipient>)
|
||||||
fun setBlocked(recipient: Recipient, blocked: Boolean)
|
fun setBlocked(recipient: Recipient, blocked: Boolean)
|
||||||
fun deleteLocally(recipient: Recipient, message: MessageRecord)
|
fun deleteLocally(recipient: Recipient, message: MessageRecord)
|
||||||
|
fun deleteAllLocalMessagesInThreadFromSenderOfMessage(messageRecord: MessageRecord)
|
||||||
fun setApproved(recipient: Recipient, isApproved: Boolean)
|
fun setApproved(recipient: Recipient, isApproved: Boolean)
|
||||||
|
suspend fun deleteForEveryone(threadId: Long, recipient: Recipient, message: MessageRecord): ResultOf<Unit>
|
||||||
suspend fun deleteForEveryone(
|
|
||||||
threadId: Long,
|
|
||||||
recipient: Recipient,
|
|
||||||
message: MessageRecord
|
|
||||||
): ResultOf<Unit>
|
|
||||||
|
|
||||||
fun buildUnsendRequest(recipient: Recipient, message: MessageRecord): UnsendRequest?
|
fun buildUnsendRequest(recipient: Recipient, message: MessageRecord): UnsendRequest?
|
||||||
|
suspend fun deleteMessageWithoutUnsendRequest(threadId: Long, messages: Set<MessageRecord>): ResultOf<Unit>
|
||||||
suspend fun deleteMessageWithoutUnsendRequest(
|
|
||||||
threadId: Long,
|
|
||||||
messages: Set<MessageRecord>
|
|
||||||
): ResultOf<Unit>
|
|
||||||
|
|
||||||
suspend fun banUser(threadId: Long, recipient: Recipient): ResultOf<Unit>
|
suspend fun banUser(threadId: Long, recipient: Recipient): ResultOf<Unit>
|
||||||
|
|
||||||
suspend fun banAndDeleteAll(threadId: Long, recipient: Recipient): ResultOf<Unit>
|
suspend fun banAndDeleteAll(threadId: Long, recipient: Recipient): ResultOf<Unit>
|
||||||
|
|
||||||
suspend fun deleteThread(threadId: Long): ResultOf<Unit>
|
suspend fun deleteThread(threadId: Long): ResultOf<Unit>
|
||||||
|
|
||||||
suspend fun deleteMessageRequest(thread: ThreadRecord): ResultOf<Unit>
|
suspend fun deleteMessageRequest(thread: ThreadRecord): ResultOf<Unit>
|
||||||
|
|
||||||
suspend fun clearAllMessageRequests(block: Boolean): ResultOf<Unit>
|
suspend fun clearAllMessageRequests(block: Boolean): ResultOf<Unit>
|
||||||
|
|
||||||
suspend fun acceptMessageRequest(threadId: Long, recipient: Recipient): ResultOf<Unit>
|
suspend fun acceptMessageRequest(threadId: Long, recipient: Recipient): ResultOf<Unit>
|
||||||
|
|
||||||
fun declineMessageRequest(threadId: Long)
|
fun declineMessageRequest(threadId: Long)
|
||||||
|
|
||||||
fun hasReceived(threadId: Long): Boolean
|
fun hasReceived(threadId: Long): Boolean
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class DefaultConversationRepository @Inject constructor(
|
class DefaultConversationRepository @Inject constructor(
|
||||||
@ -184,6 +176,15 @@ class DefaultConversationRepository @Inject constructor(
|
|||||||
messageDataProvider.deleteMessage(message.id, !message.isMms)
|
messageDataProvider.deleteMessage(message.id, !message.isMms)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun deleteAllLocalMessagesInThreadFromSenderOfMessage(messageRecord: MessageRecord) {
|
||||||
|
val threadId = messageRecord.threadId
|
||||||
|
val senderId = messageRecord.recipient.address.contactIdentifier()
|
||||||
|
val messageRecordsToRemoveFromLocalStorage = mmsSmsDb.getAllMessageRecordsFromSenderInThread(threadId, senderId)
|
||||||
|
for (message in messageRecordsToRemoveFromLocalStorage) {
|
||||||
|
messageDataProvider.deleteMessage(message.id, !message.isMms)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun setApproved(recipient: Recipient, isApproved: Boolean) {
|
override fun setApproved(recipient: Recipient, isApproved: Boolean) {
|
||||||
storage.setRecipientApproved(recipient, isApproved)
|
storage.setRecipientApproved(recipient, isApproved)
|
||||||
}
|
}
|
||||||
@ -196,18 +197,38 @@ class DefaultConversationRepository @Inject constructor(
|
|||||||
buildUnsendRequest(recipient, message)?.let { unsendRequest ->
|
buildUnsendRequest(recipient, message)?.let { unsendRequest ->
|
||||||
MessageSender.send(unsendRequest, recipient.address)
|
MessageSender.send(unsendRequest, recipient.address)
|
||||||
}
|
}
|
||||||
|
|
||||||
val openGroup = lokiThreadDb.getOpenGroupChat(threadId)
|
val openGroup = lokiThreadDb.getOpenGroupChat(threadId)
|
||||||
if (openGroup != null) {
|
if (openGroup != null) {
|
||||||
lokiMessageDb.getServerID(message.id, !message.isMms)?.let { messageServerID ->
|
val serverId = lokiMessageDb.getServerID(message.id, !message.isMms)?.let { messageServerID ->
|
||||||
OpenGroupApi.deleteMessage(messageServerID, openGroup.room, openGroup.server)
|
OpenGroupApi.deleteMessage(messageServerID, openGroup.room, openGroup.server)
|
||||||
.success {
|
.success {
|
||||||
messageDataProvider.deleteMessage(message.id, !message.isMms)
|
messageDataProvider.deleteMessage(message.id, !message.isMms)
|
||||||
continuation.resume(ResultOf.Success(Unit))
|
continuation.resume(ResultOf.Success(Unit))
|
||||||
}.fail { error ->
|
}.fail { error ->
|
||||||
|
Log.w("TAG", "Call to OpenGroupApi.deleteForEveryone failed - attempting to resume..")
|
||||||
continuation.resumeWithException(error)
|
continuation.resumeWithException(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
|
// If the server ID is null then this message is stuck in limbo (it has likely been
|
||||||
|
// deleted remotely but that deletion did not occur locally) - so we'll delete the
|
||||||
|
// message locally to clean up.
|
||||||
|
if (serverId == null) {
|
||||||
|
Log.w("ConversationRepository","Found community message without a server ID - deleting locally.")
|
||||||
|
|
||||||
|
// Caution: The bool returned from `deleteMessage` is NOT "Was the message
|
||||||
|
// successfully deleted?" - it is "Was the thread itself also deleted because
|
||||||
|
// removing that message resulted in an empty thread?".
|
||||||
|
if (message.isMms) {
|
||||||
|
mmsDb.deleteMessage(message.id)
|
||||||
|
} else {
|
||||||
|
smsDb.deleteMessage(message.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else // If this thread is NOT in a Community
|
||||||
|
{
|
||||||
messageDataProvider.deleteMessage(message.id, !message.isMms)
|
messageDataProvider.deleteMessage(message.id, !message.isMms)
|
||||||
messageDataProvider.getServerHashForMessage(message.id, message.isMms)?.let { serverHash ->
|
messageDataProvider.getServerHashForMessage(message.id, message.isMms)?.let { serverHash ->
|
||||||
var publicKey = recipient.address.serialize()
|
var publicKey = recipient.address.serialize()
|
||||||
@ -218,6 +239,7 @@ class DefaultConversationRepository @Inject constructor(
|
|||||||
.success {
|
.success {
|
||||||
continuation.resume(ResultOf.Success(Unit))
|
continuation.resume(ResultOf.Success(Unit))
|
||||||
}.fail { error ->
|
}.fail { error ->
|
||||||
|
Log.w("[onversationRepository", "Call to SnodeAPI.deleteMessage failed - attempting to resume..")
|
||||||
continuation.resumeWithException(error)
|
continuation.resumeWithException(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -225,7 +247,7 @@ class DefaultConversationRepository @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun buildUnsendRequest(recipient: Recipient, message: MessageRecord): UnsendRequest? {
|
override fun buildUnsendRequest(recipient: Recipient, message: MessageRecord): UnsendRequest? {
|
||||||
if (recipient.isOpenGroupRecipient) return null
|
if (recipient.isCommunityRecipient) return null
|
||||||
messageDataProvider.getServerHashForMessage(message.id, message.isMms) ?: return null
|
messageDataProvider.getServerHashForMessage(message.id, message.isMms) ?: return null
|
||||||
return UnsendRequest(
|
return UnsendRequest(
|
||||||
author = message.takeUnless { it.isOutgoing }?.run { individualRecipient.address.contactIdentifier() } ?: textSecurePreferences.getLocalNumber(),
|
author = message.takeUnless { it.isOutgoing }?.run { individualRecipient.address.contactIdentifier() } ?: textSecurePreferences.getLocalNumber(),
|
||||||
@ -279,8 +301,10 @@ class DefaultConversationRepository @Inject constructor(
|
|||||||
|
|
||||||
override suspend fun banAndDeleteAll(threadId: Long, recipient: Recipient): ResultOf<Unit> =
|
override suspend fun banAndDeleteAll(threadId: Long, recipient: Recipient): ResultOf<Unit> =
|
||||||
suspendCoroutine { continuation ->
|
suspendCoroutine { continuation ->
|
||||||
|
// Note: This sessionId could be the blinded Id
|
||||||
val sessionID = recipient.address.toString()
|
val sessionID = recipient.address.toString()
|
||||||
val openGroup = lokiThreadDb.getOpenGroupChat(threadId)!!
|
val openGroup = lokiThreadDb.getOpenGroupChat(threadId)!!
|
||||||
|
|
||||||
OpenGroupApi.banAndDeleteAll(sessionID, openGroup.room, openGroup.server)
|
OpenGroupApi.banAndDeleteAll(sessionID, openGroup.room, openGroup.server)
|
||||||
.success {
|
.success {
|
||||||
continuation.resume(ResultOf.Success(Unit))
|
continuation.resume(ResultOf.Success(Unit))
|
||||||
|
@ -193,7 +193,7 @@ object ConfigurationMessageUtilities {
|
|||||||
while (current != null) {
|
while (current != null) {
|
||||||
val recipient = current.recipient
|
val recipient = current.recipient
|
||||||
val contact = when {
|
val contact = when {
|
||||||
recipient.isOpenGroupRecipient -> {
|
recipient.isCommunityRecipient -> {
|
||||||
val openGroup = storage.getOpenGroup(current.threadId) ?: continue
|
val openGroup = storage.getOpenGroup(current.threadId) ?: continue
|
||||||
val (base, room, pubKey) = BaseCommunityInfo.parseFullUrl(openGroup.joinURL) ?: continue
|
val (base, room, pubKey) = BaseCommunityInfo.parseFullUrl(openGroup.joinURL) ?: continue
|
||||||
convoConfig.getOrConstructCommunity(base, room, pubKey)
|
convoConfig.getOrConstructCommunity(base, room, pubKey)
|
||||||
@ -279,7 +279,7 @@ object ConfigurationMessageUtilities {
|
|||||||
|
|
||||||
@JvmField
|
@JvmField
|
||||||
val DELETE_INACTIVE_ONE_TO_ONES: String = """
|
val DELETE_INACTIVE_ONE_TO_ONES: String = """
|
||||||
DELETE FROM ${ThreadDatabase.TABLE_NAME} WHERE ${ThreadDatabase.MESSAGE_COUNT} <= 0 AND ${ThreadDatabase.ADDRESS} NOT LIKE '${GroupUtil.CLOSED_GROUP_PREFIX}%' AND ${ThreadDatabase.ADDRESS} NOT LIKE '${GroupUtil.OPEN_GROUP_PREFIX}%' AND ${ThreadDatabase.ADDRESS} NOT LIKE '${GroupUtil.OPEN_GROUP_INBOX_PREFIX}%';
|
DELETE FROM ${ThreadDatabase.TABLE_NAME} WHERE ${ThreadDatabase.MESSAGE_COUNT} <= 0 AND ${ThreadDatabase.ADDRESS} NOT LIKE '${GroupUtil.CLOSED_GROUP_PREFIX}%' AND ${ThreadDatabase.ADDRESS} NOT LIKE '${GroupUtil.COMMUNITY_PREFIX}%' AND ${ThreadDatabase.ADDRESS} NOT LIKE '${GroupUtil.COMMUNITY_INBOX_PREFIX}%';
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
|
||||||
}
|
}
|
@ -14,7 +14,7 @@ fun ConversationVolatileConfig.getConversationUnread(thread: ThreadRecord): Bool
|
|||||||
return getOneToOne(recipient.address.serialize())?.unread == true
|
return getOneToOne(recipient.address.serialize())?.unread == true
|
||||||
} else if (recipient.isClosedGroupRecipient) {
|
} else if (recipient.isClosedGroupRecipient) {
|
||||||
return getLegacyClosedGroup(GroupUtil.doubleDecodeGroupId(recipient.address.toGroupString()))?.unread == true
|
return getLegacyClosedGroup(GroupUtil.doubleDecodeGroupId(recipient.address.toGroupString()))?.unread == true
|
||||||
} else if (recipient.isOpenGroupRecipient) {
|
} else if (recipient.isCommunityRecipient) {
|
||||||
val openGroup = MessagingModuleConfiguration.shared.storage.getOpenGroup(thread.threadId) ?: return false
|
val openGroup = MessagingModuleConfiguration.shared.storage.getOpenGroup(thread.threadId) ?: return false
|
||||||
return getCommunity(openGroup.server, openGroup.room)?.unread == true
|
return getCommunity(openGroup.server, openGroup.room)?.unread == true
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
package org.thoughtcrime.securesms
|
||||||
|
|
||||||
|
import org.session.libsignal.utilities.Log.Logger
|
||||||
|
|
||||||
|
object NoOpLogger: Logger() {
|
||||||
|
override fun v(tag: String?, message: String?, t: Throwable?) {}
|
||||||
|
|
||||||
|
override fun d(tag: String?, message: String?, t: Throwable?) {}
|
||||||
|
|
||||||
|
override fun i(tag: String?, message: String?, t: Throwable?) {}
|
||||||
|
|
||||||
|
override fun w(tag: String?, message: String?, t: Throwable?) {}
|
||||||
|
|
||||||
|
override fun e(tag: String?, message: String?, t: Throwable?) {}
|
||||||
|
|
||||||
|
override fun wtf(tag: String?, message: String?, t: Throwable?) {}
|
||||||
|
|
||||||
|
override fun blockUntilAllWritesFinished() {}
|
||||||
|
}
|
@ -1,10 +1,20 @@
|
|||||||
package org.thoughtcrime.securesms
|
package org.thoughtcrime.securesms
|
||||||
|
|
||||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||||
|
import org.junit.BeforeClass
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
|
import org.session.libsignal.utilities.Log
|
||||||
|
|
||||||
open class BaseViewModelTest: BaseCoroutineTest() {
|
open class BaseViewModelTest: BaseCoroutineTest() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@BeforeClass
|
||||||
|
@JvmStatic
|
||||||
|
fun setupLogger() {
|
||||||
|
Log.initialize(NoOpLogger)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@get:Rule
|
@get:Rule
|
||||||
var instantExecutorRule = InstantTaskExecutorRule()
|
var instantExecutorRule = InstantTaskExecutorRule()
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ import kotlin.time.Duration.Companion.minutes
|
|||||||
private const val THREAD_ID = 1L
|
private const val THREAD_ID = 1L
|
||||||
private const val LOCAL_NUMBER = "05---local---address"
|
private const val LOCAL_NUMBER = "05---local---address"
|
||||||
private val LOCAL_ADDRESS = Address.fromSerialized(LOCAL_NUMBER)
|
private val LOCAL_ADDRESS = Address.fromSerialized(LOCAL_NUMBER)
|
||||||
private const val GROUP_NUMBER = "${GroupUtil.OPEN_GROUP_PREFIX}4133"
|
private const val GROUP_NUMBER = "${GroupUtil.COMMUNITY_PREFIX}4133"
|
||||||
private val GROUP_ADDRESS = Address.fromSerialized(GROUP_NUMBER)
|
private val GROUP_ADDRESS = Address.fromSerialized(GROUP_NUMBER)
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
@ -3,12 +3,14 @@ package org.thoughtcrime.securesms.conversation.v2
|
|||||||
import com.goterl.lazysodium.utils.KeyPair
|
import com.goterl.lazysodium.utils.KeyPair
|
||||||
import kotlinx.coroutines.flow.emptyFlow
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.hamcrest.CoreMatchers.endsWith
|
import org.hamcrest.CoreMatchers.endsWith
|
||||||
import org.hamcrest.CoreMatchers.equalTo
|
import org.hamcrest.CoreMatchers.equalTo
|
||||||
import org.hamcrest.CoreMatchers.notNullValue
|
import org.hamcrest.CoreMatchers.notNullValue
|
||||||
import org.hamcrest.CoreMatchers.nullValue
|
import org.hamcrest.CoreMatchers.nullValue
|
||||||
import org.hamcrest.MatcherAssert.assertThat
|
import org.hamcrest.MatcherAssert.assertThat
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
|
import org.junit.BeforeClass
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.mockito.Mockito
|
import org.mockito.Mockito
|
||||||
import org.mockito.Mockito.anyLong
|
import org.mockito.Mockito.anyLong
|
||||||
@ -18,7 +20,9 @@ import org.mockito.kotlin.any
|
|||||||
import org.mockito.kotlin.mock
|
import org.mockito.kotlin.mock
|
||||||
import org.mockito.kotlin.whenever
|
import org.mockito.kotlin.whenever
|
||||||
import org.session.libsession.utilities.recipients.Recipient
|
import org.session.libsession.utilities.recipients.Recipient
|
||||||
|
import org.session.libsignal.utilities.Log
|
||||||
import org.thoughtcrime.securesms.BaseViewModelTest
|
import org.thoughtcrime.securesms.BaseViewModelTest
|
||||||
|
import org.thoughtcrime.securesms.NoOpLogger
|
||||||
import org.thoughtcrime.securesms.database.Storage
|
import org.thoughtcrime.securesms.database.Storage
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||||
import org.thoughtcrime.securesms.repository.ConversationRepository
|
import org.thoughtcrime.securesms.repository.ConversationRepository
|
||||||
@ -32,6 +36,7 @@ class ConversationViewModelTest: BaseViewModelTest() {
|
|||||||
private val threadId = 123L
|
private val threadId = 123L
|
||||||
private val edKeyPair = mock<KeyPair>()
|
private val edKeyPair = mock<KeyPair>()
|
||||||
private lateinit var recipient: Recipient
|
private lateinit var recipient: Recipient
|
||||||
|
private lateinit var messageRecord: MessageRecord
|
||||||
|
|
||||||
private val viewModel: ConversationViewModel by lazy {
|
private val viewModel: ConversationViewModel by lazy {
|
||||||
ConversationViewModel(threadId, edKeyPair, repository, storage)
|
ConversationViewModel(threadId, edKeyPair, repository, storage)
|
||||||
@ -40,6 +45,9 @@ class ConversationViewModelTest: BaseViewModelTest() {
|
|||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
recipient = mock()
|
recipient = mock()
|
||||||
|
messageRecord = mock { record ->
|
||||||
|
whenever(record.individualRecipient).thenReturn(recipient)
|
||||||
|
}
|
||||||
whenever(repository.maybeGetRecipientForThreadId(anyLong())).thenReturn(recipient)
|
whenever(repository.maybeGetRecipientForThreadId(anyLong())).thenReturn(recipient)
|
||||||
whenever(repository.recipientUpdateFlow(anyLong())).thenReturn(emptyFlow())
|
whenever(repository.recipientUpdateFlow(anyLong())).thenReturn(emptyFlow())
|
||||||
}
|
}
|
||||||
@ -144,7 +152,7 @@ class ConversationViewModelTest: BaseViewModelTest() {
|
|||||||
val error = Throwable()
|
val error = Throwable()
|
||||||
whenever(repository.banAndDeleteAll(anyLong(), any())).thenReturn(ResultOf.Failure(error))
|
whenever(repository.banAndDeleteAll(anyLong(), any())).thenReturn(ResultOf.Failure(error))
|
||||||
|
|
||||||
viewModel.banAndDeleteAll(recipient)
|
viewModel.banAndDeleteAll(messageRecord)
|
||||||
|
|
||||||
assertThat(viewModel.uiState.first().uiMessages.first().message, endsWith("$error"))
|
assertThat(viewModel.uiState.first().uiMessages.first().message, endsWith("$error"))
|
||||||
}
|
}
|
||||||
@ -153,7 +161,7 @@ class ConversationViewModelTest: BaseViewModelTest() {
|
|||||||
fun `should emit a message on ban user and delete all success`() = runBlockingTest {
|
fun `should emit a message on ban user and delete all success`() = runBlockingTest {
|
||||||
whenever(repository.banAndDeleteAll(anyLong(), any())).thenReturn(ResultOf.Success(Unit))
|
whenever(repository.banAndDeleteAll(anyLong(), any())).thenReturn(ResultOf.Success(Unit))
|
||||||
|
|
||||||
viewModel.banAndDeleteAll(recipient)
|
viewModel.banAndDeleteAll(messageRecord)
|
||||||
|
|
||||||
assertThat(
|
assertThat(
|
||||||
viewModel.uiState.first().uiMessages.first().message,
|
viewModel.uiState.first().uiMessages.first().message,
|
||||||
@ -189,7 +197,7 @@ class ConversationViewModelTest: BaseViewModelTest() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `open group recipient should have no blinded recipient`() {
|
fun `open group recipient should have no blinded recipient`() {
|
||||||
whenever(recipient.isOpenGroupRecipient).thenReturn(true)
|
whenever(recipient.isCommunityRecipient).thenReturn(true)
|
||||||
whenever(recipient.isOpenGroupOutboxRecipient).thenReturn(false)
|
whenever(recipient.isOpenGroupOutboxRecipient).thenReturn(false)
|
||||||
whenever(recipient.isOpenGroupInboxRecipient).thenReturn(false)
|
whenever(recipient.isOpenGroupInboxRecipient).thenReturn(false)
|
||||||
assertThat(viewModel.blindedRecipient, nullValue())
|
assertThat(viewModel.blindedRecipient, nullValue())
|
||||||
|
@ -77,7 +77,7 @@ class Contact(val sessionID: String) {
|
|||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
fun contextForRecipient(recipient: Recipient): ContactContext {
|
fun contextForRecipient(recipient: Recipient): ContactContext {
|
||||||
return if (recipient.isOpenGroupRecipient) ContactContext.OPEN_GROUP else ContactContext.REGULAR
|
return if (recipient.isCommunityRecipient) ContactContext.OPEN_GROUP else ContactContext.REGULAR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -22,7 +22,7 @@ class OpenGroupDeleteJob(private val messageServerIds: LongArray, private val th
|
|||||||
override suspend fun execute(dispatcherName: String) {
|
override suspend fun execute(dispatcherName: String) {
|
||||||
val dataProvider = MessagingModuleConfiguration.shared.messageDataProvider
|
val dataProvider = MessagingModuleConfiguration.shared.messageDataProvider
|
||||||
val numberToDelete = messageServerIds.size
|
val numberToDelete = messageServerIds.size
|
||||||
Log.d(TAG, "Deleting $numberToDelete messages")
|
Log.d(TAG, "About to attempt to delete $numberToDelete messages")
|
||||||
|
|
||||||
// FIXME: This entire process should probably run in a transaction (with the attachment deletion happening only if it succeeded)
|
// FIXME: This entire process should probably run in a transaction (with the attachment deletion happening only if it succeeded)
|
||||||
try {
|
try {
|
||||||
@ -42,6 +42,7 @@ class OpenGroupDeleteJob(private val messageServerIds: LongArray, private val th
|
|||||||
delegate?.handleJobSucceeded(this, dispatcherName)
|
delegate?.handleJobSucceeded(this, dispatcherName)
|
||||||
}
|
}
|
||||||
catch (e: Exception) {
|
catch (e: Exception) {
|
||||||
|
Log.w(TAG, "OpenGroupDeleteJob failed: $e")
|
||||||
delegate?.handleJobFailed(this, dispatcherName, e)
|
delegate?.handleJobFailed(this, dispatcherName, e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,14 +43,14 @@ sealed class Destination {
|
|||||||
val groupPublicKey = GroupUtil.doubleDecodeGroupID(groupID).toHexString()
|
val groupPublicKey = GroupUtil.doubleDecodeGroupID(groupID).toHexString()
|
||||||
ClosedGroup(groupPublicKey)
|
ClosedGroup(groupPublicKey)
|
||||||
}
|
}
|
||||||
address.isOpenGroup -> {
|
address.isCommunity -> {
|
||||||
val storage = MessagingModuleConfiguration.shared.storage
|
val storage = MessagingModuleConfiguration.shared.storage
|
||||||
val threadID = storage.getThreadId(address)!!
|
val threadID = storage.getThreadId(address)!!
|
||||||
storage.getOpenGroup(threadID)?.let {
|
storage.getOpenGroup(threadID)?.let {
|
||||||
OpenGroup(roomToken = it.room, server = it.server, fileIds = fileIds)
|
OpenGroup(roomToken = it.room, server = it.server, fileIds = fileIds)
|
||||||
} ?: throw Exception("Missing open group for thread with ID: $threadID.")
|
} ?: throw Exception("Missing open group for thread with ID: $threadID.")
|
||||||
}
|
}
|
||||||
address.isOpenGroupInbox -> {
|
address.isCommunityInbox -> {
|
||||||
val groupInboxId = GroupUtil.getDecodedGroupID(address.serialize()).split("!")
|
val groupInboxId = GroupUtil.getDecodedGroupID(address.serialize()).split("!")
|
||||||
OpenGroupInbox(
|
OpenGroupInbox(
|
||||||
groupInboxId.dropLast(2).joinToString("!"),
|
groupInboxId.dropLast(2).joinToString("!"),
|
||||||
|
@ -602,8 +602,7 @@ object OpenGroupApi {
|
|||||||
// region Message Deletion
|
// region Message Deletion
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun deleteMessage(serverID: Long, room: String, server: String): Promise<Unit, Exception> {
|
fun deleteMessage(serverID: Long, room: String, server: String): Promise<Unit, Exception> {
|
||||||
val request =
|
val request = Request(verb = DELETE, room = room, server = server, endpoint = Endpoint.RoomMessageIndividual(room, serverID))
|
||||||
Request(verb = DELETE, room = room, server = server, endpoint = Endpoint.RoomMessageIndividual(room, serverID))
|
|
||||||
return send(request).map {
|
return send(request).map {
|
||||||
Log.d("Loki", "Message deletion successful.")
|
Log.d("Loki", "Message deletion successful.")
|
||||||
}
|
}
|
||||||
@ -659,7 +658,9 @@ object OpenGroupApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun banAndDeleteAll(publicKey: String, room: String, server: String): Promise<Unit, Exception> {
|
fun banAndDeleteAll(publicKey: String, room: String, server: String): Promise<Unit, Exception> {
|
||||||
|
|
||||||
val requests = mutableListOf<BatchRequestInfo<*>>(
|
val requests = mutableListOf<BatchRequestInfo<*>>(
|
||||||
|
// Ban request
|
||||||
BatchRequestInfo(
|
BatchRequestInfo(
|
||||||
request = BatchRequest(
|
request = BatchRequest(
|
||||||
method = POST,
|
method = POST,
|
||||||
@ -669,6 +670,7 @@ object OpenGroupApi {
|
|||||||
endpoint = Endpoint.UserBan(publicKey),
|
endpoint = Endpoint.UserBan(publicKey),
|
||||||
responseType = object: TypeReference<Any>(){}
|
responseType = object: TypeReference<Any>(){}
|
||||||
),
|
),
|
||||||
|
// Delete request
|
||||||
BatchRequestInfo(
|
BatchRequestInfo(
|
||||||
request = BatchRequest(DELETE, "/room/$room/all/$publicKey"),
|
request = BatchRequest(DELETE, "/room/$room/all/$publicKey"),
|
||||||
endpoint = Endpoint.RoomDeleteMessages(room, publicKey),
|
endpoint = Endpoint.RoomDeleteMessages(room, publicKey),
|
||||||
|
@ -22,17 +22,17 @@ class Address private constructor(address: String) : Parcelable, Comparable<Addr
|
|||||||
get() = GroupUtil.isEncodedGroup(address)
|
get() = GroupUtil.isEncodedGroup(address)
|
||||||
val isClosedGroup: Boolean
|
val isClosedGroup: Boolean
|
||||||
get() = GroupUtil.isClosedGroup(address)
|
get() = GroupUtil.isClosedGroup(address)
|
||||||
val isOpenGroup: Boolean
|
val isCommunity: Boolean
|
||||||
get() = GroupUtil.isOpenGroup(address)
|
get() = GroupUtil.isCommunity(address)
|
||||||
val isOpenGroupInbox: Boolean
|
val isCommunityInbox: Boolean
|
||||||
get() = GroupUtil.isOpenGroupInbox(address)
|
get() = GroupUtil.isCommunityInbox(address)
|
||||||
val isOpenGroupOutbox: Boolean
|
val isCommunityOutbox: Boolean
|
||||||
get() = address.startsWith(IdPrefix.BLINDED.value) || address.startsWith(IdPrefix.BLINDEDV2.value)
|
get() = address.startsWith(IdPrefix.BLINDED.value) || address.startsWith(IdPrefix.BLINDEDV2.value)
|
||||||
val isContact: Boolean
|
val isContact: Boolean
|
||||||
get() = !(isGroup || isOpenGroupInbox)
|
get() = !(isGroup || isCommunityInbox)
|
||||||
|
|
||||||
fun contactIdentifier(): String {
|
fun contactIdentifier(): String {
|
||||||
if (!isContact && !isOpenGroup) {
|
if (!isContact && !isCommunity) {
|
||||||
if (isGroup) throw AssertionError("Not e164, is group")
|
if (isGroup) throw AssertionError("Not e164, is group")
|
||||||
throw AssertionError("Not e164, unknown")
|
throw AssertionError("Not e164, unknown")
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ class GroupRecord(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val isOpenGroup: Boolean
|
val isOpenGroup: Boolean
|
||||||
get() = Address.fromSerialized(encodedId).isOpenGroup
|
get() = Address.fromSerialized(encodedId).isCommunity
|
||||||
val isClosedGroup: Boolean
|
val isClosedGroup: Boolean
|
||||||
get() = Address.fromSerialized(encodedId).isClosedGroup
|
get() = Address.fromSerialized(encodedId).isClosedGroup
|
||||||
|
|
||||||
|
@ -8,12 +8,12 @@ import java.io.IOException
|
|||||||
|
|
||||||
object GroupUtil {
|
object GroupUtil {
|
||||||
const val CLOSED_GROUP_PREFIX = "__textsecure_group__!"
|
const val CLOSED_GROUP_PREFIX = "__textsecure_group__!"
|
||||||
const val OPEN_GROUP_PREFIX = "__loki_public_chat_group__!"
|
const val COMMUNITY_PREFIX = "__loki_public_chat_group__!"
|
||||||
const val OPEN_GROUP_INBOX_PREFIX = "__open_group_inbox__!"
|
const val COMMUNITY_INBOX_PREFIX = "__open_group_inbox__!"
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getEncodedOpenGroupID(groupID: ByteArray): String {
|
fun getEncodedOpenGroupID(groupID: ByteArray): String {
|
||||||
return OPEN_GROUP_PREFIX + Hex.toStringCondensed(groupID)
|
return COMMUNITY_PREFIX + Hex.toStringCondensed(groupID)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@ -25,7 +25,7 @@ object GroupUtil {
|
|||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getEncodedOpenGroupInboxID(groupInboxID: ByteArray): Address {
|
fun getEncodedOpenGroupInboxID(groupInboxID: ByteArray): Address {
|
||||||
return Address.fromSerialized(OPEN_GROUP_INBOX_PREFIX + Hex.toStringCondensed(groupInboxID))
|
return Address.fromSerialized(COMMUNITY_INBOX_PREFIX + Hex.toStringCondensed(groupInboxID))
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@ -69,17 +69,17 @@ object GroupUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun isEncodedGroup(groupId: String): Boolean {
|
fun isEncodedGroup(groupId: String): Boolean {
|
||||||
return groupId.startsWith(CLOSED_GROUP_PREFIX) || groupId.startsWith(OPEN_GROUP_PREFIX)
|
return groupId.startsWith(CLOSED_GROUP_PREFIX) || groupId.startsWith(COMMUNITY_PREFIX)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun isOpenGroup(groupId: String): Boolean {
|
fun isCommunity(groupId: String): Boolean {
|
||||||
return groupId.startsWith(OPEN_GROUP_PREFIX)
|
return groupId.startsWith(COMMUNITY_PREFIX)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun isOpenGroupInbox(groupId: String): Boolean {
|
fun isCommunityInbox(groupId: String): Boolean {
|
||||||
return groupId.startsWith(OPEN_GROUP_INBOX_PREFIX)
|
return groupId.startsWith(COMMUNITY_INBOX_PREFIX)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
@ -459,16 +459,16 @@ public class Recipient implements RecipientModifiedListener {
|
|||||||
}
|
}
|
||||||
public boolean is1on1() { return address.isContact() && !isLocalNumber; }
|
public boolean is1on1() { return address.isContact() && !isLocalNumber; }
|
||||||
|
|
||||||
public boolean isOpenGroupRecipient() {
|
public boolean isCommunityRecipient() {
|
||||||
return address.isOpenGroup();
|
return address.isCommunity();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isOpenGroupOutboxRecipient() {
|
public boolean isOpenGroupOutboxRecipient() {
|
||||||
return address.isOpenGroupOutbox();
|
return address.isCommunityOutbox();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isOpenGroupInboxRecipient() {
|
public boolean isOpenGroupInboxRecipient() {
|
||||||
return address.isOpenGroupInbox();
|
return address.isCommunityInbox();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isClosedGroupRecipient() {
|
public boolean isClosedGroupRecipient() {
|
||||||
|
Loading…
Reference in New Issue
Block a user