Merge branch 'master' into feature/offline-maps

This commit is contained in:
Jackson Rosenthal 2022-03-02 18:01:09 -05:00
commit a1a9bb1740
13 changed files with 214 additions and 78 deletions

View file

@ -95,12 +95,12 @@ class MessagesState(private val ui: UIViewModel) : Logging {
addMessage(p)
}
fun deleteMessage(packet: DataPacket, position: Int) {
fun deleteMessage(packet: DataPacket) {
val service = ui.meshService
if (service != null) {
try {
service.delete(position)
service.deleteMessage(packet.id)
} catch (ex: RemoteException) {
packet.errorMessage = "Error: ${ex.message}"
}
@ -116,7 +116,7 @@ class MessagesState(private val ui: UIViewModel) : Logging {
try {
service.deleteAllMessages()
} catch (ex: RemoteException) {
errormsg("Error: ${ex.message}")
}
removeAllMessages()
}

View file

@ -1753,10 +1753,12 @@ class MeshService : Service(), Logging {
this@MeshService.setOwner(myId, longName, shortName)
}
override fun delete(position: Int) {
if (position >= 0) {
recentDataPackets.removeAt(position)
}
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 deleteAllMessages() {

View file

@ -1,12 +1,10 @@
package com.geeksville.mesh.ui
import android.app.AlertDialog
import android.graphics.Color
import android.graphics.drawable.GradientDrawable
import android.os.Bundle
import android.text.InputType
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.*
import android.view.inputmethod.EditorInfo
import android.widget.EditText
import android.widget.ImageView
@ -14,11 +12,11 @@ import android.widget.TextView
import androidx.cardview.widget.CardView
import androidx.core.content.ContextCompat
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer
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
@ -26,6 +24,7 @@ import com.geeksville.mesh.databinding.MessagesFragmentBinding
import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.service.MeshService
import com.google.android.material.chip.Chip
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import java.text.DateFormat
import java.util.*
@ -37,7 +36,6 @@ fun EditText.on(actionId: Int, func: () -> Unit) {
if (actionId == receivedActionId) {
func()
}
true
}
}
@ -45,6 +43,7 @@ fun EditText.on(actionId: Int, func: () -> Unit) {
@AndroidEntryPoint
class MessagesFragment : ScreenFragment("Messages"), Logging {
private var actionMode: ActionMode? = null
private var _binding: MessagesFragmentBinding? = null
// This property is only valid between onCreateView and onDestroyView.
@ -53,7 +52,7 @@ class MessagesFragment : ScreenFragment("Messages"), Logging {
private val model: UIViewModel by activityViewModels()
// Allows textMultiline with IME_ACTION_SEND
fun EditText.onActionSend(func: () -> Unit) {
private fun EditText.onActionSend(func: () -> Unit) {
setImeOptions(EditorInfo.IME_ACTION_SEND)
setRawInputType(InputType.TYPE_CLASS_TEXT)
setOnEditorActionListener { _, actionId, _ ->
@ -61,7 +60,6 @@ class MessagesFragment : ScreenFragment("Messages"), Logging {
if (actionId == EditorInfo.IME_ACTION_SEND) {
func()
}
true
}
}
@ -73,13 +71,12 @@ class MessagesFragment : ScreenFragment("Messages"), Logging {
private fun getShortDateTime(time: Date): String {
// return time if within 24 hours, otherwise date/time
val one_day = 60 * 60 * 24 * 1000
if (System.currentTimeMillis() - time.time > one_day) {
return dateTimeFormat.format(time)
} else return timeFormat.format(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) :
@ -119,8 +116,6 @@ class MessagesFragment : ScreenFragment("Messages"), Logging {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(requireContext())
// Inflate the custom layout
// Inflate the custom layout
val contactViewBinding = AdapterMessageLayoutBinding.inflate(inflater, parent, false)
@ -128,6 +123,9 @@ class MessagesFragment : ScreenFragment("Messages"), Logging {
return ViewHolder(contactViewBinding)
}
var messages = arrayOf<DataPacket>()
var selectedList = ArrayList<DataPacket>()
/**
* Returns the total number of items in the data set held by the adapter.
*
@ -167,30 +165,10 @@ class MessagesFragment : ScreenFragment("Messages"), Logging {
// Set cardview offset and color.
val marginParams = holder.card.layoutParams as ViewGroup.MarginLayoutParams
val messageOffset = resources.getDimensionPixelOffset(R.dimen.message_offset)
holder.card.setOnLongClickListener {
val deleteMessageDialog = AlertDialog.Builder(context)
deleteMessageDialog.setMessage(R.string.delete_selected_message)
deleteMessageDialog.setPositiveButton(
R.string.delete
) { _, _ ->
model.messagesState.deleteMessage((messages[position]), position)
}
deleteMessageDialog.setNeutralButton(
R.string.cancel
) { _, _ ->
}
deleteMessageDialog.setNegativeButton(
R.string.delete_all_messages
) { _, _ ->
model.messagesState.deleteAllMessages()
}
deleteMessageDialog.create()
deleteMessageDialog.show()
true
}
if (isMe) {
marginParams.leftMargin = messageOffset
marginParams.rightMargin = 0
holder.messageText.textAlignment = View.TEXT_ALIGNMENT_TEXT_END
context?.let {
holder.card.setCardBackgroundColor(
ContextCompat.getColor(
@ -202,6 +180,7 @@ class MessagesFragment : ScreenFragment("Messages"), Logging {
} else {
marginParams.rightMargin = messageOffset
marginParams.leftMargin = 0
holder.messageText.textAlignment = View.TEXT_ALIGNMENT_TEXT_START
context?.let {
holder.card.setCardBackgroundColor(
ContextCompat.getColor(
@ -243,9 +222,114 @@ class MessagesFragment : ScreenFragment("Messages"), Logging {
} else
holder.messageStatusIcon.visibility = View.INVISIBLE
holder.itemView.setOnLongClickListener {
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 -> {
// if all selected -> unselect all
if (selectedList.size == messages.size) {
selectedList.clear()
mode.finish()
} else {
// else --> select all
selectedList.clear()
selectedList.addAll(messages)
}
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)
}
true
}
holder.itemView.setOnClickListener {
if (actionMode != null) clickItem(holder)
}
if (selectedList.contains(msg)) {
holder.itemView.background = GradientDrawable().apply {
shape = GradientDrawable.RECTANGLE
cornerRadius = 32f
setColor(Color.rgb(127, 127, 127))
}
} else {
holder.itemView.background = GradientDrawable().apply {
shape = GradientDrawable.RECTANGLE
cornerRadius = 32f
setColor(ContextCompat.getColor(holder.itemView.context, R.color.colorAdvancedBackground))
}
}
}
private var messages = arrayOf<DataPacket>()
private fun clickItem(holder: ViewHolder) {
val position = holder.bindingAdapterPosition
if (!selectedList.contains(messages[position])) {
selectedList.add(messages[position])
} else {
selectedList.remove(messages[position])
}
if (selectedList.isEmpty()) {
// finish action mode when no items selected
actionMode?.finish()
} else {
// show total items selected on action mode title
actionMode?.title = "${selectedList.size}"
}
notifyItemChanged(position)
}
/// Called when our node DB changes
fun onMessagesChanged(msgIn: Collection<DataPacket>) {
@ -258,10 +342,15 @@ class MessagesFragment : ScreenFragment("Messages"), Logging {
}
}
override fun onPause() {
actionMode?.finish()
super.onPause()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
): View {
_binding = MessagesFragmentBinding.inflate(inflater, container, false)
return binding.root
}
@ -269,7 +358,7 @@ class MessagesFragment : ScreenFragment("Messages"), Logging {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.sendButton.setOnClickListener {
debug("sendButton click")
debug("User clicked sendButton")
val str = binding.messageInputText.text.toString().trim()
if (str.isNotEmpty())
@ -295,34 +384,20 @@ class MessagesFragment : ScreenFragment("Messages"), Logging {
layoutManager.stackFromEnd = true // We want the last rows to always be shown
binding.messageListView.layoutManager = layoutManager
model.messagesState.messages.observe(viewLifecycleOwner, Observer {
model.messagesState.messages.observe(viewLifecycleOwner) {
debug("New messages received: ${it.size}")
messagesAdapter.onMessagesChanged(it)
})
}
// If connection state _OR_ myID changes we have to fix our ability to edit outgoing messages
fun updateTextEnabled() {
binding.textInputLayout.isEnabled =
model.isConnected.value != MeshService.ConnectionState.DISCONNECTED
model.isConnected.observe(viewLifecycleOwner) { connectionState ->
// If we don't know our node ID and we are offline don't let user try to send
val connected = connectionState == MeshService.ConnectionState.CONNECTED
binding.textInputLayout.isEnabled = connected
binding.sendButton.isEnabled = connected
// Just being connected is enough to allow sending texts I think
// && model.nodeDB.myId.value != null && model.radioConfig.value != null
}
model.isConnected.observe(viewLifecycleOwner, Observer { _ ->
// If we don't know our node ID and we are offline don't let user try to send
updateTextEnabled()
})
/* model.nodeDB.myId.observe(viewLifecycleOwner, Observer { _ ->
// If we don't know our node ID and we are offline don't let user try to send
updateTextEnabled()
})
model.radioConfig.observe(viewLifecycleOwner, Observer { _ ->
// If we don't know our node ID and we are offline don't let user try to send
updateTextEnabled()
}) */
}
}