feat: automation option menu

This commit is contained in:
Andries Smit 2022-03-14 20:46:30 +01:00
parent b511cc147f
commit b1ac36fd16
12 changed files with 245 additions and 194 deletions

View file

@ -20,6 +20,7 @@ class AutomationEvent(private val injector: HasAndroidInjector) {
var title: String = "" var title: String = ""
var isEnabled = true var isEnabled = true
var position = -1
var systemAction: Boolean = false // true = generated by AAPS, false = entered by user var systemAction: Boolean = false // true = generated by AAPS, false = entered by user
var readOnly: Boolean = false // removing, editing disabled var readOnly: Boolean = false // removing, editing disabled
var autoRemove: Boolean = false // auto-remove once used var autoRemove: Boolean = false // auto-remove once used
@ -66,11 +67,12 @@ class AutomationEvent(private val injector: HasAndroidInjector) {
.toString() .toString()
} }
fun fromJSON(data: String): AutomationEvent { fun fromJSON(data: String, position: Int): AutomationEvent {
val d = JSONObject(data) val d = JSONObject(data)
title = d.optString("title", "") title = d.optString("title", "")
isEnabled = d.optBoolean("enabled", true) isEnabled = d.optBoolean("enabled", true)
systemAction = d.optBoolean("systemAction", false) systemAction = d.optBoolean("systemAction", false)
this.position = position
readOnly = d.optBoolean("readOnly", false) readOnly = d.optBoolean("readOnly", false)
autoRemove = d.optBoolean("autoRemove", false) autoRemove = d.optBoolean("autoRemove", false)
userAction = d.optBoolean("userAction", false) userAction = d.optBoolean("userAction", false)
@ -88,4 +90,4 @@ class AutomationEvent(private val injector: HasAndroidInjector) {
fun shouldRun(): Boolean { fun shouldRun(): Boolean {
return lastRun <= dateUtil.now() - T.mins(5).msecs() return lastRun <= dateUtil.now() - T.mins(5).msecs()
} }
} }

View file

@ -2,13 +2,11 @@ package info.nightscout.androidaps.plugins.general.automation
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.text.method.ScrollingMovementMethod import android.text.method.ScrollingMovementMethod
import android.view.LayoutInflater import android.util.SparseArray
import android.view.MotionEvent import android.view.*
import android.view.View import androidx.core.util.forEach
import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.annotation.DrawableRes 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.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBus import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.automation.dialogs.EditEventDialog 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.EventAutomationDataChanged
import info.nightscout.androidaps.plugins.general.automation.events.EventAutomationUpdateGui import info.nightscout.androidaps.plugins.general.automation.events.EventAutomationUpdateGui
import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnector 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 info.nightscout.androidaps.utils.alertDialogs.OKDialog
import io.reactivex.rxjava3.kotlin.plusAssign import io.reactivex.rxjava3.kotlin.plusAssign
import info.nightscout.androidaps.extensions.toVisibility 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.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers import info.nightscout.androidaps.utils.rx.AapsSchedulers
import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.disposables.CompositeDisposable
@ -55,13 +52,14 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
private var disposable: CompositeDisposable = CompositeDisposable() private var disposable: CompositeDisposable = CompositeDisposable()
private lateinit var eventListAdapter: EventListAdapter private lateinit var eventListAdapter: EventListAdapter
private var selectedItems: SparseArray<AutomationEvent> = SparseArray()
private var itemTouchHelper: ItemTouchHelper? = null private var removeActionMode: ActionMode? = null
private var sortActionMode: ActionMode? = null
private val itemTouchHelper = ItemTouchHelper(SimpleItemTouchHelperCallback())
private var _binding: AutomationFragmentBinding? = null private var _binding: AutomationFragmentBinding? = null
// This property is only valid between onCreateView and // This property is only valid between onCreateView and onDestroyView.
// onDestroyView.
private val binding get() = _binding!! private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { 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?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setHasOptionsMenu(true)
eventListAdapter = EventListAdapter() eventListAdapter = EventListAdapter()
binding.eventListView.layoutManager = LinearLayoutManager(context) binding.eventListView.layoutManager = LinearLayoutManager(context)
binding.eventListView.adapter = eventListAdapter binding.eventListView.adapter = eventListAdapter
binding.logView.movementMethod = ScrollingMovementMethod() binding.logView.movementMethod = ScrollingMovementMethod()
binding.fabAddEvent.setOnClickListener { binding.fabAddEvent.setOnClickListener {
removeActionMode?.finish()
sortActionMode?.finish()
val dialog = EditEventDialog() val dialog = EditEventDialog()
val args = Bundle() val args = Bundle()
args.putString("event", AutomationEvent(injector).toJSON()) args.putString("event", AutomationEvent(injector).toJSON())
@ -87,12 +86,10 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
dialog.show(childFragmentManager, "EditEventDialog") dialog.show(childFragmentManager, "EditEventDialog")
} }
val callback: ItemTouchHelper.Callback = SimpleItemTouchHelperCallback(eventListAdapter) itemTouchHelper.attachToRecyclerView(binding.eventListView)
itemTouchHelper = ItemTouchHelper(callback)
itemTouchHelper?.attachToRecyclerView(binding.eventListView)
} }
@SuppressLint("NotifyDataSetChanged")
@Synchronized @Synchronized
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
@ -113,6 +110,8 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
@Synchronized @Synchronized
override fun onPause() { override fun onPause() {
removeActionMode?.finish()
sortActionMode?.finish()
super.onPause() super.onPause()
disposable.clear() disposable.clear()
} }
@ -120,9 +119,39 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
@Synchronized @Synchronized
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
removeActionMode?.finish()
sortActionMode?.finish()
_binding = null _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 @Synchronized
private fun updateGui() { private fun updateGui() {
if (_binding == null) return if (_binding == null) return
@ -134,7 +163,7 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
} }
override fun onStartDrag(viewHolder: RecyclerView.ViewHolder) { override fun onStartDrag(viewHolder: RecyclerView.ViewHolder) {
itemTouchHelper?.startDrag(viewHolder) itemTouchHelper.startDrag(viewHolder)
} }
fun fillIconSet(connector: TriggerConnector, set: HashSet<Int>) { fun fillIconSet(connector: TriggerConnector, set: HashSet<Int>) {
@ -166,20 +195,22 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val event = automationPlugin.at(position) val automation = automationPlugin.at(position)
holder.binding.rootLayout.setBackgroundColor(rh.gc( holder.binding.rootLayout.setBackgroundColor(
if (event.userAction) R.color.mdtp_line_dark rh.gc(
else if (event.areActionsValid()) R.color.ribbonDefault if (automation.userAction) R.color.mdtp_line_dark
else R.color.errorAlertBackground) else if (automation.areActionsValid()) R.color.ribbonDefault
else R.color.errorAlertBackground
)
) )
holder.binding.eventTitle.text = event.title holder.binding.eventTitle.text = automation.title
holder.binding.enabled.isChecked = event.isEnabled holder.binding.enabled.isChecked = automation.isEnabled
holder.binding.enabled.isEnabled = !event.readOnly holder.binding.enabled.isEnabled = !automation.readOnly
holder.binding.iconLayout.removeAllViews() holder.binding.iconLayout.removeAllViews()
// trigger icons // trigger icons
val triggerIcons = HashSet<Int>() val triggerIcons = HashSet<Int>()
if (event.userAction) triggerIcons.add(R.drawable.ic_danar_useropt) if (automation.userAction) triggerIcons.add(R.drawable.ic_danar_useropt)
fillIconSet(event.trigger, triggerIcons) fillIconSet(automation.trigger, triggerIcons)
for (res in triggerIcons) { for (res in triggerIcons) {
addImage(res, holder.context, holder.binding.iconLayout) addImage(res, holder.context, holder.binding.iconLayout)
} }
@ -191,7 +222,7 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
holder.binding.iconLayout.addView(iv) holder.binding.iconLayout.addView(iv)
// action icons // action icons
val actionIcons = HashSet<Int>() val actionIcons = HashSet<Int>()
for (action in event.actions) { for (action in automation.actions) {
actionIcons.add(action.icon()) actionIcons.add(action.icon())
} }
for (res in actionIcons) { for (res in actionIcons) {
@ -199,70 +230,133 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
} }
// enabled event // enabled event
holder.binding.enabled.setOnClickListener { holder.binding.enabled.setOnClickListener {
event.isEnabled = holder.binding.enabled.isChecked automation.isEnabled = holder.binding.enabled.isChecked
rxBus.send(EventAutomationDataChanged()) rxBus.send(EventAutomationDataChanged())
} }
// edit event holder.binding.aapsLogo.visibility = (automation.systemAction).toVisibility()
holder.binding.rootLayout.setOnClickListener {
val dialog = EditEventDialog() fun updateSelection(selected: Boolean) {
val args = Bundle() if (selected) {
args.putString("event", event.toJSON()) selectedItems.put(position, automation)
args.putInt("position", position) } else {
dialog.arguments = args selectedItems.remove(position)
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)
return@setOnTouchListener true
} }
v.onTouchEvent(motionEvent) removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size())
} }
// remove event
holder.binding.iconTrash.setOnClickListener { holder.binding.rootLayout.setOnTouchListener { _, touchEvent ->
OKDialog.showConfirmation(requireContext(), rh.gs(R.string.removerecord) + " " + automationPlugin.at(position).title, if (touchEvent.actionMasked == MotionEvent.ACTION_UP && sortActionMode == null && removeActionMode == null) {
{ val dialog = EditEventDialog()
uel.log(Action.AUTOMATION_REMOVED, Sources.Automation, automationPlugin.at(position).title) val args = Bundle()
automationPlugin.removeAt(position) args.putString("event", automation.toJSON())
notifyItemRemoved(position) args.putInt("position", position)
}, { dialog.arguments = args
rxBus.send(EventAutomationUpdateGui()) dialog.show(childFragmentManager, "EditEventDialog")
}) }
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
} }
holder.binding.iconTrash.visibility = (!event.readOnly).toVisibility() holder.binding.cbRemove.isChecked = selectedItems.get(position) != null
holder.binding.aapsLogo.visibility = (event.systemAction).toVisibility() 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 getItemCount(): Int = automationPlugin.size()
override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean { override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
automationPlugin.swap(fromPosition, toPosition) automationPlugin.swap(fromPosition, toPosition)
notifyItemMoved(fromPosition, toPosition)
return true return true
} }
override fun onItemDismiss(position: Int) { override fun onDrop() {
activity?.let { activity -> rxBus.send(EventAutomationDataChanged())
OKDialog.showConfirmation( }
activity,
rh.gs(R.string.removerecord) + " " + automationPlugin.at(position).title, inner class ViewHolder(view: View, val context: Context) : RecyclerView.ViewHolder(view) {
{
uel.log(Action.AUTOMATION_REMOVED, Sources.Automation, automationPlugin.at(position).title) val binding = AutomationEventItemBinding.bind(view)
automationPlugin.removeAt(position) }
notifyItemRemoved(position) }
rxBus.send(EventAutomationDataChanged())
}, { rxBus.send(EventAutomationUpdateGui()) }) inner class SortActionModeCallback : ActionMode.Callback {
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
} }
} }
inner class ViewHolder(view: View, val context: Context) : RecyclerView.ViewHolder(view), ItemTouchHelperViewHolder { override fun onDestroyActionMode(mode: ActionMode?) {
removeActionMode = null
binding.eventListView.adapter?.notifyDataSetChanged()
}
}
val binding = AutomationEventItemBinding.bind(view) 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())
}
override fun onItemSelected() = itemView.setBackgroundColor(Color.LTGRAY) private fun removeSelected() {
if (selectedItems.size() > 0) {
override fun onItemClear() = itemView.setBackgroundColor(rh.gc(R.color.ribbonDefault)) 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()
} }
} }
} }

