Merge pull request #1443 from Andries-Smit/feat/automation-option-menu
Feat: automation move delete and sort to option menu
This commit is contained in:
commit
32060a82c4
12 changed files with 245 additions and 194 deletions
|
@ -20,6 +20,7 @@ class AutomationEvent(private val injector: HasAndroidInjector) {
|
|||
|
||||
var title: String = ""
|
||||
var isEnabled = true
|
||||
var position = -1
|
||||
var systemAction: Boolean = false // true = generated by AAPS, false = entered by user
|
||||
var readOnly: Boolean = false // removing, editing disabled
|
||||
var autoRemove: Boolean = false // auto-remove once used
|
||||
|
@ -66,11 +67,12 @@ class AutomationEvent(private val injector: HasAndroidInjector) {
|
|||
.toString()
|
||||
}
|
||||
|
||||
fun fromJSON(data: String): AutomationEvent {
|
||||
fun fromJSON(data: String, position: Int): AutomationEvent {
|
||||
val d = JSONObject(data)
|
||||
title = d.optString("title", "")
|
||||
isEnabled = d.optBoolean("enabled", true)
|
||||
systemAction = d.optBoolean("systemAction", false)
|
||||
this.position = position
|
||||
readOnly = d.optBoolean("readOnly", false)
|
||||
autoRemove = d.optBoolean("autoRemove", false)
|
||||
userAction = d.optBoolean("userAction", false)
|
||||
|
|
|
@ -2,13 +2,11 @@ package info.nightscout.androidaps.plugins.general.automation
|
|||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.text.method.ScrollingMovementMethod
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.util.SparseArray
|
||||
import android.view.*
|
||||
import androidx.core.util.forEach
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import androidx.annotation.DrawableRes
|
||||
|
@ -25,10 +23,6 @@ import info.nightscout.androidaps.database.entities.UserEntry.Sources
|
|||
import info.nightscout.androidaps.logging.UserEntryLogger
|
||||
import info.nightscout.androidaps.plugins.bus.RxBus
|
||||
import info.nightscout.androidaps.plugins.general.automation.dialogs.EditEventDialog
|
||||
import info.nightscout.androidaps.plugins.general.automation.dragHelpers.ItemTouchHelperAdapter
|
||||
import info.nightscout.androidaps.plugins.general.automation.dragHelpers.ItemTouchHelperViewHolder
|
||||
import info.nightscout.androidaps.plugins.general.automation.dragHelpers.OnStartDragListener
|
||||
import info.nightscout.androidaps.plugins.general.automation.dragHelpers.SimpleItemTouchHelperCallback
|
||||
import info.nightscout.androidaps.plugins.general.automation.events.EventAutomationDataChanged
|
||||
import info.nightscout.androidaps.plugins.general.automation.events.EventAutomationUpdateGui
|
||||
import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnector
|
||||
|
@ -37,6 +31,9 @@ import info.nightscout.androidaps.utils.HtmlHelper
|
|||
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
|
||||
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||
import info.nightscout.androidaps.extensions.toVisibility
|
||||
import info.nightscout.androidaps.plugins.general.automation.dragHelpers.ItemTouchHelperAdapter
|
||||
import info.nightscout.androidaps.plugins.general.automation.dragHelpers.OnStartDragListener
|
||||
import info.nightscout.androidaps.plugins.general.automation.dragHelpers.SimpleItemTouchHelperCallback
|
||||
import info.nightscout.androidaps.utils.resources.ResourceHelper
|
||||
import info.nightscout.androidaps.utils.rx.AapsSchedulers
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
|
@ -55,13 +52,14 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
|
|||
|
||||
private var disposable: CompositeDisposable = CompositeDisposable()
|
||||
private lateinit var eventListAdapter: EventListAdapter
|
||||
|
||||
private var itemTouchHelper: ItemTouchHelper? = null
|
||||
private var selectedItems: SparseArray<AutomationEvent> = SparseArray()
|
||||
private var removeActionMode: ActionMode? = null
|
||||
private var sortActionMode: ActionMode? = null
|
||||
private val itemTouchHelper = ItemTouchHelper(SimpleItemTouchHelperCallback())
|
||||
|
||||
private var _binding: AutomationFragmentBinding? = null
|
||||
|
||||
// This property is only valid between onCreateView and
|
||||
// onDestroyView.
|
||||
// This property is only valid between onCreateView and onDestroyView.
|
||||
private val binding get() = _binding!!
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
|
@ -71,14 +69,15 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
|
|||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
|
||||
eventListAdapter = EventListAdapter()
|
||||
binding.eventListView.layoutManager = LinearLayoutManager(context)
|
||||
binding.eventListView.adapter = eventListAdapter
|
||||
|
||||
binding.logView.movementMethod = ScrollingMovementMethod()
|
||||
|
||||
binding.fabAddEvent.setOnClickListener {
|
||||
removeActionMode?.finish()
|
||||
sortActionMode?.finish()
|
||||
val dialog = EditEventDialog()
|
||||
val args = Bundle()
|
||||
args.putString("event", AutomationEvent(injector).toJSON())
|
||||
|
@ -87,12 +86,10 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
|
|||
dialog.show(childFragmentManager, "EditEventDialog")
|
||||
}
|
||||
|
||||
val callback: ItemTouchHelper.Callback = SimpleItemTouchHelperCallback(eventListAdapter)
|
||||
itemTouchHelper = ItemTouchHelper(callback)
|
||||
itemTouchHelper?.attachToRecyclerView(binding.eventListView)
|
||||
|
||||
itemTouchHelper.attachToRecyclerView(binding.eventListView)
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
@Synchronized
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
@ -113,6 +110,8 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
|
|||
|
||||
@Synchronized
|
||||
override fun onPause() {
|
||||
removeActionMode?.finish()
|
||||
sortActionMode?.finish()
|
||||
super.onPause()
|
||||
disposable.clear()
|
||||
}
|
||||
|
@ -120,9 +119,39 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
|
|||
@Synchronized
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
removeActionMode?.finish()
|
||||
sortActionMode?.finish()
|
||||
_binding = null
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater)
|
||||
inflater.inflate(R.menu.menu_automation, menu)
|
||||
}
|
||||
|
||||
override fun onPrepareOptionsMenu(menu: Menu) {
|
||||
// Only show when tab automation is shown
|
||||
menu.findItem(R.id.nav_remove_automation_items)?.isVisible = isResumed
|
||||
menu.findItem(R.id.nav_sort_automation_items)?.isVisible = isResumed
|
||||
super.onPrepareOptionsMenu(menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.nav_remove_automation_items -> {
|
||||
removeActionMode = activity?.startActionMode(RemoveActionModeCallback())
|
||||
true
|
||||
}
|
||||
|
||||
R.id.nav_sort_automation_items -> {
|
||||
sortActionMode = activity?.startActionMode(SortActionModeCallback())
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun updateGui() {
|
||||
if (_binding == null) return
|
||||
|
@ -134,7 +163,7 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
|
|||
}
|
||||
|
||||
override fun onStartDrag(viewHolder: RecyclerView.ViewHolder) {
|
||||
itemTouchHelper?.startDrag(viewHolder)
|
||||
itemTouchHelper.startDrag(viewHolder)
|
||||
}
|
||||
|
||||
fun fillIconSet(connector: TriggerConnector, set: HashSet<Int>) {
|
||||
|
@ -166,20 +195,22 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
|
|||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val event = automationPlugin.at(position)
|
||||
holder.binding.rootLayout.setBackgroundColor(rh.gc(
|
||||
if (event.userAction) R.color.mdtp_line_dark
|
||||
else if (event.areActionsValid()) R.color.ribbonDefault
|
||||
else R.color.errorAlertBackground)
|
||||
val automation = automationPlugin.at(position)
|
||||
holder.binding.rootLayout.setBackgroundColor(
|
||||
rh.gc(
|
||||
if (automation.userAction) R.color.mdtp_line_dark
|
||||
else if (automation.areActionsValid()) R.color.ribbonDefault
|
||||
else R.color.errorAlertBackground
|
||||
)
|
||||
holder.binding.eventTitle.text = event.title
|
||||
holder.binding.enabled.isChecked = event.isEnabled
|
||||
holder.binding.enabled.isEnabled = !event.readOnly
|
||||
)
|
||||
holder.binding.eventTitle.text = automation.title
|
||||
holder.binding.enabled.isChecked = automation.isEnabled
|
||||
holder.binding.enabled.isEnabled = !automation.readOnly
|
||||
holder.binding.iconLayout.removeAllViews()
|
||||
// trigger icons
|
||||
val triggerIcons = HashSet<Int>()
|
||||
if (event.userAction) triggerIcons.add(R.drawable.ic_danar_useropt)
|
||||
fillIconSet(event.trigger, triggerIcons)
|
||||
if (automation.userAction) triggerIcons.add(R.drawable.ic_danar_useropt)
|
||||
fillIconSet(automation.trigger, triggerIcons)
|
||||
for (res in triggerIcons) {
|
||||
addImage(res, holder.context, holder.binding.iconLayout)
|
||||
}
|
||||
|
@ -191,7 +222,7 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
|
|||
holder.binding.iconLayout.addView(iv)
|
||||
// action icons
|
||||
val actionIcons = HashSet<Int>()
|
||||
for (action in event.actions) {
|
||||
for (action in automation.actions) {
|
||||
actionIcons.add(action.icon())
|
||||
}
|
||||
for (res in actionIcons) {
|
||||
|
@ -199,70 +230,133 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
|
|||
}
|
||||
// enabled event
|
||||
holder.binding.enabled.setOnClickListener {
|
||||
event.isEnabled = holder.binding.enabled.isChecked
|
||||
automation.isEnabled = holder.binding.enabled.isChecked
|
||||
rxBus.send(EventAutomationDataChanged())
|
||||
}
|
||||
// edit event
|
||||
holder.binding.rootLayout.setOnClickListener {
|
||||
holder.binding.aapsLogo.visibility = (automation.systemAction).toVisibility()
|
||||
|
||||
fun updateSelection(selected: Boolean) {
|
||||
if (selected) {
|
||||
selectedItems.put(position, automation)
|
||||
} else {
|
||||
selectedItems.remove(position)
|
||||
}
|
||||
removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size())
|
||||
}
|
||||
|
||||
holder.binding.rootLayout.setOnTouchListener { _, touchEvent ->
|
||||
if (touchEvent.actionMasked == MotionEvent.ACTION_UP && sortActionMode == null && removeActionMode == null) {
|
||||
val dialog = EditEventDialog()
|
||||
val args = Bundle()
|
||||
args.putString("event", event.toJSON())
|
||||
args.putString("event", automation.toJSON())
|
||||
args.putInt("position", position)
|
||||
dialog.arguments = args
|
||||
dialog.show(childFragmentManager, "EditEventDialog")
|
||||
}
|
||||
// Start a drag whenever the handle view it touched
|
||||
holder.binding.iconSort.setOnTouchListener { v: View, motionEvent: MotionEvent ->
|
||||
if (motionEvent.action == MotionEvent.ACTION_DOWN) {
|
||||
this@AutomationFragment.onStartDrag(holder)
|
||||
if (touchEvent.actionMasked == MotionEvent.ACTION_DOWN && sortActionMode != null) {
|
||||
onStartDrag(holder)
|
||||
}
|
||||
if (touchEvent.actionMasked == MotionEvent.ACTION_UP && removeActionMode != null) {
|
||||
holder.binding.cbRemove.toggle()
|
||||
updateSelection(holder.binding.cbRemove.isChecked)
|
||||
removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size())
|
||||
}
|
||||
return@setOnTouchListener true
|
||||
}
|
||||
v.onTouchEvent(motionEvent)
|
||||
}
|
||||
// remove event
|
||||
holder.binding.iconTrash.setOnClickListener {
|
||||
OKDialog.showConfirmation(requireContext(), rh.gs(R.string.removerecord) + " " + automationPlugin.at(position).title,
|
||||
{
|
||||
uel.log(Action.AUTOMATION_REMOVED, Sources.Automation, automationPlugin.at(position).title)
|
||||
automationPlugin.removeAt(position)
|
||||
notifyItemRemoved(position)
|
||||
}, {
|
||||
rxBus.send(EventAutomationUpdateGui())
|
||||
})
|
||||
}
|
||||
holder.binding.iconTrash.visibility = (!event.readOnly).toVisibility()
|
||||
holder.binding.aapsLogo.visibility = (event.systemAction).toVisibility()
|
||||
holder.binding.cbRemove.isChecked = selectedItems.get(position) != null
|
||||
holder.binding.cbRemove.setOnCheckedChangeListener { _, value -> updateSelection(value) }
|
||||
holder.binding.iconSort.visibility = (sortActionMode != null).toVisibility()
|
||||
holder.binding.cbRemove.visibility = (removeActionMode != null).toVisibility()
|
||||
holder.binding.cbRemove.isEnabled = !automation.readOnly
|
||||
holder.binding.enabled.visibility = if (removeActionMode == null) View.VISIBLE else View.INVISIBLE
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = automationPlugin.size()
|
||||
|
||||
override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
|
||||
automationPlugin.swap(fromPosition, toPosition)
|
||||
notifyItemMoved(fromPosition, toPosition)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onItemDismiss(position: Int) {
|
||||
activity?.let { activity ->
|
||||
OKDialog.showConfirmation(
|
||||
activity,
|
||||
rh.gs(R.string.removerecord) + " " + automationPlugin.at(position).title,
|
||||
{
|
||||
uel.log(Action.AUTOMATION_REMOVED, Sources.Automation, automationPlugin.at(position).title)
|
||||
automationPlugin.removeAt(position)
|
||||
notifyItemRemoved(position)
|
||||
override fun onDrop() {
|
||||
rxBus.send(EventAutomationDataChanged())
|
||||
}, { rxBus.send(EventAutomationUpdateGui()) })
|
||||
}
|
||||
}
|
||||
|
||||
inner class ViewHolder(view: View, val context: Context) : RecyclerView.ViewHolder(view), ItemTouchHelperViewHolder {
|
||||
inner class ViewHolder(view: View, val context: Context) : RecyclerView.ViewHolder(view) {
|
||||
|
||||
val binding = AutomationEventItemBinding.bind(view)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onItemSelected() = itemView.setBackgroundColor(Color.LTGRAY)
|
||||
inner class SortActionModeCallback : ActionMode.Callback {
|
||||
|
||||
override fun onItemClear() = itemView.setBackgroundColor(rh.gc(R.color.ribbonDefault))
|
||||
override fun onCreateActionMode(mode: ActionMode, menu: Menu?): Boolean {
|
||||
mode.title = rh.gs(R.string.sort_label)
|
||||
binding.eventListView.adapter?.notifyDataSetChanged()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?) = false
|
||||
|
||||
override fun onActionItemClicked(mode: ActionMode, item: MenuItem) = false
|
||||
|
||||
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||
sortActionMode = null
|
||||
binding.eventListView.adapter?.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
inner class RemoveActionModeCallback : ActionMode.Callback {
|
||||
|
||||
override fun onCreateActionMode(mode: ActionMode, menu: Menu?): Boolean {
|
||||
mode.menuInflater.inflate(R.menu.menu_delete_selection, menu)
|
||||
selectedItems.clear()
|
||||
mode.title = rh.gs(R.string.count_selected, selectedItems.size())
|
||||
binding.eventListView.adapter?.notifyDataSetChanged()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?) = false
|
||||
|
||||
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.remove_selected -> {
|
||||
removeSelected()
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||
removeActionMode = null
|
||||
binding.eventListView.adapter?.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getConfirmationText(): String {
|
||||
if (selectedItems.size() == 1) {
|
||||
val event = selectedItems.valueAt(0)
|
||||
return rh.gs(R.string.removerecord) + " " + event.title
|
||||
}
|
||||
return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size())
|
||||
}
|
||||
|
||||
private fun removeSelected() {
|
||||
if (selectedItems.size() > 0) {
|
||||
activity?.let { activity ->
|
||||
OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(), Runnable {
|
||||
selectedItems.forEach { _, event ->
|
||||
uel.log(Action.AUTOMATION_REMOVED, Sources.Automation, event.title)
|
||||
automationPlugin.removeAt(event.position)
|
||||
rxBus.send(EventAutomationDataChanged())
|
||||
}
|
||||
removeActionMode?.finish()
|
||||
})
|
||||
}
|
||||
} else {
|
||||
removeActionMode?.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -169,14 +169,14 @@ class AutomationPlugin @Inject constructor(
|
|||
val array = JSONArray(data)
|
||||
for (i in 0 until array.length()) {
|
||||
val o = array.getJSONObject(i)
|
||||
val event = AutomationEvent(injector).fromJSON(o.toString())
|
||||
val event = AutomationEvent(injector).fromJSON(o.toString(), i)
|
||||
automationEvents.add(event)
|
||||
}
|
||||
} catch (e: JSONException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
else
|
||||
automationEvents.add(AutomationEvent(injector).fromJSON(event))
|
||||
automationEvents.add(AutomationEvent(injector).fromJSON(event, 0))
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
|
@ -305,7 +305,6 @@ class AutomationPlugin @Inject constructor(
|
|||
@Synchronized
|
||||
fun swap(fromPosition: Int, toPosition: Int) {
|
||||
Collections.swap(automationEvents, fromPosition, toPosition)
|
||||
rxBus.send(EventAutomationDataChanged())
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
|
|
|
@ -56,7 +56,7 @@ class EditEventDialog : DialogFragmentWithDate() {
|
|||
// load data from bundle
|
||||
(savedInstanceState ?: arguments)?.let { bundle ->
|
||||
position = bundle.getInt("position", -1)
|
||||
bundle.getString("event")?.let { event = AutomationEvent(injector).fromJSON(it) }
|
||||
bundle.getString("event")?.let { event = AutomationEvent(injector).fromJSON(it, position) }
|
||||
}
|
||||
|
||||
onCreateViewGeneral()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package info.nightscout.androidaps.plugins.general.automation.dragHelpers
|
||||
|
||||
|
||||
interface ItemTouchHelperAdapter {
|
||||
|
||||
/**
|
||||
* Called when an item has been dragged far enough to trigger a move. This is called every time
|
||||
* an item is shifted, and **not** at the end of a "drop" event.<br></br>
|
||||
|
@ -18,16 +18,6 @@ interface ItemTouchHelperAdapter {
|
|||
*/
|
||||
fun onItemMove(fromPosition: Int, toPosition: Int): Boolean
|
||||
|
||||
/**
|
||||
* Called when an item has been dismissed by a swipe.<br></br>
|
||||
* <br></br>
|
||||
* Implementations should call [RecyclerView.Adapter.notifyItemRemoved] after
|
||||
* adjusting the underlying data to reflect this removal.
|
||||
*
|
||||
* @param position The position of the item dismissed.
|
||||
*
|
||||
* @see RecyclerView.getAdapterPositionFor
|
||||
* @see RecyclerView.ViewHolder.getAdapterPosition
|
||||
*/
|
||||
fun onItemDismiss(position: Int)
|
||||
fun onDrop()
|
||||
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
package info.nightscout.androidaps.plugins.general.automation.dragHelpers
|
||||
|
||||
/**
|
||||
* Interface to notify an item ViewHolder of relevant callbacks from [ ].
|
||||
*
|
||||
* @author Paul Burke (ipaulpro)
|
||||
*/
|
||||
interface ItemTouchHelperViewHolder {
|
||||
/**
|
||||
* Called when the [ItemTouchHelper] first registers an item as being moved or swiped.
|
||||
* Implementations should update the item view to indicate it's active state.
|
||||
*/
|
||||
fun onItemSelected()
|
||||
|
||||
/**
|
||||
* Called when the [ItemTouchHelper] has completed the move or swipe, and the active item
|
||||
* state should be cleared.
|
||||
*/
|
||||
fun onItemClear()
|
||||
}
|
|
@ -1,85 +1,37 @@
|
|||
package info.nightscout.androidaps.plugins.general.automation.dragHelpers
|
||||
|
||||
import android.graphics.Canvas
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlin.math.abs
|
||||
import info.nightscout.androidaps.plugins.general.automation.AutomationFragment
|
||||
|
||||
class SimpleItemTouchHelperCallback : ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP or ItemTouchHelper.DOWN or ItemTouchHelper.START or ItemTouchHelper.END, 0) {
|
||||
|
||||
/**
|
||||
* An implementation of [ItemTouchHelper.Callback] that enables basic drag & drop and
|
||||
* swipe-to-dismiss. Drag events are automatically started by an item long-press.<br></br>
|
||||
*
|
||||
* Expects the `RecyclerView.Adapter` to listen for [ ] callbacks and the `RecyclerView.ViewHolder` to implement
|
||||
* [ItemTouchHelperViewHolder].
|
||||
*
|
||||
*/
|
||||
class SimpleItemTouchHelperCallback(private val mAdapter: ItemTouchHelperAdapter) : ItemTouchHelper.Callback() {
|
||||
override fun isLongPressDragEnabled(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun isItemViewSwipeEnabled(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int { // Set movement flags based on the layout manager
|
||||
return if (recyclerView.layoutManager is GridLayoutManager) {
|
||||
val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN or ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
|
||||
val swipeFlags = 0
|
||||
makeMovementFlags(dragFlags, swipeFlags)
|
||||
} else {
|
||||
val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
|
||||
val swipeFlags = ItemTouchHelper.START or ItemTouchHelper.END
|
||||
makeMovementFlags(dragFlags, swipeFlags)
|
||||
}
|
||||
}
|
||||
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
|
||||
val adapter = recyclerView.adapter as AutomationFragment.EventListAdapter
|
||||
val from = viewHolder.layoutPosition
|
||||
val to = target.layoutPosition
|
||||
adapter.onItemMove(from, to)
|
||||
adapter.notifyItemMoved(from, to)
|
||||
|
||||
override fun onMove(recyclerView: RecyclerView, source: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
|
||||
if (source.itemViewType != target.itemViewType) {
|
||||
return false
|
||||
}
|
||||
// Notify the adapter of the move
|
||||
mAdapter.onItemMove(source.absoluteAdapterPosition, target.absoluteAdapterPosition)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, i: Int) { // Notify the adapter of the dismissal
|
||||
mAdapter.onItemDismiss(viewHolder.absoluteAdapterPosition)
|
||||
}
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {}
|
||||
|
||||
override fun onChildDraw(c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean) {
|
||||
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) { // Fade out the view as it is swiped out of the parent's bounds
|
||||
val alpha = ALPHA_FULL - abs(dX) / viewHolder.itemView.width.toFloat()
|
||||
viewHolder.itemView.alpha = alpha
|
||||
viewHolder.itemView.translationX = dX
|
||||
} else {
|
||||
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) { // We only want the active item to change
|
||||
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
|
||||
if (viewHolder is ItemTouchHelperViewHolder) { // Let the view holder know that this item is being moved or dragged
|
||||
val itemViewHolder: ItemTouchHelperViewHolder = viewHolder
|
||||
itemViewHolder.onItemSelected()
|
||||
}
|
||||
}
|
||||
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
|
||||
super.onSelectedChanged(viewHolder, actionState)
|
||||
if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) {
|
||||
viewHolder?.itemView?.alpha = 0.5f
|
||||
}
|
||||
}
|
||||
|
||||
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
|
||||
super.clearView(recyclerView, viewHolder)
|
||||
viewHolder.itemView.alpha = ALPHA_FULL
|
||||
if (viewHolder is ItemTouchHelperViewHolder) { // Tell the view holder it's time to restore the idle state
|
||||
val itemViewHolder: ItemTouchHelperViewHolder = viewHolder
|
||||
itemViewHolder.onItemClear()
|
||||
viewHolder.itemView.alpha = 1.0f
|
||||
(recyclerView.adapter as AutomationFragment.EventListAdapter).onDrop()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ALPHA_FULL = 1.0f
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/rootLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -26,7 +27,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:contentDescription="@string/remove_label"
|
||||
android:contentDescription="@string/system_automation"
|
||||
android:scaleX="0.9"
|
||||
android:scaleY="0.9"
|
||||
android:src="@drawable/ic_notif_aaps"
|
||||
|
@ -42,18 +43,17 @@
|
|||
android:textAlignment="viewStart"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toTopOf="@+id/iconLayout"
|
||||
app:layout_constraintEnd_toStartOf="@+id/iconTrash"
|
||||
app:layout_constraintEnd_toStartOf="@+id/cb_remove"
|
||||
app:layout_constraintStart_toEndOf="@+id/aapsLogo"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Event title" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iconTrash"
|
||||
<CheckBox
|
||||
android:id="@+id/cb_remove"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:contentDescription="@string/remove_label"
|
||||
android:orientation="horizontal"
|
||||
android:src="@drawable/ic_trash_outline"
|
||||
app:layout_constraintBottom_toTopOf="@+id/iconLayout"
|
||||
app:layout_constraintEnd_toStartOf="@+id/iconSort"
|
||||
app:layout_constraintStart_toEndOf="@+id/eventTitle"
|
||||
|
@ -63,12 +63,12 @@
|
|||
android:id="@+id/iconSort"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:contentDescription="@string/reorder_label"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginEnd="8dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/iconLayout"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/iconTrash"
|
||||
app:layout_constraintStart_toEndOf="@+id/cb_remove"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_reorder_gray_24dp" />
|
||||
|
||||
|
@ -78,10 +78,10 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingBottom="4dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/iconSort" />
|
||||
|
||||
app:layout_constraintTop_toBottomOf="@+id/enabled" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
16
automation/src/main/res/menu/menu_automation.xml
Normal file
16
automation/src/main/res/menu/menu_automation.xml
Normal file
|
@ -0,0 +1,16 @@
|
|||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_remove_automation_items"
|
||||
android:orderInCategory="0"
|
||||
android:title="@string/remove_automation"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_sort_automation_items"
|
||||
android:orderInCategory="0"
|
||||
android:title="@string/sort_automation"
|
||||
app:showAsAction="never" />
|
||||
|
||||
</menu>
|
11
automation/src/main/res/menu/menu_delete_selection.xml
Normal file
11
automation/src/main/res/menu/menu_delete_selection.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/remove_selected"
|
||||
android:icon="@drawable/ic_trash"
|
||||
android:iconTint="@color/white"
|
||||
android:title="@string/remove_selected_items"
|
||||
app:showAsAction="always" />
|
||||
|
||||
</menu>
|
|
@ -116,5 +116,12 @@
|
|||
<string name="reorder_label">Reorder</string>
|
||||
<string name="key_automation_settings" translatable="false">automation_settings</string>
|
||||
<string name="user_action">User action</string>
|
||||
<string name="remove_automation">Remove automation</string>
|
||||
<string name="sort_automation">Sort automation</string>
|
||||
<string name="remove_selected_items">Remove selected items</string>
|
||||
<string name="count_selected">%1$d selected</string>
|
||||
<string name="confirm_remove_multiple_items">Are you sure you want to remove %1$d items</string>
|
||||
<string name="sort_label">Sort</string>
|
||||
<string name="system_automation">System automation</string>
|
||||
|
||||
</resources>
|
|
@ -54,7 +54,7 @@ class AutomationEventTest : TestBase() {
|
|||
Assert.assertEquals(eventJsonExpected, event.toJSON())
|
||||
|
||||
// clone
|
||||
val clone = AutomationEvent(injector).fromJSON(eventJsonExpected)
|
||||
val clone = AutomationEvent(injector).fromJSON(eventJsonExpected, 1)
|
||||
|
||||
// check title
|
||||
Assert.assertEquals(event.title, clone.title)
|
||||
|
|
Loading…
Reference in a new issue