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
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
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
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -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>
|
|
||||||
|
|
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="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>
|
|
@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue