Implement conversation item context menu interaction

This commit is contained in:
nielsandriesse 2021-06-07 14:04:55 +10:00
parent 4ecfd1f230
commit e1345a8774
5 changed files with 109 additions and 53 deletions

View File

@ -14,15 +14,28 @@ import kotlinx.android.synthetic.main.activity_conversation_v2.*
import kotlinx.android.synthetic.main.activity_conversation_v2_action_bar.* import kotlinx.android.synthetic.main.activity_conversation_v2_action_bar.*
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.conversation.v2.menus.ConversationActionModeCallback
import org.thoughtcrime.securesms.conversation.v2.menus.ConversationMenuHelper
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.mms.GlideApp
class ConversationActivityV2 : PassphraseRequiredActionBarActivity() { class ConversationActivityV2 : PassphraseRequiredActionBarActivity() {
private var threadID: Long = -1 private var threadID: Long = -1
private var actionMode: ActionMode? = null
private val adapter by lazy { private val adapter by lazy {
val cursor = DatabaseFactory.getMmsSmsDatabase(this).getConversation(threadID) val cursor = DatabaseFactory.getMmsSmsDatabase(this).getConversation(threadID)
val adapter = ConversationAdapter(this, cursor) { handleLongPress(it) } val adapter = ConversationAdapter(
this,
cursor,
onItemPress = { message, position ->
handlePress(message, position)
},
onItemLongPress = { message, position ->
handleLongPress(message, position)
}
)
adapter.setHasStableIds(true) adapter.setHasStableIds(true)
adapter adapter
} }
@ -84,7 +97,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity() {
} }
override fun onPrepareOptionsMenu(menu: Menu): Boolean { override fun onPrepareOptionsMenu(menu: Menu): Boolean {
return ConversationMenuHelper.onPrepareOptionsMenu(menu, menuInflater, thread, this) { onOptionsItemSelected(it) } ConversationMenuHelper.onPrepareOptionsMenu(menu, menuInflater, thread, this) { onOptionsItemSelected(it) }
// FIXME: Make the menu respect the current app theme
super.onPrepareOptionsMenu(menu)
return true
} }
// endregion // endregion
@ -98,13 +114,37 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity() {
Log.d("Loki", "Reply to message at position: $messagePosition.") Log.d("Loki", "Reply to message at position: $messagePosition.")
} }
private fun handleLongPress(messagePosition: Int) { private fun handlePress(message: MessageRecord, position: Int) {
val actionMode = this.actionMode
if (actionMode != null) {
adapter.toggleSelection(message, position)
val actionModeCallback = ConversationActionModeCallback(adapter, threadID, this) val actionModeCallback = ConversationActionModeCallback(adapter, threadID, this)
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { actionModeCallback.updateActionModeMenu(actionMode.menu)
if (adapter.selectedItems.isEmpty()) {
actionMode.finish()
this.actionMode = null
}
}
}
private fun handleLongPress(message: MessageRecord, position: Int) {
val actionMode = this.actionMode
val actionModeCallback = ConversationActionModeCallback(adapter, threadID, this)
if (actionMode == null) { // Nothing should be selected if this is the case
adapter.toggleSelection(message, position)
this.actionMode = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
startActionMode(actionModeCallback, ActionMode.TYPE_PRIMARY) startActionMode(actionModeCallback, ActionMode.TYPE_PRIMARY)
} else { } else {
startActionMode(actionModeCallback) startActionMode(actionModeCallback)
} }
} else {
adapter.toggleSelection(message, position)
actionModeCallback.updateActionModeMenu(actionMode.menu)
if (adapter.selectedItems.isEmpty()) {
actionMode.finish()
this.actionMode = null
}
}
} }
// endregion // endregion
} }

View File

