refactor messages to Room database

This commit is contained in:
andrekir 2022-09-15 22:24:04 -03:00
parent ab7bf4922b
commit 65e982ddd5
12 changed files with 191 additions and 279 deletions

View file

@ -16,8 +16,10 @@ import androidx.recyclerview.widget.RecyclerView
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.DataPacket
import com.geeksville.mesh.R
import com.geeksville.mesh.database.entity.Packet
import com.geeksville.mesh.databinding.AdapterContactLayoutBinding
import com.geeksville.mesh.databinding.FragmentContactsBinding
import com.geeksville.mesh.model.Channel
import com.geeksville.mesh.model.UIViewModel
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
@ -71,36 +73,36 @@ class ContactsFragment : ScreenFragment("Messages"), Logging {
return ViewHolder(contactsView)
}
var contacts = arrayOf<DataPacket>()
var contacts = arrayOf<Packet>()
var selectedList = ArrayList<String>()
override fun getItemCount(): Int = contacts.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val contact = contacts[position]
val packet = contacts[position]
val contact = packet.data
// Determine if this is my message (originated on this device)
val isLocal = contact.from == DataPacket.ID_LOCAL
val isBroadcast = contact.to == DataPacket.ID_BROADCAST
val contactId = if (isLocal || isBroadcast) contact.to else contact.from
val fromLocal = contact.from == DataPacket.ID_LOCAL
val toBroadcast = contact.to == DataPacket.ID_BROADCAST
// grab usernames from NodeInfo
val nodes = model.nodeDB.nodes.value!!
val node = nodes[if (isLocal) contact.to else contact.from]
val node = nodes[if (fromLocal) contact.to else contact.from]
//grab channel names from DeviceConfig
val channels = model.channels.value
val primaryChannel = channels.primaryChannel
val channels = model.channels.value.protobuf
val channelName = if (channels.settingsCount > contact.channel)
Channel(channels.settingsList[contact.channel], channels.loraConfig).name else null
val shortName = node?.user?.shortName ?: "???"
val longName =
if (isBroadcast) primaryChannel?.name ?: getString(R.string.channel_name)
else node?.user?.longName ?: getString(R.string.unknown_username)
val longName = if (toBroadcast) channelName ?: getString(R.string.channel_name)
else node?.user?.longName ?: getString(R.string.unknown_username)
holder.shortName.text = if (isBroadcast) "All" else shortName
holder.shortName.text = if (toBroadcast) "${contact.channel}" else shortName
holder.longName.text = longName
val text = if (isLocal) contact.text else "$shortName: ${contact.text}"
val text = if (fromLocal) contact.text else "$shortName: ${contact.text}"
holder.lastMessageText.text = text
if (contact.time != 0L) {
@ -109,7 +111,7 @@ class ContactsFragment : ScreenFragment("Messages"), Logging {
} else holder.lastMessageTime.visibility = View.INVISIBLE
holder.itemView.setOnLongClickListener {
clickItem(holder, contactId)
clickItem(holder, packet.contact_key)
if (actionMode == null) {
actionMode =
(activity as AppCompatActivity).startSupportActionMode(actionModeCallback)
@ -117,12 +119,12 @@ class ContactsFragment : ScreenFragment("Messages"), Logging {
true
}
holder.itemView.setOnClickListener {
if (actionMode != null) clickItem(holder, contactId)
if (actionMode != null) clickItem(holder, packet.contact_key)
else {
debug("calling MessagesFragment filter:$contactId")
debug("calling MessagesFragment filter:${packet.contact_key}")
setFragmentResult(
"requestKey",
bundleOf("contactId" to contactId, "contactName" to longName)
bundleOf("contactKey" to packet.contact_key, "contactName" to longName)
)
parentFragmentManager.beginTransaction()
.replace(R.id.mainActivityLayout, MessagesFragment())
@ -131,7 +133,7 @@ class ContactsFragment : ScreenFragment("Messages"), Logging {
}
}
if (selectedList.contains(contactId)) {
if (selectedList.contains(packet.contact_key)) {
holder.itemView.background = GradientDrawable().apply {
shape = GradientDrawable.RECTANGLE
cornerRadius = 32f
@ -151,12 +153,12 @@ class ContactsFragment : ScreenFragment("Messages"), Logging {
}
}
private fun clickItem(holder: ViewHolder, contactId: String? = DataPacket.ID_BROADCAST) {
private fun clickItem(holder: ViewHolder, contactKey: String) {
val position = holder.bindingAdapterPosition
if (contactId != null && !selectedList.contains(contactId)) {
selectedList.add(contactId)
if (!selectedList.contains(contactKey)) {
selectedList.add(contactKey)
} else {
selectedList.remove(contactId)
selectedList.remove(contactKey)
}
if (selectedList.isEmpty()) {
// finish action mode when no items selected
@ -169,13 +171,13 @@ class ContactsFragment : ScreenFragment("Messages"), Logging {
}
/// Called when our contacts DB changes
fun onContactsChanged(contactsIn: Collection<DataPacket>) {
contacts = contactsIn.sortedByDescending { it.time }.toTypedArray()
fun onContactsChanged(contacts: Collection<Packet>) {
this.contacts = contacts.sortedByDescending { it.received_time }.toTypedArray()
notifyDataSetChanged() // FIXME, this is super expensive and redraws all nodes
}
fun onChannelsChanged() {
val oldBroadcast = contacts.find { it.to == DataPacket.ID_BROADCAST }
val oldBroadcast = contacts.find { it.contact_key == DataPacket.ID_BROADCAST }
if (oldBroadcast != null) {
notifyItemChanged(contacts.indexOf(oldBroadcast))
}
@ -209,9 +211,23 @@ class ContactsFragment : ScreenFragment("Messages"), Logging {
contactsAdapter.notifyDataSetChanged()
}
model.messagesState.contacts.observe(viewLifecycleOwner) {
model.contacts.observe(viewLifecycleOwner) {
debug("New contacts received: ${it.size}")
contactsAdapter.onContactsChanged(it.values)
fun emptyDataPacket(channel: Int = 0): DataPacket {
return DataPacket(bytes = null, dataType = 1, time = 0L, channel = channel)
}
fun emptyPacket(contactKey: String, channel: Int = 0): Packet {
return Packet(0L, 1, contactKey, 0L, emptyDataPacket(channel))
}
// Add empty channel placeholders
val mutableContacts = it.toMutableMap()
val all = DataPacket.ID_BROADCAST // always show Broadcast contacts, even when empty
for (ch in 0 until model.channels.value.protobuf.settingsCount)
if (it["$ch$all"] == null) mutableContacts["$ch$all"] = emptyPacket("$ch$all", ch)
contactsAdapter.onContactsChanged(mutableContacts.values)
}
}
@ -230,15 +246,12 @@ class ContactsFragment : ScreenFragment("Messages"), Logging {
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
when (item.itemId) {
R.id.deleteButton -> {
val messagesTotal = model.messagesState.messages.value!!
val messagesTotal = model.packets.value.filter { it.port_num == 1 }
val selectedList = contactsAdapter.selectedList
val deleteList = ArrayList<DataPacket>()
val deleteList = ArrayList<Packet>()
// find messages for each contactId
selectedList.forEach { contactId ->
deleteList += messagesTotal.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
}
selectedList.forEach { contact ->
deleteList += messagesTotal.filter { it.contact_key == contact }
}
val deleteMessagesString = resources.getQuantityString(
R.plurals.delete_messages,
@ -251,9 +264,9 @@ class ContactsFragment : ScreenFragment("Messages"), Logging {
debug("User clicked deleteButton")
// all items selected --> deleteAllMessages()
if (deleteList.size == messagesTotal.size) {
model.messagesState.deleteAllMessages()
model.deleteAllMessages()
} else {
model.messagesState.deleteMessages(deleteList)
model.deleteMessages(deleteList.map { it.uuid })
}
mode.finish()
}
@ -270,9 +283,7 @@ class ContactsFragment : ScreenFragment("Messages"), Logging {
// 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!!)
contactsAdapter.selectedList.add(it.contact_key)
}
}
actionMode?.title = contactsAdapter.selectedList.size.toString()

View file

@ -21,6 +21,7 @@ import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.DataPacket
import com.geeksville.mesh.MessageStatus
import com.geeksville.mesh.R
import com.geeksville.mesh.database.entity.Packet
import com.geeksville.mesh.database.entity.QuickChatAction
import com.geeksville.mesh.databinding.AdapterMessageLayoutBinding
import com.geeksville.mesh.databinding.MessagesFragmentBinding
@ -52,7 +53,7 @@ class MessagesFragment : Fragment(), Logging {
// This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!!
private var contactId: String = DataPacket.ID_BROADCAST
private var contactKey: String = DataPacket.ID_BROADCAST
private var contactName: String = DataPacket.ID_BROADCAST
private val model: UIViewModel by activityViewModels()
@ -106,13 +107,14 @@ class MessagesFragment : Fragment(), Logging {
return ViewHolder(contactViewBinding)
}
var messages = arrayOf<DataPacket>()
var selectedList = ArrayList<DataPacket>()
var messages = listOf<Packet>()
var selectedList = ArrayList<Packet>()
override fun getItemCount(): Int = messages.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val msg = messages[position]
val packet = messages[position]
val msg = packet.data
val nodes = model.nodeDB.nodes.value!!
val node = nodes[msg.from]
// Determine if this is my message (originated on this device)
@ -190,7 +192,7 @@ class MessagesFragment : Fragment(), Logging {
if (actionMode != null) clickItem(holder)
}
if (selectedList.contains(msg)) {
if (selectedList.contains(packet)) {
holder.itemView.background = GradientDrawable().apply {
shape = GradientDrawable.RECTANGLE
cornerRadius = 32f
@ -223,11 +225,8 @@ class MessagesFragment : Fragment(), Logging {
}
/// Called when our node DB changes
fun onMessagesChanged(msgIn: Collection<DataPacket>) {
messages = msgIn.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
}.toTypedArray()
fun onMessagesChanged(messages: List<Packet>) {
this.messages = messages
notifyDataSetChanged() // FIXME, this is super expensive and redraws all messages
// scroll to the last line
@ -254,17 +253,27 @@ class MessagesFragment : Fragment(), Logging {
setFragmentResultListener("requestKey") { _, bundle->
// get the result from bundle
contactId = bundle.getString("contactId").toString()
contactKey = bundle.getString("contactKey").toString()
contactName = bundle.getString("contactName").toString()
model.setContactKey(contactKey)
binding.messageTitle.text = contactName
}
// contactKey: unique contact key filter (channel)+(nodeId)
fun sendMessage(str: String, contactKey: String) {
model.sendMessage(
str,
contactKey[0].digitToInt(), // Channel
contactKey.substring(1) // NodeID
)
}
binding.sendButton.setOnClickListener {
debug("User clicked sendButton")
val str = binding.messageInputText.text.toString().trim()
if (str.isNotEmpty())
model.messagesState.sendMessage(str, contactId)
sendMessage(str, contactKey)
binding.messageInputText.setText("") // blow away the string the user just entered
// requireActivity().hideKeyboard()
@ -274,8 +283,7 @@ class MessagesFragment : Fragment(), Logging {
debug("did IME action")
val str = binding.messageInputText.text.toString().trim()
if (str.isNotEmpty())
model.messagesState.sendMessage(str)
if (str.isNotEmpty()) sendMessage(str, contactKey)
binding.messageInputText.setText("") // blow away the string the user just entered
// requireActivity().hideKeyboard()
@ -286,7 +294,7 @@ class MessagesFragment : Fragment(), Logging {
layoutManager.stackFromEnd = true // We want the last rows to always be shown
binding.messageListView.layoutManager = layoutManager
model.messagesState.messages.observe(viewLifecycleOwner) {
model.messages.observe(viewLifecycleOwner) {
debug("New messages received: ${it.size}")
messagesAdapter.onMessagesChanged(it)
}
@ -310,7 +318,7 @@ class MessagesFragment : Fragment(), Logging {
binding.quickChatLayout.removeAllViews()
for (action in actions) {
val button = Button(context)
button.setText(action.name)
button.text = action.name
button.isEnabled = isConnected
if (action.mode == QuickChatAction.Mode.Instant) {
button.backgroundTintList = ContextCompat.getColorStateList(requireActivity(), R.color.colorMyMsg)
@ -327,7 +335,7 @@ class MessagesFragment : Fragment(), Logging {
binding.messageInputText.setText(newText)
binding.messageInputText.setSelection(newText.length)
} else {
model.messagesState.sendMessage(action.message, contactId)
sendMessage(action.message, contactKey)
}
}
binding.quickChatLayout.addView(button)
@ -361,11 +369,11 @@ class MessagesFragment : Fragment(), Logging {
.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()
val messagesTotal = model.packets.value.filter { it.port_num == 1 }
if (selectedList.size == messagesTotal.size) {
model.deleteAllMessages()
} else {
model.messagesState.deleteMessages(selectedList)
model.deleteMessages(selectedList.map { it.uuid })
}
mode.finish()
}
@ -391,7 +399,7 @@ class MessagesFragment : Fragment(), Logging {
val selectedList = messagesAdapter.selectedList
var resendText = ""
selectedList.forEach {
resendText = resendText + it.text + System.lineSeparator()
resendText = resendText + it.data.text + System.lineSeparator()
}
if (resendText!="")
resendText = resendText.substring(0, resendText.length - 1)

View file

@ -166,11 +166,12 @@ class UsersFragment : ScreenFragment("Users"), Logging {
}
}
holder.itemView.setOnLongClickListener {
if (position > 0) {
debug("calling MessagesFragment filter:${n.user?.id}")
val node = n.user
if (position > 0 && node != null) {
debug("calling MessagesFragment filter:${node.id}")
setFragmentResult(
"requestKey",
bundleOf("contactId" to n.user?.id, "contactName" to name)
bundleOf("contactKey" to "0${node.id}", "contactName" to name)
)
parentFragmentManager.beginTransaction()
.replace(R.id.mainActivityLayout, MessagesFragment())