View file

@ -169,14 +169,14 @@ class AutomationPlugin @Inject constructor(
val array = JSONArray(data) val array = JSONArray(data)
for (i in 0 until array.length()) { for (i in 0 until array.length()) {
val o = array.getJSONObject(i) val o = array.getJSONObject(i)
val event = AutomationEvent(injector).fromJSON(o.toString()) val event = AutomationEvent(injector).fromJSON(o.toString(), i)
automationEvents.add(event) automationEvents.add(event)
} }
} catch (e: JSONException) { } catch (e: JSONException) {
e.printStackTrace() e.printStackTrace()
} }
else else
automationEvents.add(AutomationEvent(injector).fromJSON(event)) automationEvents.add(AutomationEvent(injector).fromJSON(event, 0))
} }
@Synchronized @Synchronized
@ -305,7 +305,6 @@ class AutomationPlugin @Inject constructor(
@Synchronized @Synchronized
fun swap(fromPosition: Int, toPosition: Int) { fun swap(fromPosition: Int, toPosition: Int) {
Collections.swap(automationEvents, fromPosition, toPosition) Collections.swap(automationEvents, fromPosition, toPosition)
rxBus.send(EventAutomationDataChanged())
} }
@Synchronized @Synchronized

View file

@ -56,7 +56,7 @@ class EditEventDialog : DialogFragmentWithDate() {
// load data from bundle // load data from bundle
(savedInstanceState ?: arguments)?.let { bundle -> (savedInstanceState ?: arguments)?.let { bundle ->
position = bundle.getInt("position", -1) 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() onCreateViewGeneral()

View file

@ -1,7 +1,7 @@
package info.nightscout.androidaps.plugins.general.automation.dragHelpers package info.nightscout.androidaps.plugins.general.automation.dragHelpers
interface ItemTouchHelperAdapter { interface ItemTouchHelperAdapter {
/** /**
* Called when an item has been dragged far enough to trigger a move. This is called every time * 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> * 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 fun onItemMove(fromPosition: Int, toPosition: Int): Boolean
/** fun onDrop()
* 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)
}

View file

@ -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()
}

View file

@ -1,85 +1,37 @@
package info.nightscout.androidaps.plugins.general.automation.dragHelpers 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.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView 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 { override fun isLongPressDragEnabled(): Boolean {
return true
}
override fun isItemViewSwipeEnabled(): Boolean {
return false return false
} }
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int { // Set movement flags based on the layout manager override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
return if (recyclerView.layoutManager is GridLayoutManager) { val adapter = recyclerView.adapter as AutomationFragment.EventListAdapter
val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN or ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT val from = viewHolder.layoutPosition
val swipeFlags = 0 val to = target.layoutPosition
makeMovementFlags(dragFlags, swipeFlags) adapter.onItemMove(from, to)
} else { adapter.notifyItemMoved(from, to)
val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
val swipeFlags = ItemTouchHelper.START or ItemTouchHelper.END
makeMovementFlags(dragFlags, swipeFlags)
}
}
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 return true
} }
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, i: Int) { // Notify the adapter of the dismissal override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {}
mAdapter.onItemDismiss(viewHolder.absoluteAdapterPosition)
}
override fun onChildDraw(c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean) { override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
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()
}
}
super.onSelectedChanged(viewHolder, actionState) super.onSelectedChanged(viewHolder, actionState)
if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) {
viewHolder?.itemView?.alpha = 0.5f
}
} }
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
super.clearView(recyclerView, viewHolder) super.clearView(recyclerView, viewHolder)
viewHolder.itemView.alpha = ALPHA_FULL viewHolder.itemView.alpha = 1.0f
if (viewHolder is ItemTouchHelperViewHolder) { // Tell the view holder it's time to restore the idle state (recyclerView.adapter as AutomationFragment.EventListAdapter).onDrop()
val itemViewHolder: ItemTouchHelperViewHolder = viewHolder
itemViewHolder.onItemClear()
}
} }
}
companion object {
const val ALPHA_FULL = 1.0f
}
}

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rootLayout" android:id="@+id/rootLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -26,7 +27,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="5dp" android:layout_marginEnd="5dp"
android:contentDescription="@string/remove_label" android:contentDescription="@string/system_automation"
android:scaleX="0.9" android:scaleX="0.9"
android:scaleY="0.9" android:scaleY="0.9"
android:src="@drawable/ic_notif_aaps" android:src="@drawable/ic_notif_aaps"
@ -42,18 +43,17 @@
android:textAlignment="viewStart" android:textAlignment="viewStart"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/iconLayout" 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_constraintStart_toEndOf="@+id/aapsLogo"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent"
tools:text="Event title" />
<ImageView <CheckBox
android:id="@+id/iconTrash" android:id="@+id/cb_remove"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="20dp"
android:contentDescription="@string/remove_label" android:contentDescription="@string/remove_label"
android:orientation="horizontal" android:orientation="horizontal"
android:src="@drawable/ic_trash_outline"
app:layout_constraintBottom_toTopOf="@+id/iconLayout" app:layout_constraintBottom_toTopOf="@+id/iconLayout"
app:layout_constraintEnd_toStartOf="@+id/iconSort" app:layout_constraintEnd_toStartOf="@+id/iconSort"
app:layout_constraintStart_toEndOf="@+id/eventTitle" app:layout_constraintStart_toEndOf="@+id/eventTitle"
@ -63,12 +63,12 @@
android:id="@+id/iconSort" android:id="@+id/iconSort"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:contentDescription="@string/reorder_label" android:contentDescription="@string/reorder_label"
android:orientation="horizontal" android:orientation="horizontal"
android:layout_marginEnd="8dp"
app:layout_constraintBottom_toTopOf="@+id/iconLayout" app:layout_constraintBottom_toTopOf="@+id/iconLayout"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/iconTrash" app:layout_constraintStart_toEndOf="@+id/cb_remove"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_reorder_gray_24dp" /> app:srcCompat="@drawable/ic_reorder_gray_24dp" />
@ -78,10 +78,10 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingBottom="4dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/iconSort" /> app:layout_constraintTop_toBottomOf="@+id/enabled" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View 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>

View 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>

View file

@ -116,5 +116,12 @@
<string name="reorder_label">Reorder</string> <string name="reorder_label">Reorder</string>
<string name="key_automation_settings" translatable="false">automation_settings</string> <string name="key_automation_settings" translatable="false">automation_settings</string>
<string name="user_action">User action</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> </resources>

View file

@ -54,7 +54,7 @@ class AutomationEventTest : TestBase() {
Assert.assertEquals(eventJsonExpected, event.toJSON()) Assert.assertEquals(eventJsonExpected, event.toJSON())
// clone // clone
val clone = AutomationEvent(injector).fromJSON(eventJsonExpected) val clone = AutomationEvent(injector).fromJSON(eventJsonExpected, 1)
// check title // check title
Assert.assertEquals(event.title, clone.title) Assert.assertEquals(event.title, clone.title)
@ -70,4 +70,4 @@ class AutomationEventTest : TestBase() {
Assert.assertFalse(event.actions === clone.actions) // not the same object reference Assert.assertFalse(event.actions === clone.actions) // not the same object reference
Assert.assertEquals(clone.toJSON(), clone.toJSON()) Assert.assertEquals(clone.toJSON(), clone.toJSON())
} }
} }