@ -2,19 +2,22 @@ package org.thoughtcrime.securesms.conversation.v2
import android.content.Context import android.content.Context
import android.database.Cursor import android.database.Cursor
import android.graphics.drawable.ColorDrawable
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView.ViewHolder import androidx.recyclerview.widget.RecyclerView.ViewHolder
import network.loki.messenger.R
import org.thoughtcrime.securesms.conversation.v2.messages.ControlMessageView import org.thoughtcrime.securesms.conversation.v2.messages.ControlMessageView
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.loki.utilities.getColorWithID
import java.lang.IllegalStateException import java.lang.IllegalStateException
class ConversationAdapter(context: Context, cursor: Cursor, private val onItemLongPress: (Int) -> Unit) class ConversationAdapter(context: Context, cursor: Cursor, private val onItemPress: (MessageRecord, Int) -> Unit,
: CursorRecyclerViewAdapter<ViewHolder>(context, cursor) { private val onItemLongPress: (MessageRecord, Int) -> Unit) : CursorRecyclerViewAdapter<ViewHolder>(context, cursor) {
private val messageDB = DatabaseFactory.getMmsSmsDatabase(context) private val messageDB = DatabaseFactory.getMmsSmsDatabase(context)
var selectedItems = setOf<MessageRecord>() var selectedItems = mutableSetOf<MessageRecord>()
sealed class ViewType(val rawValue: Int) { sealed class ViewType(val rawValue: Int) {
object Visible : ViewType(0) object Visible : ViewType(0)
@ -59,9 +62,15 @@ class ConversationAdapter(context: Context, cursor: Cursor, private val onItemLo
when (viewHolder) { when (viewHolder) {
is VisibleMessageViewHolder -> { is VisibleMessageViewHolder -> {
val view = viewHolder.view val view = viewHolder.view
view.background = if (selectedItems.contains(message)) {
ColorDrawable(context.resources.getColorWithID(R.color.red, context.theme))
} else {
null
}
view.bind(message) view.bind(message)
view.setOnClickListener { onItemPress(message, viewHolder.adapterPosition) }
view.setOnLongClickListener { view.setOnLongClickListener {
onItemLongPress(viewHolder.adapterPosition) onItemLongPress(message, viewHolder.adapterPosition)
true true
} }
} }
@ -80,4 +89,9 @@ class ConversationAdapter(context: Context, cursor: Cursor, private val onItemLo
private fun getMessage(cursor: Cursor): MessageRecord? { private fun getMessage(cursor: Cursor): MessageRecord? {
return messageDB.readerFor(cursor).current return messageDB.readerFor(cursor).current
} }
fun toggleSelection(message: MessageRecord, position: Int) {
if (selectedItems.contains(message)) selectedItems.remove(message) else selectedItems.add(message)
notifyItemChanged(position)
}
} }

View File

@ -1,4 +1,4 @@
package org.thoughtcrime.securesms.conversation.v2 package org.thoughtcrime.securesms.conversation.v2.menus
import android.content.Context import android.content.Context
import android.view.ActionMode import android.view.ActionMode
@ -7,21 +7,26 @@ import android.view.MenuItem
import network.loki.messenger.R import network.loki.messenger.R
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2 import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.thoughtcrime.securesms.conversation.v2.ConversationAdapter
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord
import java.util.*
class ConversationActionModeCallback(private val adapter: ConversationAdapter, private val threadID: Long, class ConversationActionModeCallback(private val adapter: ConversationAdapter, private val threadID: Long,
private val context: Context) : ActionMode.Callback { private val context: Context) : ActionMode.Callback {
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
// Prepare
val inflater = mode.menuInflater val inflater = mode.menuInflater
inflater.inflate(R.menu.menu_conversation_item_action, menu) inflater.inflate(R.menu.menu_conversation_item_action, menu)
updateActionModeMenu(menu)
return true
}
fun updateActionModeMenu(menu: Menu) {
// Prepare
val selectedItems = adapter.selectedItems val selectedItems = adapter.selectedItems
val containsControlMessage = selectedItems.any { it.isUpdate } val containsControlMessage = selectedItems.any { it.isUpdate }
val hasText = selectedItems.any { it.body.isNotEmpty() } val hasText = selectedItems.any { it.body.isNotEmpty() }
if (selectedItems.isEmpty()) { mode.finish(); return false } if (selectedItems.isEmpty()) { return }
val firstMessage = selectedItems.iterator().next() val firstMessage = selectedItems.iterator().next()
val openGroup = DatabaseFactory.getLokiThreadDatabase(context).getOpenGroupChat(threadID) val openGroup = DatabaseFactory.getLokiThreadDatabase(context).getOpenGroupChat(threadID)
val userPublicKey = TextSecurePreferences.getLocalNumber(context)!! val userPublicKey = TextSecurePreferences.getLocalNumber(context)!!
@ -58,8 +63,6 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p
// Reply // Reply
menu.findItem(R.id.menu_context_reply).isVisible = menu.findItem(R.id.menu_context_reply).isVisible =
(selectedItems.size == 1 && !firstMessage.isPending && !firstMessage.isFailed) (selectedItems.size == 1 && !firstMessage.isPending && !firstMessage.isFailed)
// Return
return true
} }
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu): Boolean { override fun onPrepareActionMode(mode: ActionMode?, menu: Menu): Boolean {
@ -71,6 +74,7 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p
} }
override fun onDestroyActionMode(mode: ActionMode) { override fun onDestroyActionMode(mode: ActionMode) {
adapter.selectedItems.clear()
adapter.notifyDataSetChanged()
} }
} }

View File

@ -1,4 +1,4 @@
package org.thoughtcrime.securesms.conversation.v2 package org.thoughtcrime.securesms.conversation.v2.menus
import android.content.Context import android.content.Context
import android.graphics.PorterDuff import android.graphics.PorterDuff
@ -16,7 +16,7 @@ import org.thoughtcrime.securesms.loki.utilities.getColorWithID
object ConversationMenuHelper { object ConversationMenuHelper {
fun onPrepareOptionsMenu(menu: Menu, inflater: MenuInflater, thread: Recipient, context: Context, onOptionsItemSelected: (MenuItem) -> Unit): Boolean { fun onPrepareOptionsMenu(menu: Menu, inflater: MenuInflater, thread: Recipient, context: Context, onOptionsItemSelected: (MenuItem) -> Unit) {
// Prepare // Prepare
menu.clear() menu.clear()
val isOpenGroup = thread.isOpenGroupRecipient val isOpenGroup = thread.isOpenGroupRecipient
@ -62,7 +62,5 @@ object ConversationMenuHelper {
inflater.inflate(R.menu.menu_conversation_unmuted, menu) inflater.inflate(R.menu.menu_conversation_unmuted, menu)
} }
// TODO: Implement search // TODO: Implement search
// Return
return true
} }
} }

View File

@ -4,37 +4,11 @@
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item <item
android:title="@string/conversation_context__menu_message_details" android:title="@string/conversation_context__menu_reply_to_message"
android:id="@+id/menu_context_details" android:id="@+id/menu_context_reply"
app:showAsAction="never" /> android:icon="?menu_reply_icon"
<item
android:title="@string/conversation_context__menu_delete_message"
android:id="@+id/menu_context_delete_message"
android:icon="?menu_trash_icon"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />
<item
android:title="@string/conversation_context__menu_ban_user"
android:id="@+id/menu_context_ban_user"
app:showAsAction="never" />
<item
android:title="@string/conversation_context__menu_copy_text"
android:id="@+id/menu_context_copy"
android:icon="?menu_copy_icon"
app:showAsAction="ifRoom" />
<item
android:title="@string/activity_conversation_copy_public_key_button_title"
android:id="@+id/menu_context_copy_public_key"
app:showAsAction="never" />
<item
android:title="@string/conversation_context__menu_resend_message"
android:id="@+id/menu_context_resend"
app:showAsAction="never" />
<item <item
android:title="@string/conversation_context_image__save_attachment" android:title="@string/conversation_context_image__save_attachment"
android:id="@+id/menu_context_save_attachment" android:id="@+id/menu_context_save_attachment"
@ -42,9 +16,35 @@
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />
<item <item
android:title="@string/conversation_context__menu_reply_to_message" android:title="@string/conversation_context__menu_delete_message"
android:id="@+id/menu_context_reply" android:id="@+id/menu_context_delete_message"
android:icon="?menu_reply_icon" android:icon="?menu_trash_icon"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />
<item
android:title="@string/conversation_context__menu_copy_text"
android:id="@+id/menu_context_copy"
android:icon="?menu_copy_icon"
app:showAsAction="ifRoom" />
<item
android:title="@string/conversation_context__menu_resend_message"
android:id="@+id/menu_context_resend"
app:showAsAction="never" />
<item
android:title="@string/conversation_context__menu_message_details"
android:id="@+id/menu_context_details"
app:showAsAction="never" />
<item
android:title="@string/conversation_context__menu_ban_user"
android:id="@+id/menu_context_ban_user"
app:showAsAction="never" />
<item
android:title="@string/activity_conversation_copy_public_key_button_title"
android:id="@+id/menu_context_copy_public_key"
app:showAsAction="never" />
</menu> </menu>