Replace default action message request behavior (#927)

* refactor: add a block action and change default message request behavior to decline/delete

* refactor: move log calls, add block & delete menu option

* refactor: migrate some more actions into ConversationActivityV2.kt and only show message request incoming menu if it's an incoming message request

* refactor: change block behaviour to be in the message request actions

* refactor: use block user copy

* refactor: parameters for ConversationMenuHelper interface cleaned up
This commit is contained in:
Harris 2022-09-13 15:06:46 +10:00 committed by GitHub
parent aa43ab2a2e
commit 9f8ed4daf2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 228 additions and 98 deletions

View File

@ -1,8 +1,8 @@
package org.thoughtcrime.securesms package org.thoughtcrime.securesms
import android.util.Log
import nl.komponents.kovenant.Kovenant import nl.komponents.kovenant.Kovenant
import nl.komponents.kovenant.jvm.asDispatcher import nl.komponents.kovenant.jvm.asDispatcher
import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.ThreadUtils import org.session.libsignal.utilities.ThreadUtils
import java.util.concurrent.Executors import java.util.concurrent.Executors

View File

@ -19,7 +19,6 @@ import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.text.TextUtils import android.text.TextUtils
import android.util.Log
import android.util.Pair import android.util.Pair
import android.util.TypedValue import android.util.TypedValue
import android.view.ActionMode import android.view.ActionMode
@ -56,6 +55,7 @@ import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.messaging.mentions.Mention import org.session.libsession.messaging.mentions.Mention
import org.session.libsession.messaging.mentions.MentionsManager import org.session.libsession.messaging.mentions.MentionsManager
import org.session.libsession.messaging.messages.control.DataExtractionNotification import org.session.libsession.messaging.messages.control.DataExtractionNotification
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate
import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage
import org.session.libsession.messaging.messages.signal.OutgoingTextMessage import org.session.libsession.messaging.messages.signal.OutgoingTextMessage
import org.session.libsession.messaging.messages.visible.Reaction import org.session.libsession.messaging.messages.visible.Reaction
@ -79,9 +79,11 @@ import org.session.libsession.utilities.recipients.RecipientModifiedListener
import org.session.libsignal.crypto.MnemonicCodec import org.session.libsignal.crypto.MnemonicCodec
import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.IdPrefix
import org.session.libsignal.utilities.ListenableFuture import org.session.libsignal.utilities.ListenableFuture
import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.guava.Optional import org.session.libsignal.utilities.guava.Optional
import org.session.libsignal.utilities.hexEncodedPrivateKey import org.session.libsignal.utilities.hexEncodedPrivateKey
import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.ExpirationDialog
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.audio.AudioRecorder import org.thoughtcrime.securesms.audio.AudioRecorder
import org.thoughtcrime.securesms.contacts.SelectContactsActivity.Companion.selectedContactsKey import org.thoughtcrime.securesms.contacts.SelectContactsActivity.Companion.selectedContactsKey
@ -116,6 +118,7 @@ import org.thoughtcrime.securesms.database.LokiThreadDatabase
import org.thoughtcrime.securesms.database.MmsDatabase import org.thoughtcrime.securesms.database.MmsDatabase
import org.thoughtcrime.securesms.database.MmsSmsDatabase import org.thoughtcrime.securesms.database.MmsSmsDatabase
import org.thoughtcrime.securesms.database.ReactionDatabase import org.thoughtcrime.securesms.database.ReactionDatabase
import org.thoughtcrime.securesms.database.RecipientDatabase
import org.thoughtcrime.securesms.database.SessionContactDatabase import org.thoughtcrime.securesms.database.SessionContactDatabase
import org.thoughtcrime.securesms.database.SmsDatabase import org.thoughtcrime.securesms.database.SmsDatabase
import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.database.Storage
@ -168,7 +171,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
InputBarRecordingViewDelegate, AttachmentManager.AttachmentListener, ActivityDispatcher, InputBarRecordingViewDelegate, AttachmentManager.AttachmentListener, ActivityDispatcher,
ConversationActionModeCallbackDelegate, VisibleMessageViewDelegate, RecipientModifiedListener, ConversationActionModeCallbackDelegate, VisibleMessageViewDelegate, RecipientModifiedListener,
SearchBottomBar.EventListener, LoaderManager.LoaderCallbacks<Cursor>, SearchBottomBar.EventListener, LoaderManager.LoaderCallbacks<Cursor>,
OnReactionSelectedListener, ReactWithAnyEmojiDialogFragment.Callback, ReactionsDialogFragment.Callback { OnReactionSelectedListener, ReactWithAnyEmojiDialogFragment.Callback, ReactionsDialogFragment.Callback,
ConversationMenuHelper.ConversationMenuListener {
private var binding: ActivityConversationV2Binding? = null private var binding: ActivityConversationV2Binding? = null
@ -178,6 +182,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
@Inject lateinit var lokiThreadDb: LokiThreadDatabase @Inject lateinit var lokiThreadDb: LokiThreadDatabase
@Inject lateinit var sessionContactDb: SessionContactDatabase @Inject lateinit var sessionContactDb: SessionContactDatabase
@Inject lateinit var groupDb: GroupDatabase @Inject lateinit var groupDb: GroupDatabase
@Inject lateinit var recipientDb: RecipientDatabase
@Inject lateinit var lokiApiDb: LokiAPIDatabase @Inject lateinit var lokiApiDb: LokiAPIDatabase
@Inject lateinit var smsDb: SmsDatabase @Inject lateinit var smsDb: SmsDatabase
@Inject lateinit var mmsDb: MmsDatabase @Inject lateinit var mmsDb: MmsDatabase
@ -605,9 +610,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
} }
override fun onPrepareOptionsMenu(menu: Menu): Boolean { override fun onPrepareOptionsMenu(menu: Menu): Boolean {
val recipient = viewModel.recipient ?: return false
if (!isMessageRequestThread()) { if (!isMessageRequestThread()) {
val recipient = viewModel.recipient
if (recipient != null) {
ConversationMenuHelper.onPrepareOptionsMenu( ConversationMenuHelper.onPrepareOptionsMenu(
menu, menu,
menuInflater, menuInflater,
@ -616,7 +620,6 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
this this
) { onOptionsItemSelected(it) } ) { onOptionsItemSelected(it) }
} }
}
super.onPrepareOptionsMenu(menu) super.onPrepareOptionsMenu(menu)
return true return true
} }
@ -666,6 +669,9 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
binding?.acceptMessageRequestButton?.setOnClickListener { binding?.acceptMessageRequestButton?.setOnClickListener {
acceptMessageRequest() acceptMessageRequest()
} }
binding?.messageRequestBlock?.setOnClickListener {
block(deleteThread = true)
}
binding?.declineMessageRequestButton?.setOnClickListener { binding?.declineMessageRequestButton?.setOnClickListener {
viewModel.declineMessageRequest() viewModel.declineMessageRequest()
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
@ -937,6 +943,58 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
} ?: false } ?: false
} }
override fun block(deleteThread: Boolean) {
val title = R.string.RecipientPreferenceActivity_block_this_contact_question
val message = R.string.RecipientPreferenceActivity_you_will_no_longer_receive_messages_and_calls_from_this_contact
AlertDialog.Builder(this)
.setTitle(title)
.setMessage(message)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.RecipientPreferenceActivity_block) { _, _ ->
viewModel.block()
if (deleteThread) {
viewModel.deleteThread()
finish()
}
}.show()
}
override fun copySessionID(sessionId: String) {
val clip = ClipData.newPlainText("Session ID", sessionId)
val manager = getSystemService(PassphraseRequiredActionBarActivity.CLIPBOARD_SERVICE) as ClipboardManager
manager.setPrimaryClip(clip)
Toast.makeText(this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
}
override fun showExpiringMessagesDialog(thread: Recipient) {
if (thread.isClosedGroupRecipient) {
val group = groupDb.getGroup(thread.address.toGroupString()).orNull()
if (group?.isActive == false) { return }
}
ExpirationDialog.show(this, thread.expireMessages) { expirationTime: Int ->
recipientDb.setExpireMessages(thread, expirationTime)
val message = ExpirationTimerUpdate(expirationTime)
message.recipient = thread.address.serialize()
message.sentTimestamp = System.currentTimeMillis()
val expiringMessageManager = ApplicationContext.getInstance(this).expiringMessageManager
expiringMessageManager.setExpirationTimer(message)
MessageSender.send(message, thread.address)
invalidateOptionsMenu()
}
}
override fun unblock() {
val title = R.string.ConversationActivity_unblock_this_contact_question
val message = R.string.ConversationActivity_you_will_once_again_be_able_to_receive_messages_and_calls_from_this_contact
AlertDialog.Builder(this)
.setTitle(title)
.setMessage(message)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.ConversationActivity_unblock) { _, _ ->
viewModel.unblock()
}.show()
}
// `position` is the adapter position; not the visual position // `position` is the adapter position; not the visual position
private fun handlePress(message: MessageRecord, position: Int, view: VisibleMessageView, event: MotionEvent) { private fun handlePress(message: MessageRecord, position: Int, view: VisibleMessageView, event: MotionEvent) {
val actionMode = this.actionMode val actionMode = this.actionMode

View File

@ -64,13 +64,24 @@ class ConversationViewModel(
repository.inviteContacts(threadId, contacts) repository.inviteContacts(threadId, contacts)
} }
fun block() {
val recipient = recipient ?: return Log.w("Loki", "Recipient was null for block action")
if (recipient.isContactRecipient) {
repository.setBlocked(recipient, true)
}
}
fun unblock() { fun unblock() {
val recipient = recipient ?: return Log.w("Loki", "Recipient was null for unblock action") val recipient = recipient ?: return Log.w("Loki", "Recipient was null for unblock action")
if (recipient.isContactRecipient) { if (recipient.isContactRecipient) {
repository.unblock(recipient) repository.setBlocked(recipient, false)
} }
} }
fun deleteThread() = viewModelScope.launch {
repository.deleteThread(threadId)
}
fun deleteLocally(message: MessageRecord) { fun deleteLocally(message: MessageRecord) {
val recipient = recipient ?: return Log.w("Loki", "Recipient was null for delete locally action") val recipient = recipient ?: return Log.w("Loki", "Recipient was null for delete locally action")
repository.deleteLocally(recipient, message) repository.deleteLocally(recipient, message)
@ -130,8 +141,7 @@ class ConversationViewModel(
} }
fun declineMessageRequest() { fun declineMessageRequest() {
val recipient = recipient ?: return Log.w("Loki", "Recipient was null for decline message request action") repository.declineMessageRequest(threadId)
repository.declineMessageRequest(threadId, recipient)
} }
private fun showMessage(message: String) { private fun showMessage(message: String) {

View File

@ -1,8 +1,6 @@
package org.thoughtcrime.securesms.conversation.v2.menus package org.thoughtcrime.securesms.conversation.v2.menus
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
@ -24,7 +22,6 @@ import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat import androidx.core.graphics.drawable.IconCompat
import network.loki.messenger.R import network.loki.messenger.R
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate
import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.messaging.sending_receiving.leave import org.session.libsession.messaging.sending_receiving.leave
import org.session.libsession.utilities.ExpirationUtil import org.session.libsession.utilities.ExpirationUtil
@ -33,11 +30,8 @@ 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.guava.Optional import org.session.libsignal.utilities.guava.Optional
import org.session.libsignal.utilities.toHexString import org.session.libsignal.utilities.toHexString
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.ExpirationDialog
import org.thoughtcrime.securesms.MediaOverviewActivity import org.thoughtcrime.securesms.MediaOverviewActivity
import org.thoughtcrime.securesms.MuteDialog import org.thoughtcrime.securesms.MuteDialog
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.ShortcutLauncherActivity import org.thoughtcrime.securesms.ShortcutLauncherActivity
import org.thoughtcrime.securesms.calls.WebRtcCallActivity import org.thoughtcrime.securesms.calls.WebRtcCallActivity
import org.thoughtcrime.securesms.contacts.SelectContactsActivity import org.thoughtcrime.securesms.contacts.SelectContactsActivity
@ -156,7 +150,8 @@ object ConversationMenuHelper {
R.id.menu_expiring_messages -> { showExpiringMessagesDialog(context, thread) } R.id.menu_expiring_messages -> { showExpiringMessagesDialog(context, thread) }
R.id.menu_expiring_messages_off -> { showExpiringMessagesDialog(context, thread) } R.id.menu_expiring_messages_off -> { showExpiringMessagesDialog(context, thread) }
R.id.menu_unblock -> { unblock(context, thread) } R.id.menu_unblock -> { unblock(context, thread) }
R.id.menu_block -> { block(context, thread) } R.id.menu_block -> { block(context, thread, deleteThread = false) }
R.id.menu_block_delete -> { blockAndDelete(context, thread) }
R.id.menu_copy_session_id -> { copySessionID(context, thread) } R.id.menu_copy_session_id -> { copySessionID(context, thread) }
R.id.menu_edit_group -> { editClosedGroup(context, thread) } R.id.menu_edit_group -> { editClosedGroup(context, thread) }
R.id.menu_leave_group -> { leaveClosedGroup(context, thread) } R.id.menu_leave_group -> { leaveClosedGroup(context, thread) }
@ -246,59 +241,32 @@ object ConversationMenuHelper {
} }
private fun showExpiringMessagesDialog(context: Context, thread: Recipient) { private fun showExpiringMessagesDialog(context: Context, thread: Recipient) {
if (thread.isClosedGroupRecipient) { val listener = context as? ConversationMenuListener ?: return
val group = DatabaseComponent.get(context).groupDatabase().getGroup(thread.address.toGroupString()).orNull() listener.showExpiringMessagesDialog(thread)
if (group?.isActive == false) { return }
}
ExpirationDialog.show(context, thread.expireMessages) { expirationTime: Int ->
DatabaseComponent.get(context).recipientDatabase().setExpireMessages(thread, expirationTime)
val message = ExpirationTimerUpdate(expirationTime)
message.recipient = thread.address.serialize()
message.sentTimestamp = System.currentTimeMillis()
val expiringMessageManager = ApplicationContext.getInstance(context).expiringMessageManager
expiringMessageManager.setExpirationTimer(message)
MessageSender.send(message, thread.address)
val activity = context as AppCompatActivity
activity.invalidateOptionsMenu()
}
} }
private fun unblock(context: Context, thread: Recipient) { private fun unblock(context: Context, thread: Recipient) {
if (!thread.isContactRecipient) { return } if (!thread.isContactRecipient) { return }
val title = R.string.ConversationActivity_unblock_this_contact_question val listener = context as? ConversationMenuListener ?: return
val message = R.string.ConversationActivity_you_will_once_again_be_able_to_receive_messages_and_calls_from_this_contact listener.unblock()
AlertDialog.Builder(context)
.setTitle(title)
.setMessage(message)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.ConversationActivity_unblock) { _, _ ->
DatabaseComponent.get(context).recipientDatabase()
.setBlocked(thread, false)
}.show()
} }
private fun block(context: Context, thread: Recipient) { private fun block(context: Context, thread: Recipient, deleteThread: Boolean) {
if (!thread.isContactRecipient) { return } if (!thread.isContactRecipient) { return }
val title = R.string.RecipientPreferenceActivity_block_this_contact_question val listener = context as? ConversationMenuListener ?: return
val message = R.string.RecipientPreferenceActivity_you_will_no_longer_receive_messages_and_calls_from_this_contact listener.block(deleteThread)
AlertDialog.Builder(context) }
.setTitle(title)
.setMessage(message) private fun blockAndDelete(context: Context, thread: Recipient) {
.setNegativeButton(android.R.string.cancel, null) if (!thread.isContactRecipient) { return }
.setPositiveButton(R.string.RecipientPreferenceActivity_block) { _, _ -> val listener = context as? ConversationMenuListener ?: return
DatabaseComponent.get(context).recipientDatabase() listener.block(deleteThread = true)
.setBlocked(thread, true)
}.show()
} }
private fun copySessionID(context: Context, thread: Recipient) { private fun copySessionID(context: Context, thread: Recipient) {
if (!thread.isContactRecipient) { return } if (!thread.isContactRecipient) { return }
val sessionID = thread.address.toString() val listener = context as? ConversationMenuListener ?: return
val clip = ClipData.newPlainText("Session ID", sessionID) listener.copySessionID(thread.address.toString())
val activity = context as AppCompatActivity
val manager = activity.getSystemService(PassphraseRequiredActionBarActivity.CLIPBOARD_SERVICE) as ClipboardManager
manager.setPrimaryClip(clip)
Toast.makeText(context, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
} }
private fun editClosedGroup(context: Context, thread: Recipient) { private fun editClosedGroup(context: Context, thread: Recipient) {
@ -371,4 +339,11 @@ object ConversationMenuHelper {
} }
} }
interface ConversationMenuListener {
fun block(deleteThread: Boolean = false)
fun unblock()
fun copySessionID(sessionId: String)
fun showExpiringMessagesDialog(thread: Recipient)
}
} }

View File

@ -1,21 +1,13 @@
package org.thoughtcrime.securesms.conversation.v2.utilities package org.thoughtcrime.securesms.conversation.v2.utilities
import android.animation.ValueAnimator
import android.content.Context import android.content.Context
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Interpolator
import android.graphics.Paint import android.graphics.Paint
import android.graphics.Rect import android.graphics.Rect
import android.os.SystemClock import android.os.SystemClock
import android.util.AttributeSet import android.util.AttributeSet
import android.util.Log
import android.view.View import android.view.View
import android.view.animation.AccelerateDecelerateInterpolator
import android.view.animation.Animation
import android.view.animation.AnimationSet
import android.view.animation.AnimationUtils
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import network.loki.messenger.R import network.loki.messenger.R
import kotlin.math.sin import kotlin.math.sin

View File

@ -76,17 +76,32 @@ class MessageRequestsActivity : PassphraseRequiredActionBarActivity(), Conversat
push(intent) push(intent)
} }
override fun onLongConversationClick(thread: ThreadRecord) { override fun onBlockConversationClick(thread: ThreadRecord) {
val dialog = AlertDialog.Builder(this) val dialog = AlertDialog.Builder(this)
dialog.setMessage(resources.getString(R.string.message_requests_delete_message)) dialog.setTitle(R.string.RecipientPreferenceActivity_block_this_contact_question)
dialog.setPositiveButton(R.string.yes) { _, _ -> .setMessage(R.string.message_requests_block_message)
.setPositiveButton(R.string.recipient_preferences__block) { _, _ ->
viewModel.blockMessageRequest(thread)
LoaderManager.getInstance(this).restartLoader(0, null, this)
}
.setNegativeButton(R.string.no) { _, _ ->
// Do nothing
}
dialog.create().show()
}
override fun onDeleteConversationClick(thread: ThreadRecord) {
val dialog = AlertDialog.Builder(this)
dialog.setTitle(R.string.decline)
.setMessage(resources.getString(R.string.message_requests_decline_message))
.setPositiveButton(R.string.decline) { _,_ ->
viewModel.deleteMessageRequest(thread) viewModel.deleteMessageRequest(thread)
LoaderManager.getInstance(this).restartLoader(0, null, this) LoaderManager.getInstance(this).restartLoader(0, null, this)
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(this@MessageRequestsActivity) ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(this@MessageRequestsActivity)
} }
} }
dialog.setNegativeButton(R.string.no) { _, _ -> .setNegativeButton(R.string.no) { _, _ ->
// Do nothing // Do nothing
} }
dialog.create().show() dialog.create().show()

