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

View file

@ -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 {
val dialog = EditEventDialog()
val args = Bundle()
args.putString("event", event.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)
return@setOnTouchListener true
holder.binding.aapsLogo.visibility = (automation.systemAction).toVisibility()
fun updateSelection(selected: Boolean) {
if (selected) {
selectedItems.put(position, automation)
} else {
selectedItems.remove(position)
}
v.onTouchEvent(motionEvent)
removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size())
}
// 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.rootLayout.setOnTouchListener { _, touchEvent ->
if (touchEvent.actionMasked == MotionEvent.ACTION_UP && sortActionMode == null && removeActionMode == null) {
val dialog = EditEventDialog()
val args = Bundle()
args.putString("event", automation.toJSON())
args.putInt("position", position)
dialog.arguments = args
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.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)
rxBus.send(EventAutomationDataChanged())
}, { rxBus.send(EventAutomationUpdateGui()) })
override fun onDrop() {
rxBus.send(EventAutomationDataChanged())
}
inner class ViewHolder(view: View, val context: Context) : RecyclerView.ViewHolder(view) {
val binding = AutomationEventItemBinding.bind(view)
}
}
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)
override fun onItemClear() = itemView.setBackgroundColor(rh.gc(R.color.ribbonDefault))
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()
}
}
}

View file

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

View file

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

View file

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

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

View file

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

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="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>

View file

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