From 89811d4aa49d22ec31633c37c6986fd3b35d783f Mon Sep 17 00:00:00 2001 From: andrekir Date: Tue, 19 Apr 2022 15:04:18 -0300 Subject: [PATCH 1/2] cleanup actionmode --- .../com/geeksville/mesh/IMeshService.aidl | 2 +- .../java/com/geeksville/mesh/MainActivity.kt | 5 +- .../geeksville/mesh/model/MessagesState.kt | 16 +- .../geeksville/mesh/service/MeshService.kt | 10 +- .../geeksville/mesh/ui/ContactsFragment.kt | 254 ++++++----------- .../geeksville/mesh/ui/MessagesFragment.kt | 265 +++++++----------- 6 files changed, 205 insertions(+), 347 deletions(-) diff --git a/app/src/main/aidl/com/geeksville/mesh/IMeshService.aidl b/app/src/main/aidl/com/geeksville/mesh/IMeshService.aidl index 8242c67be..62767952b 100644 --- a/app/src/main/aidl/com/geeksville/mesh/IMeshService.aidl +++ b/app/src/main/aidl/com/geeksville/mesh/IMeshService.aidl @@ -66,7 +66,7 @@ interface IMeshService { */ void send(inout DataPacket packet); - void deleteMessage(int packetId); + void deleteMessages(in List deleteList); void deleteAllMessages(); diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index 6202627cb..66ce9a683 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -607,10 +607,7 @@ class MainActivity : BaseActivity(), Logging, private fun updateNodesFromDevice() { model.meshService?.let { service -> // Update our nodeinfos based on data from the device - val nodes = service.nodes.map { - it.user?.id!! to it - }.toMap() - + val nodes = service.nodes.associateBy { it.user?.id!! } model.nodeDB.nodes.value = nodes try { diff --git a/app/src/main/java/com/geeksville/mesh/model/MessagesState.kt b/app/src/main/java/com/geeksville/mesh/model/MessagesState.kt index 470ce8a5e..8da1e3c94 100644 --- a/app/src/main/java/com/geeksville/mesh/model/MessagesState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/MessagesState.kt @@ -77,10 +77,10 @@ class MessagesState(private val ui: UIViewModel) : Logging { buildContacts() } - fun removeMessage(m: DataPacket) { - debug("Removing message from view id=${m.id}") + private fun removeMessages(deleteList: List) { + debug("Removing ${deleteList.size} messages from view") - messagesList.remove(m) + messagesList.removeAll(deleteList) messages.value = messagesList buildContacts() } @@ -128,19 +128,19 @@ class MessagesState(private val ui: UIViewModel) : Logging { addMessage(p) } - fun deleteMessage(packet: DataPacket) { + fun deleteMessages(deleteList: List) { val service = ui.meshService if (service != null) { try { - service.deleteMessage(packet.id) + service.deleteMessages(deleteList) } catch (ex: RemoteException) { - packet.errorMessage = "Error: ${ex.message}" + debug("Error: ${ex.message}") } } else { - packet.errorMessage = "Error: No Mesh service" + debug("Error: No Mesh service") } - removeMessage(packet) + removeMessages(deleteList) } fun deleteAllMessages() { diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt index 638dcf835..aa7b32baa 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -1796,15 +1796,13 @@ class MeshService : Service(), Logging { this@MeshService.setOwner(myId, longName, shortName) } - override fun deleteMessage(packetId: Int) { - val packet = recentDataPackets.find {it.id == packetId} - if (packet != null) { - recentDataPackets.remove(packet) - debug("Deleting message id=${packet.id}") - } else debug("Nothing to delete, message id=${packetId} not found") + override fun deleteMessages(deleteList: List) { + debug("Deleting ${deleteList.size} messages") + recentDataPackets.removeAll(deleteList) } override fun deleteAllMessages() { + debug("Deleting all messages") recentDataPackets.clear() } diff --git a/app/src/main/java/com/geeksville/mesh/ui/ContactsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/ContactsFragment.kt index 5a33898eb..320a3076e 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/ContactsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/ContactsFragment.kt @@ -4,6 +4,8 @@ import android.graphics.Color import android.graphics.drawable.GradientDrawable import android.os.Bundle import android.view.* +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.view.ActionMode import androidx.core.content.ContextCompat import androidx.core.os.bundleOf import androidx.fragment.app.activityViewModels @@ -12,7 +14,6 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.geeksville.android.Logging import com.geeksville.mesh.DataPacket -import com.geeksville.mesh.MainActivity import com.geeksville.mesh.R import com.geeksville.mesh.databinding.AdapterContactLayoutBinding import com.geeksville.mesh.databinding.FragmentContactsBinding @@ -25,6 +26,7 @@ import java.util.* @AndroidEntryPoint class ContactsFragment : ScreenFragment("Messages"), Logging { + private val actionModeCallback: ActionModeCallback = ActionModeCallback() private var actionMode: ActionMode? = null private var _binding: FragmentContactsBinding? = null @@ -33,19 +35,6 @@ class ContactsFragment : ScreenFragment("Messages"), Logging { private val model: UIViewModel by activityViewModels() - private val dateTimeFormat: DateFormat = - DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT) - private val timeFormat: DateFormat = - DateFormat.getTimeInstance(DateFormat.SHORT) - - private fun getShortDateTime(time: Date): String { - // return time if within 24 hours, otherwise date/time - val oneDayMsec = 60 * 60 * 24 * 1000L - return if (System.currentTimeMillis() - time.time > oneDayMsec) { - dateTimeFormat.format(time) - } else timeFormat.format(time) - } - // Provide a direct reference to each of the views within a data item // Used to cache the views within the item layout for fast access class ViewHolder(itemView: AdapterContactLayoutBinding) : @@ -58,29 +47,19 @@ class ContactsFragment : ScreenFragment("Messages"), Logging { private val contactsAdapter = object : RecyclerView.Adapter() { - /** - * Called when RecyclerView needs a new [ViewHolder] of the given type to represent - * an item. - * - * - * This new ViewHolder should be constructed with a new View that can represent the items - * of the given type. You can either create a new View manually or inflate it from an XML - * layout file. - * - * - * The new ViewHolder will be used to display items of the adapter using - * [.onBindViewHolder]. Since it will be re-used to display - * different items in the data set, it is a good idea to cache references to sub views of - * the View to avoid unnecessary [View.findViewById] calls. - * - * @param parent The ViewGroup into which the new View will be added after it is bound to - * an adapter position. - * @param viewType The view type of the new View. - * - * @return A new ViewHolder that holds a View of the given view type. - * @see .getItemViewType - * @see .onBindViewHolder - */ + private val dateTimeFormat: DateFormat = + DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT) + private val timeFormat: DateFormat = + DateFormat.getTimeInstance(DateFormat.SHORT) + + private fun getShortDateTime(time: Date): String { + // return time if within 24 hours, otherwise date/time + val oneDayMsec = 60 * 60 * 24 * 1000L + return if (System.currentTimeMillis() - time.time > oneDayMsec) { + dateTimeFormat.format(time) + } else timeFormat.format(time) + } + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val inflater = LayoutInflater.from(requireContext()) @@ -91,38 +70,11 @@ class ContactsFragment : ScreenFragment("Messages"), Logging { return ViewHolder(contactsView) } - private var messages = arrayOf() - private var contacts = arrayOf() - private var selectedList = ArrayList() + var contacts = arrayOf() + var selectedList = ArrayList() - /** - * Returns the total number of items in the data set held by the adapter. - * - * @return The total number of items in this adapter. - */ override fun getItemCount(): Int = contacts.size - /** - * Called by RecyclerView to display the data at the specified position. This method should - * update the contents of the [ViewHolder.itemView] to reflect the item at the given - * position. - * - * - * Note that unlike [android.widget.ListView], RecyclerView will not call this method - * again if the position of the item changes in the data set unless the item itself is - * invalidated or the new position cannot be determined. For this reason, you should only - * use the `position` parameter while acquiring the related data item inside - * this method and should not keep a copy of it. If you need the position of an item later - * on (e.g. in a click listener), use [ViewHolder.getAdapterPosition] which will - * have the updated adapter position. - * - * Override [.onBindViewHolder] instead if Adapter can - * handle efficient partial bind. - * - * @param holder The ViewHolder which should be updated to represent the contents of the - * item at the given position in the data set. - * @param position The position of the item within the adapter's data set. - */ override fun onBindViewHolder(holder: ViewHolder, position: Int) { val contact = contacts[position] @@ -156,92 +108,10 @@ class ContactsFragment : ScreenFragment("Messages"), Logging { } else holder.lastMessageTime.visibility = View.INVISIBLE holder.itemView.setOnLongClickListener { + clickItem(holder, contactId) if (actionMode == null) { actionMode = - (activity as MainActivity).startActionMode(object : ActionMode.Callback { - override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { - mode.menuInflater.inflate(R.menu.menu_messages, menu) - mode.title = "1" - return true - } - - override fun onPrepareActionMode( - mode: ActionMode, - menu: Menu - ): Boolean { - clickItem(holder, contactId) - return true - } - - override fun onActionItemClicked( - mode: ActionMode, - item: MenuItem - ): Boolean { - when (item.itemId) { - R.id.deleteButton -> { - val messagesByContactId = ArrayList() - selectedList.forEach { contactId -> - messagesByContactId += messages.filter { - if (contactId == DataPacket.ID_BROADCAST) - it.to == DataPacket.ID_BROADCAST - else - it.from == contactId && it.to != DataPacket.ID_BROADCAST - || it.from == DataPacket.ID_LOCAL && it.to == contactId - } - } - val deleteMessagesString = resources.getQuantityString( - R.plurals.delete_messages, - messagesByContactId.size, - messagesByContactId.size - ) - MaterialAlertDialogBuilder(requireContext()) - .setMessage(deleteMessagesString) - .setPositiveButton(getString(R.string.delete)) { _, _ -> - debug("User clicked deleteButton") - // all items selected --> deleteAllMessages() - if (messagesByContactId.size == messages.size) { - model.messagesState.deleteAllMessages() - } else { - messagesByContactId.forEach { - model.messagesState.deleteMessage(it) - } - } - mode.finish() - } - .setNeutralButton(R.string.cancel) { _, _ -> - } - .show() - } - R.id.selectAllButton -> { - // if all selected -> unselect all - if (selectedList.size == contacts.size) { - selectedList.clear() - mode.finish() - } else { - // else --> select all - selectedList.clear() - - contacts.forEach { - if (it.from == DataPacket.ID_LOCAL || it.to == DataPacket.ID_BROADCAST) - selectedList.add(it.to!!) else selectedList.add(it.from!!) - } - } - actionMode?.title = selectedList.size.toString() - notifyDataSetChanged() - } - } - return true - } - - override fun onDestroyActionMode(mode: ActionMode) { - selectedList.clear() - notifyDataSetChanged() - actionMode = null - } - }) - } else { - // when action mode is enabled - clickItem(holder, contactId) + (activity as AppCompatActivity).startSupportActionMode(actionModeCallback) } true } @@ -280,10 +150,7 @@ class ContactsFragment : ScreenFragment("Messages"), Logging { } } - private fun clickItem( - holder: ViewHolder, - contactId: String? = DataPacket.ID_BROADCAST - ) { + private fun clickItem(holder: ViewHolder, contactId: String? = DataPacket.ID_BROADCAST) { val position = holder.bindingAdapterPosition if (contactId != null && !selectedList.contains(contactId)) { selectedList.add(contactId) @@ -306,11 +173,6 @@ class ContactsFragment : ScreenFragment("Messages"), Logging { notifyDataSetChanged() // FIXME, this is super expensive and redraws all nodes } - /// Called when our message DB changes - fun onMessagesChanged(msgIn: Collection) { - messages = msgIn.toTypedArray() - } - fun onChannelsChanged() { val oldBroadcast = contacts.find { it.to == DataPacket.ID_BROADCAST } if (oldBroadcast != null) { @@ -346,9 +208,79 @@ class ContactsFragment : ScreenFragment("Messages"), Logging { debug("New contacts received: ${it.size}") contactsAdapter.onContactsChanged(it.values) } + } - model.messagesState.messages.observe(viewLifecycleOwner) { - contactsAdapter.onMessagesChanged(it) + private inner class ActionModeCallback : ActionMode.Callback { + override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { + mode.menuInflater.inflate(R.menu.menu_messages, menu) + mode.title = "1" + return true + } + + override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean { + return false + } + + override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { + when (item.itemId) { + R.id.deleteButton -> { + val messagesTotal = model.messagesState.messages.value!! + val selectedList = contactsAdapter.selectedList + val deleteList = ArrayList() + // find messages for each contactId + selectedList.forEach { contactId -> + deleteList += messagesTotal.filter { + if (contactId == DataPacket.ID_BROADCAST) + it.to == DataPacket.ID_BROADCAST || it.delayed == 1 // MeshPacket.Delayed.DELAYED_BROADCAST_VALUE == 1 + else it.from == contactId && it.to != DataPacket.ID_BROADCAST || it.from == DataPacket.ID_LOCAL && it.to == contactId + } + } + val deleteMessagesString = resources.getQuantityString( + R.plurals.delete_messages, + deleteList.size, + deleteList.size + ) + MaterialAlertDialogBuilder(requireContext()) + .setMessage(deleteMessagesString) + .setPositiveButton(getString(R.string.delete)) { _, _ -> + debug("User clicked deleteButton") + // all items selected --> deleteAllMessages() + if (deleteList.size == messagesTotal.size) { + model.messagesState.deleteAllMessages() + } else { + model.messagesState.deleteMessages(deleteList) + } + mode.finish() + } + .setNeutralButton(R.string.cancel) { _, _ -> + } + .show() + } + R.id.selectAllButton -> { + // if all selected -> unselect all + if (contactsAdapter.selectedList.size == contactsAdapter.contacts.size) { + contactsAdapter.selectedList.clear() + mode.finish() + } else { + // else --> select all + contactsAdapter.selectedList.clear() + contactsAdapter.contacts.forEach { + if (it.from == DataPacket.ID_LOCAL || it.to == DataPacket.ID_BROADCAST) + contactsAdapter.selectedList.add(it.to!!) + else contactsAdapter.selectedList.add(it.from!!) + } + } + actionMode?.title = contactsAdapter.selectedList.size.toString() + contactsAdapter.notifyDataSetChanged() + } + } + return true + } + + override fun onDestroyActionMode(mode: ActionMode) { + contactsAdapter.selectedList.clear() + contactsAdapter.notifyDataSetChanged() + actionMode = null } } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt index 7e30100f5..5e490c6af 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt @@ -9,6 +9,8 @@ import android.view.inputmethod.EditorInfo import android.widget.EditText import android.widget.ImageView import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.view.ActionMode import androidx.cardview.widget.CardView import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment @@ -18,7 +20,6 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.geeksville.android.Logging import com.geeksville.mesh.DataPacket -import com.geeksville.mesh.MainActivity import com.geeksville.mesh.MessageStatus import com.geeksville.mesh.R import com.geeksville.mesh.databinding.AdapterMessageLayoutBinding @@ -45,6 +46,7 @@ fun EditText.on(actionId: Int, func: () -> Unit) { @AndroidEntryPoint class MessagesFragment : Fragment(), Logging { + private val actionModeCallback: ActionModeCallback = ActionModeCallback() private var actionMode: ActionMode? = null private var _binding: MessagesFragmentBinding? = null @@ -69,19 +71,6 @@ class MessagesFragment : Fragment(), Logging { } } - private val dateTimeFormat: DateFormat = - DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT) - private val timeFormat: DateFormat = - DateFormat.getTimeInstance(DateFormat.SHORT) - - private fun getShortDateTime(time: Date): String { - // return time if within 24 hours, otherwise date/time - val oneDayMsec = 60 * 60 * 24 * 1000L - return if (System.currentTimeMillis() - time.time > oneDayMsec) { - dateTimeFormat.format(time) - } else timeFormat.format(time) - } - // Provide a direct reference to each of the views within a data item // Used to cache the views within the item layout for fast access class ViewHolder(itemView: AdapterMessageLayoutBinding) : @@ -95,29 +84,19 @@ class MessagesFragment : Fragment(), Logging { private val messagesAdapter = object : RecyclerView.Adapter() { - /** - * Called when RecyclerView needs a new [ViewHolder] of the given type to represent - * an item. - * - * - * This new ViewHolder should be constructed with a new View that can represent the items - * of the given type. You can either create a new View manually or inflate it from an XML - * layout file. - * - * - * The new ViewHolder will be used to display items of the adapter using - * [.onBindViewHolder]. Since it will be re-used to display - * different items in the data set, it is a good idea to cache references to sub views of - * the View to avoid unnecessary [View.findViewById] calls. - * - * @param parent The ViewGroup into which the new View will be added after it is bound to - * an adapter position. - * @param viewType The view type of the new View. - * - * @return A new ViewHolder that holds a View of the given view type. - * @see .getItemViewType - * @see .onBindViewHolder - */ + private val dateTimeFormat: DateFormat = + DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT) + private val timeFormat: DateFormat = + DateFormat.getTimeInstance(DateFormat.SHORT) + + private fun getShortDateTime(time: Date): String { + // return time if within 24 hours, otherwise date/time + val oneDayMsec = 60 * 60 * 24 * 1000L + return if (System.currentTimeMillis() - time.time > oneDayMsec) { + dateTimeFormat.format(time) + } else timeFormat.format(time) + } + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val inflater = LayoutInflater.from(requireContext()) @@ -131,54 +110,14 @@ class MessagesFragment : Fragment(), Logging { var messages = arrayOf() var selectedList = ArrayList() - /** - * Returns the total number of items in the data set held by the adapter. - * - * @return The total number of items in this adapter. - */ override fun getItemCount(): Int = messages.size - /** - * Called by RecyclerView to display the data at the specified position. This method should - * update the contents of the [ViewHolder.itemView] to reflect the item at the given - * position. - * - * - * Note that unlike [android.widget.ListView], RecyclerView will not call this method - * again if the position of the item changes in the data set unless the item itself is - * invalidated or the new position cannot be determined. For this reason, you should only - * use the `position` parameter while acquiring the related data item inside - * this method and should not keep a copy of it. If you need the position of an item later - * on (e.g. in a click listener), use [ViewHolder.getAdapterPosition] which will - * have the updated adapter position. - * - * Override [.onBindViewHolder] instead if Adapter can - * handle efficient partial bind. - * - * @param holder The ViewHolder which should be updated to represent the contents of the - * item at the given position in the data set. - * @param position The position of the item within the adapter's data set. - */ override fun onBindViewHolder(holder: ViewHolder, position: Int) { val msg = messages[position] val nodes = model.nodeDB.nodes.value!! - val node = nodes.get(msg.from) + val node = nodes[msg.from] // Determine if this is my message (originated on this device) val isLocal = msg.from == DataPacket.ID_LOCAL - val isBroadcast = (msg.to == DataPacket.ID_BROADCAST - || msg.delayed == 1) // MeshProtos.MeshPacket.Delayed.DELAYED_BROADCAST_VALUE == 1 - - // Filter messages by contactId - if (contactId == DataPacket.ID_BROADCAST) { - if (isBroadcast) { - holder.card.visibility = View.VISIBLE - } else holder.card.visibility = View.GONE - } else { - if (msg.from == contactId && msg.to != DataPacket.ID_BROADCAST - || msg.from == DataPacket.ID_LOCAL && msg.to == contactId) { - holder.card.visibility = View.VISIBLE - } else holder.card.visibility = View.GONE - } // Set cardview offset and color. val marginParams = holder.card.layoutParams as ViewGroup.MarginLayoutParams @@ -237,98 +176,14 @@ class MessagesFragment : Fragment(), Logging { if (icon != null) { holder.messageStatusIcon.setImageResource(icon) holder.messageStatusIcon.visibility = View.VISIBLE - } else - holder.messageStatusIcon.visibility = View.INVISIBLE + holder.messageStatusIcon.visibility = View.GONE holder.itemView.setOnLongClickListener { + clickItem(holder) if (actionMode == null) { - actionMode = (activity as MainActivity).startActionMode(object : ActionMode.Callback { - override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { - mode.menuInflater.inflate(R.menu.menu_messages, menu) - mode.title = "1" - return true - } - - override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean { - clickItem(holder) - return true - } - - override fun onActionItemClicked( - mode: ActionMode, - item: MenuItem - ): Boolean { - when (item.itemId) { - R.id.deleteButton -> { - val deleteMessagesString = resources.getQuantityString( - R.plurals.delete_messages, - selectedList.size, - selectedList.size - ) - MaterialAlertDialogBuilder(requireContext()) - .setMessage(deleteMessagesString) - .setPositiveButton(getString(R.string.delete)) { _, _ -> - debug("User clicked deleteButton") - // all items selected --> deleteAllMessages() - if (selectedList.size == messages.size) { - model.messagesState.deleteAllMessages() - } else { - selectedList.forEach { - model.messagesState.deleteMessage(it) - } - } - mode.finish() - } - .setNeutralButton(R.string.cancel) { _, _ -> - } - .show() - } - R.id.selectAllButton -> { - // filter messages by ContactId - val messagesByContactId = messages.filter { - if (contactId == DataPacket.ID_BROADCAST) - it.to == DataPacket.ID_BROADCAST - else - it.from == contactId && it.to != DataPacket.ID_BROADCAST - || it.from == DataPacket.ID_LOCAL && it.to == contactId - } - // if all selected -> unselect all - if (selectedList.size == messagesByContactId.size) { - selectedList.clear() - mode.finish() - } else { - // else --> select all - selectedList.clear() - selectedList.addAll(messagesByContactId) - } - actionMode?.title = selectedList.size.toString() - notifyDataSetChanged() - } - R.id.resendButton -> { - debug("User clicked resendButton") - var resendText = "" - selectedList.forEach { - resendText = resendText + it.text + System.lineSeparator() - } - if (resendText!="") - resendText = resendText.substring(0, resendText.length - 1) - binding.messageInputText.setText(resendText) - mode.finish() - } - } - return true - } - - override fun onDestroyActionMode(mode: ActionMode) { - selectedList.clear() - notifyDataSetChanged() - actionMode = null - } - }) - } else { - // when action mode is enabled - clickItem(holder) + actionMode = + (activity as AppCompatActivity).startSupportActionMode(actionModeCallback) } true } @@ -370,7 +225,11 @@ class MessagesFragment : Fragment(), Logging { /// Called when our node DB changes fun onMessagesChanged(msgIn: Collection) { - messages = msgIn.toTypedArray() + messages = msgIn.filter { + if (contactId == DataPacket.ID_BROADCAST) + it.to == DataPacket.ID_BROADCAST || it.delayed == 1 // MeshPacket.Delayed.DELAYED_BROADCAST_VALUE == 1 + else it.from == contactId && it.to != DataPacket.ID_BROADCAST || it.from == DataPacket.ID_LOCAL && it.to == contactId + }.toTypedArray() notifyDataSetChanged() // FIXME, this is super expensive and redraws all messages // scroll to the last line @@ -445,4 +304,76 @@ class MessagesFragment : Fragment(), Logging { // && model.nodeDB.myId.value != null && model.radioConfig.value != null } } + + private inner class ActionModeCallback : ActionMode.Callback { + override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { + mode.menuInflater.inflate(R.menu.menu_messages, menu) + mode.title = "1" + return true + } + + override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean { + return false + } + + override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { + when (item.itemId) { + R.id.deleteButton -> { + val selectedList = messagesAdapter.selectedList + val deleteMessagesString = resources.getQuantityString( + R.plurals.delete_messages, + selectedList.size, + selectedList.size + ) + MaterialAlertDialogBuilder(requireContext()) + .setMessage(deleteMessagesString) + .setPositiveButton(getString(R.string.delete)) { _, _ -> + debug("User clicked deleteButton") + // all items selected --> deleteAllMessages() + val messagesTotal = model.messagesState.messages.value + if (messagesTotal != null && selectedList.size == messagesTotal.size) { + model.messagesState.deleteAllMessages() + } else { + model.messagesState.deleteMessages(selectedList) + } + mode.finish() + } + .setNeutralButton(R.string.cancel) { _, _ -> + } + .show() + } + R.id.selectAllButton -> { + // if all selected -> unselect all + if (messagesAdapter.selectedList.size == messagesAdapter.messages.size) { + messagesAdapter.selectedList.clear() + mode.finish() + } else { + // else --> select all + messagesAdapter.selectedList.clear() + messagesAdapter.selectedList.addAll(messagesAdapter.messages) + } + actionMode?.title = messagesAdapter.selectedList.size.toString() + messagesAdapter.notifyDataSetChanged() + } + R.id.resendButton -> { + debug("User clicked resendButton") + var resendText = "" + selectedList.forEach { + resendText = resendText + it.text + System.lineSeparator() + } + if (resendText!="") + resendText = resendText.substring(0, resendText.length - 1) + binding.messageInputText.setText(resendText) + mode.finish() + } + } + return true + } + + override fun onDestroyActionMode(mode: ActionMode) { + messagesAdapter.selectedList.clear() + messagesAdapter.notifyDataSetChanged() + actionMode = null + } + } } From def08cde7513e09604841bcd5e140e2fd57562bf Mon Sep 17 00:00:00 2001 From: andrekir Date: Tue, 19 Apr 2022 16:15:47 -0300 Subject: [PATCH 2/2] include resendButton in changes --- app/src/main/java/com/geeksville/mesh/ui/ContactsFragment.kt | 1 + app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt | 1 + 2 files changed, 2 insertions(+) diff --git a/app/src/main/java/com/geeksville/mesh/ui/ContactsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/ContactsFragment.kt index 320a3076e..d6f732b4f 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/ContactsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/ContactsFragment.kt @@ -213,6 +213,7 @@ class ContactsFragment : ScreenFragment("Messages"), Logging { private inner class ActionModeCallback : ActionMode.Callback { override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { mode.menuInflater.inflate(R.menu.menu_messages, menu) + menu.findItem(R.id.resendButton).isVisible = false mode.title = "1" return true } diff --git a/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt index 5e490c6af..4847f0691 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt @@ -357,6 +357,7 @@ class MessagesFragment : Fragment(), Logging { } R.id.resendButton -> { debug("User clicked resendButton") + val selectedList = messagesAdapter.selectedList var resendText = "" selectedList.forEach { resendText = resendText + it.text + System.lineSeparator()