View File

@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.messagerequests package org.thoughtcrime.securesms.messagerequests
import android.content.Context import android.content.Context
import android.content.res.ColorStateList
import android.database.Cursor import android.database.Cursor
import android.text.SpannableString import android.text.SpannableString
import android.text.style.ForegroundColorSpan import android.text.style.ForegroundColorSpan
@ -8,6 +9,7 @@ import android.view.ViewGroup
import android.widget.PopupMenu import android.widget.PopupMenu
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import network.loki.messenger.R import network.loki.messenger.R
import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter
import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.database.model.ThreadRecord
import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.dependencies.DatabaseComponent
@ -49,7 +51,9 @@ class MessageRequestsAdapter(
popupMenu.menuInflater.inflate(R.menu.menu_message_request, popupMenu.menu) popupMenu.menuInflater.inflate(R.menu.menu_message_request, popupMenu.menu)
popupMenu.setOnMenuItemClickListener { menuItem -> popupMenu.setOnMenuItemClickListener { menuItem ->
if (menuItem.itemId == R.id.menu_delete_message_request) { if (menuItem.itemId == R.id.menu_delete_message_request) {
listener.onLongConversationClick(view.thread!!) listener.onDeleteConversationClick(view.thread!!)
} else if (menuItem.itemId == R.id.menu_block_message_request) {
listener.onBlockConversationClick(view.thread!!)
} }
true true
} }
@ -57,6 +61,7 @@ class MessageRequestsAdapter(
val item = popupMenu.menu.getItem(i) val item = popupMenu.menu.getItem(i)
val s = SpannableString(item.title) val s = SpannableString(item.title)
s.setSpan(ForegroundColorSpan(context.getColor(R.color.destructive)), 0, s.length, 0) s.setSpan(ForegroundColorSpan(context.getColor(R.color.destructive)), 0, s.length, 0)
item.iconTintList = ColorStateList.valueOf(context.getColor(R.color.destructive))
item.title = s item.title = s
} }
popupMenu.forceShowIcon() popupMenu.forceShowIcon()
@ -70,5 +75,6 @@ class MessageRequestsAdapter(
interface ConversationClickListener { interface ConversationClickListener {
fun onConversationClick(thread: ThreadRecord) fun onConversationClick(thread: ThreadRecord)
fun onLongConversationClick(thread: ThreadRecord) fun onBlockConversationClick(thread: ThreadRecord)
fun onDeleteConversationClick(thread: ThreadRecord)
} }

View File

@ -13,6 +13,14 @@ class MessageRequestsViewModel @Inject constructor(
private val repository: ConversationRepository private val repository: ConversationRepository
) : ViewModel() { ) : ViewModel() {
fun blockMessageRequest(thread: ThreadRecord) = viewModelScope.launch {
val recipient = thread.recipient
if (recipient.isContactRecipient) {
repository.setBlocked(recipient, true)
deleteMessageRequest(thread)
}
}
fun deleteMessageRequest(thread: ThreadRecord) = viewModelScope.launch { fun deleteMessageRequest(thread: ThreadRecord) = viewModelScope.launch {
repository.deleteMessageRequest(thread) repository.deleteMessageRequest(thread)
} }

View File

@ -37,7 +37,7 @@ interface ConversationRepository {
fun saveDraft(threadId: Long, text: String) fun saveDraft(threadId: Long, text: String)
fun getDraft(threadId: Long): String? fun getDraft(threadId: Long): String?
fun inviteContacts(threadId: Long, contacts: List<Recipient>) fun inviteContacts(threadId: Long, contacts: List<Recipient>)
fun unblock(recipient: Recipient) fun setBlocked(recipient: Recipient, blocked: Boolean)
fun deleteLocally(recipient: Recipient, message: MessageRecord) fun deleteLocally(recipient: Recipient, message: MessageRecord)
fun setApproved(recipient: Recipient, isApproved: Boolean) fun setApproved(recipient: Recipient, isApproved: Boolean)
@ -58,13 +58,15 @@ interface ConversationRepository {
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 deleteMessageRequest(thread: ThreadRecord): ResultOf<Unit> suspend fun deleteMessageRequest(thread: ThreadRecord): ResultOf<Unit>
suspend fun clearAllMessageRequests(): ResultOf<Unit> suspend fun clearAllMessageRequests(): ResultOf<Unit>
suspend fun acceptMessageRequest(threadId: Long, recipient: Recipient): ResultOf<Unit> suspend fun acceptMessageRequest(threadId: Long, recipient: Recipient): ResultOf<Unit>
fun declineMessageRequest(threadId: Long, recipient: Recipient) fun declineMessageRequest(threadId: Long)
fun hasReceived(threadId: Long): Boolean fun hasReceived(threadId: Long): Boolean
@ -125,8 +127,8 @@ class DefaultConversationRepository @Inject constructor(
} }
} }
override fun unblock(recipient: Recipient) { override fun setBlocked(recipient: Recipient, blocked: Boolean) {
recipientDb.setBlocked(recipient, false) recipientDb.setBlocked(recipient, blocked)
} }
override fun deleteLocally(recipient: Recipient, message: MessageRecord) { override fun deleteLocally(recipient: Recipient, message: MessageRecord) {
@ -248,9 +250,15 @@ class DefaultConversationRepository @Inject constructor(
} }
} }
override suspend fun deleteThread(threadId: Long): ResultOf<Unit> {
sessionJobDb.cancelPendingMessageSendJobs(threadId)
threadDb.deleteConversation(threadId)
return ResultOf.Success(Unit)
}
override suspend fun deleteMessageRequest(thread: ThreadRecord): ResultOf<Unit> { override suspend fun deleteMessageRequest(thread: ThreadRecord): ResultOf<Unit> {
sessionJobDb.cancelPendingMessageSendJobs(thread.threadId) sessionJobDb.cancelPendingMessageSendJobs(thread.threadId)
recipientDb.setBlocked(thread.recipient, true) threadDb.deleteConversation(thread.threadId)
return ResultOf.Success(Unit) return ResultOf.Success(Unit)
} }
@ -275,8 +283,9 @@ class DefaultConversationRepository @Inject constructor(
} }
} }
override fun declineMessageRequest(threadId: Long, recipient: Recipient) { override fun declineMessageRequest(threadId: Long) {
recipientDb.setBlocked(recipient, true) sessionJobDb.cancelPendingMessageSendJobs(threadId)
threadDb.deleteConversation(threadId)
} }
override fun hasReceived(threadId: Long): Boolean { override fun hasReceived(threadId: Long): Boolean {

View File

@ -201,6 +201,17 @@
android:visibility="gone" android:visibility="gone"
tools:visibility="visible"> tools:visibility="visible">
<TextView
android:id="@+id/messageRequestBlock"
android:layout_gravity="center"
android:textColor="@color/destructive"
android:paddingHorizontal="@dimen/massive_spacing"
android:paddingVertical="@dimen/small_spacing"
android:textSize="@dimen/text_size"
android:text="@string/activity_conversation_block_user"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView <TextView
android:id="@+id/sendAcceptsTextView" android:id="@+id/sendAcceptsTextView"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
style="@android:style/Widget.ActionButton"
android:orientation="horizontal"
android:gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:layout_margin="@dimen/very_small_spacing"
android:layout_width="13dp"
android:layout_height="13dp"
android:src="@drawable/ic_baseline_block_24"
app:tint="@color/destructive" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold"
android:textColor="@color/destructive"
android:textSize="16sp"
android:text="@string/recipient_preferences__block"
/>
</LinearLayout>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
app:actionLayout="@layout/layout_conversation_block_icon"
app:showAsAction="always"
android:title="@string/recipient_preferences__block"
android:id="@+id/menu_block_delete" />
</menu>

View File

@ -4,6 +4,11 @@
<item <item
android:id="@+id/menu_delete_message_request" android:id="@+id/menu_delete_message_request"
android:icon="@drawable/ic_delete_24" android:icon="@drawable/ic_delete_24"
android:title="@string/delete"/> android:title="@string/decline"/>
<item
android:id="@+id/menu_block_message_request"
android:icon="@drawable/ic_baseline_block_24"
android:title="@string/RecipientPreferenceActivity_block"/>
</menu> </menu>

View File

@ -41,6 +41,7 @@
<dimen name="input_bar_button_expanded_size">48dp</dimen> <dimen name="input_bar_button_expanded_size">48dp</dimen>
<!-- Distances --> <!-- Distances -->
<dimen name="very_small_spacing">4dp</dimen>
<dimen name="small_spacing">8dp</dimen> <dimen name="small_spacing">8dp</dimen>
<dimen name="medium_spacing">16dp</dimen> <dimen name="medium_spacing">16dp</dimen>
<dimen name="large_spacing">24dp</dimen> <dimen name="large_spacing">24dp</dimen>

View File

@ -865,6 +865,7 @@
<string name="dialog_download_button_title">Download</string> <string name="dialog_download_button_title">Download</string>
<string name="activity_conversation_blocked_banner_text">%s is blocked. Unblock them?</string> <string name="activity_conversation_blocked_banner_text">%s is blocked. Unblock them?</string>
<string name="activity_conversation_block_user">Block User</string>
<string name="activity_conversation_attachment_prep_failed">Failed to prepare attachment for sending.</string> <string name="activity_conversation_attachment_prep_failed">Failed to prepare attachment for sending.</string>
<string name="media">Media</string> <string name="media">Media</string>
@ -894,7 +895,8 @@
<string name="accept">Accept</string> <string name="accept">Accept</string>
<string name="decline">Decline</string> <string name="decline">Decline</string>
<string name="message_requests_clear_all">Clear All</string> <string name="message_requests_clear_all">Clear All</string>
<string name="message_requests_delete_message">Are you sure you want to delete this message request?</string> <string name="message_requests_decline_message">Are you sure you want to decline this message request?</string>
<string name="message_requests_block_message">Are you sure you want to block this message request?</string>
<string name="message_requests_deleted">Message request deleted</string> <string name="message_requests_deleted">Message request deleted</string>
<string name="message_requests_clear_all_message">Are you sure you want to clear all message requests?</string> <string name="message_requests_clear_all_message">Are you sure you want to clear all message requests?</string>
<string name="message_requests_cleared">Message requests deleted</string> <string name="message_requests_cleared">Message requests deleted</string>

View File

@ -81,7 +81,7 @@ class ConversationViewModelTest: BaseViewModelTest() {
viewModel.unblock() viewModel.unblock()
verify(repository).unblock(recipient) verify(repository).setBlocked(recipient, false)
} }
@Test @Test
@ -173,7 +173,7 @@ class ConversationViewModelTest: BaseViewModelTest() {
fun `should decline message request`() { fun `should decline message request`() {
viewModel.declineMessageRequest() viewModel.declineMessageRequest()
verify(repository).declineMessageRequest(threadId, recipient) verify(repository).declineMessageRequest(threadId)
} }
@Test @Test

View File

@ -1,6 +1,5 @@
package org.session.libsession.messaging.sending_receiving package org.session.libsession.messaging.sending_receiving
import android.util.Log
import com.goterl.lazysodium.LazySodiumAndroid import com.goterl.lazysodium.LazySodiumAndroid
import com.goterl.lazysodium.SodiumAndroid import com.goterl.lazysodium.SodiumAndroid
import com.goterl.lazysodium.interfaces.Box import com.goterl.lazysodium.interfaces.Box
@ -12,6 +11,7 @@ import org.session.libsession.messaging.utilities.SodiumUtilities
import org.session.libsignal.crypto.ecc.ECKeyPair import org.session.libsignal.crypto.ecc.ECKeyPair
import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.Hex
import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.IdPrefix
import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.hexEncodedPublicKey import org.session.libsignal.utilities.hexEncodedPublicKey
import org.session.libsignal.utilities.removingIdPrefixIfNeeded import org.session.libsignal.utilities.removingIdPrefixIfNeeded