chore: shared action helper sort and remove

This commit is contained in:
Andries Smit 2022-03-28 11:44:58 +02:00
parent 0c4cf71ef8
commit b2bfd47bcb
15 changed files with 436 additions and 443 deletions

View file

@ -7,11 +7,6 @@ import android.view.*
import androidx.core.util.forEach
import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.ItemTouchHelper.ACTION_STATE_DRAG
import androidx.recyclerview.widget.ItemTouchHelper.DOWN
import androidx.recyclerview.widget.ItemTouchHelper.END
import androidx.recyclerview.widget.ItemTouchHelper.START
import androidx.recyclerview.widget.ItemTouchHelper.UP
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import info.nightscout.androidaps.R
@ -20,8 +15,12 @@ import info.nightscout.androidaps.databinding.OverviewQuickwizardlistActivityBin
import info.nightscout.androidaps.databinding.OverviewQuickwizardlistItemBinding
import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.utils.dragHelpers.ItemTouchHelperAdapter
import info.nightscout.androidaps.utils.dragHelpers.OnStartDragListener
import info.nightscout.androidaps.utils.dragHelpers.SimpleItemTouchHelperCallback
import info.nightscout.androidaps.plugins.general.overview.dialogs.EditQuickWizardDialog
import info.nightscout.androidaps.plugins.general.overview.events.EventQuickWizardChange
import info.nightscout.androidaps.utils.ActionHelper
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
@ -33,7 +32,7 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import javax.inject.Inject
class QuickWizardListActivity : DaggerAppCompatActivityWithResult() {
class QuickWizardListActivity : DaggerAppCompatActivityWithResult(), OnStartDragListener {
@Inject lateinit var aapsSchedulers: AapsSchedulers
@Inject lateinit var rxBus: RxBus
@ -43,49 +42,15 @@ class QuickWizardListActivity : DaggerAppCompatActivityWithResult() {
@Inject lateinit var sp: SP
private var disposable: CompositeDisposable = CompositeDisposable()
private var selectedItems: SparseArray<QuickWizardEntry> = SparseArray()
private var removeActionMode: ActionMode? = null
private var sortActionMode: ActionMode? = null
private lateinit var actionHelper: ActionHelper<QuickWizardEntry>
private val itemTouchHelper = ItemTouchHelper(SimpleItemTouchHelperCallback())
private lateinit var binding: OverviewQuickwizardlistActivityBinding
private val itemTouchHelper by lazy {
val simpleItemTouchCallback = object : ItemTouchHelper.SimpleCallback(UP or DOWN or START or END, 0) {
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
val adapter = recyclerView.adapter as RecyclerViewAdapter
val from = viewHolder.layoutPosition
val to = target.layoutPosition
adapter.moveItem(from, to)
adapter.notifyItemMoved(from, to)
return true
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {}
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
super.onSelectedChanged(viewHolder, actionState)
if (actionState == ACTION_STATE_DRAG) {
viewHolder?.itemView?.alpha = 0.5f
}
}
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
super.clearView(recyclerView, viewHolder)
viewHolder.itemView.alpha = 1.0f
(recyclerView.adapter as RecyclerViewAdapter).onDrop()
}
}
ItemTouchHelper(simpleItemTouchCallback)
}
fun startDragging(viewHolder: RecyclerView.ViewHolder) {
override fun onStartDrag(viewHolder: RecyclerView.ViewHolder) {
itemTouchHelper.startDrag(viewHolder)
}
private inner class RecyclerViewAdapter(var fragmentManager: FragmentManager) : RecyclerView.Adapter<RecyclerViewAdapter.QuickWizardEntryViewHolder>() {
private inner class RecyclerViewAdapter(var fragmentManager: FragmentManager) : RecyclerView.Adapter<RecyclerViewAdapter.QuickWizardEntryViewHolder>(), ItemTouchHelperAdapter {
private inner class QuickWizardEntryViewHolder(val binding: OverviewQuickwizardlistItemBinding, val fragmentManager: FragmentManager) : RecyclerView.ViewHolder(binding.root)
@ -113,60 +78,48 @@ class QuickWizardListActivity : DaggerAppCompatActivityWithResult() {
)
}
if (sortActionMode != null && removeActionMode != null) {
holder.binding.cardview.setOnClickListener {
fun click() {
if (actionHelper.isNoAction) {
val manager = fragmentManager
val editQuickWizardDialog = EditQuickWizardDialog()
val bundle = Bundle()
bundle.putInt("position", position)
editQuickWizardDialog.arguments = bundle
editQuickWizardDialog.show(manager, "EditQuickWizardDialog")
}
}
fun updateSelection(selected: Boolean) {
if (selected) {
selectedItems.put(position, entry)
} else {
selectedItems.remove(position)
}
removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size())
}
holder.binding.cardview.setOnTouchListener { _, event ->
if (event.actionMasked == MotionEvent.ACTION_UP && sortActionMode == null && removeActionMode == null) {
val manager = fragmentManager
val editQuickWizardDialog = EditQuickWizardDialog()
val bundle = Bundle()
bundle.putInt("position", position)
editQuickWizardDialog.arguments = bundle
editQuickWizardDialog.show(manager, "EditQuickWizardDialog")
}
if (event.actionMasked == MotionEvent.ACTION_DOWN && sortActionMode != null) {
startDragging(holder)
}
if (event.actionMasked == MotionEvent.ACTION_UP && removeActionMode != null) {
} else if (actionHelper.isRemoving) {
holder.binding.cbRemove.toggle()
updateSelection(holder.binding.cbRemove.isChecked)
actionHelper.updateSelection(position, entry, holder.binding.cbRemove.isChecked)
}
}
// For accessibility add click lister too, unfortunately sort can not made accessible
holder.binding.root.setOnClickListener {
click()
}
holder.binding.root.setOnTouchListener { _, event ->
if (event.actionMasked == MotionEvent.ACTION_UP) {
click()
} else if (event.actionMasked == MotionEvent.ACTION_DOWN && actionHelper.isSorting) {
onStartDrag(holder)
}
return@setOnTouchListener true
}
holder.binding.cbRemove.isChecked = selectedItems.get(position) != null
holder.binding.cbRemove.setOnCheckedChangeListener { _, value -> updateSelection(value) }
holder.binding.handleView.visibility = (sortActionMode != null).toVisibility()
holder.binding.cbRemove.visibility = (removeActionMode != null).toVisibility()
removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size())
holder.binding.cbRemove.isChecked = actionHelper.isSelected(position)
holder.binding.cbRemove.setOnCheckedChangeListener { _, value ->
actionHelper.updateSelection(position, entry, value)
}
holder.binding.handleView.visibility = actionHelper.isSorting.toVisibility()
holder.binding.cbRemove.visibility = actionHelper.isRemoving.toVisibility()
}
override fun getItemCount(): Int = quickWizard.size()
override fun getItemCount() = quickWizard.size()
fun moveItem(from: Int, to: Int) {
quickWizard.move(from, to)
override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
binding.recyclerview.adapter?.notifyItemMoved(fromPosition, toPosition)
quickWizard.move(fromPosition, toPosition)
return true
}
fun onDrop() {
rxBus.send(EventQuickWizardChange())
}
override fun onDrop() = rxBus.send(EventQuickWizardChange())
}
override fun onCreate(savedInstanceState: Bundle?) {
@ -174,6 +127,11 @@ class QuickWizardListActivity : DaggerAppCompatActivityWithResult() {
binding = OverviewQuickwizardlistActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
actionHelper = ActionHelper(rh, this)
actionHelper.setUpdateListHandler { binding.recyclerview.adapter?.notifyDataSetChanged() }
actionHelper.setOnRemoveHandler { removeSelected(it) }
actionHelper.enableSort = true
title = rh.gs(R.string.quickwizard)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
@ -184,6 +142,7 @@ class QuickWizardListActivity : DaggerAppCompatActivityWithResult() {
itemTouchHelper.attachToRecyclerView(binding.recyclerview)
binding.addButton.setOnClickListener {
actionHelper.finish()
val manager = supportFragmentManager
val editQuickWizardDialog = EditQuickWizardDialog()
editQuickWizardDialog.show(manager, "EditQuickWizardDialog")
@ -203,25 +162,22 @@ class QuickWizardListActivity : DaggerAppCompatActivityWithResult() {
override fun onPause() {
disposable.clear()
actionHelper.finish()
super.onPause()
}
private fun removeSelected() {
if (selectedItems.size() > 0)
OKDialog.showConfirmation(this, rh.gs(R.string.removerecord), getConfirmationText(), Runnable {
private fun removeSelected(selectedItems: SparseArray<QuickWizardEntry>) {
OKDialog.showConfirmation(this, rh.gs(R.string.removerecord), getConfirmationText(selectedItems), Runnable {
selectedItems.forEach { _, item ->
quickWizard.remove(item.position)
rxBus.send(EventQuickWizardChange())
}
removeActionMode?.finish()
actionHelper.finish()
})
else
removeActionMode?.finish()
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
val inflater = menuInflater
inflater.inflate(R.menu.menu_quickwizard, menu)
menuInflater.inflate(R.menu.menu_actions, menu)
return super.onCreateOptionsMenu(menu)
}
@ -232,76 +188,11 @@ class QuickWizardListActivity : DaggerAppCompatActivityWithResult() {
true
}
R.id.nav_remove_items -> {
removeActionMode = startActionMode(RemoveActionModeCallback())
true
else -> actionHelper.onOptionsItemSelected(item)
}
R.id.nav_sort_items -> {
sortActionMode = startActionMode(SortActionModeCallback())
true
}
else -> false
}
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.recyclerview.adapter?.notifyDataSetChanged()
return true
}
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?) = false
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
return when (item.itemId) {
R.id.remove_selected -> {
removeSelected()
true
}
else -> false
}
}
override fun onDestroyActionMode(mode: ActionMode?) {
removeActionMode = null
binding.recyclerview.adapter?.notifyDataSetChanged()
}
}
inner class SortActionModeCallback : ActionMode.Callback {
override fun onCreateActionMode(mode: ActionMode, menu: Menu?): Boolean {
mode.title = rh.gs(R.string.sort_label)
binding.recyclerview.adapter?.notifyDataSetChanged()
return true
}
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?) = false
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
return when (item.itemId) {
R.id.remove_selected -> {
removeSelected()
true
}
else -> false
}
}
override fun onDestroyActionMode(mode: ActionMode?) {
sortActionMode = null
binding.recyclerview.adapter?.notifyDataSetChanged()
}
}
private fun getConfirmationText(): String {
private fun getConfirmationText(selectedItems: SparseArray<QuickWizardEntry>): String {
if (selectedItems.size() == 1) {
val entry = selectedItems.valueAt(0)
return "${rh.gs(R.string.remove_button)} ${entry.buttonText()} ${rh.gs(R.string.format_carbs, entry.carbs())}\n" +
@ -309,4 +200,5 @@ class QuickWizardListActivity : DaggerAppCompatActivityWithResult() {
}
return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size())
}
}

View file

@ -3,7 +3,6 @@ package info.nightscout.androidaps.plugins.source
import android.os.Bundle
import android.util.SparseArray
import android.view.*
import androidx.appcompat.widget.Toolbar
import androidx.core.util.forEach
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
@ -26,6 +25,7 @@ import info.nightscout.androidaps.interfaces.PluginBase
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.utils.ActionHelper
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.T
@ -54,11 +54,9 @@ class BGSourceFragment : DaggerFragment() {
private val disposable = CompositeDisposable()
private val millsToThePast = T.hours(36).msecs()
private var selectedItems: SparseArray<GlucoseValue> = SparseArray()
private var toolbar: Toolbar? = null
private var removeActionMode: ActionMode? = null
private lateinit var actionHelper: ActionHelper<GlucoseValue>
private var _binding: BgsourceFragmentBinding? = null
// This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!!
@ -67,8 +65,10 @@ class BGSourceFragment : DaggerFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
toolbar = activity?.findViewById(R.id.toolbar)
setHasOptionsMenu(true)
actionHelper = ActionHelper(rh, activity)
actionHelper.setUpdateListHandler { binding.recyclerview.adapter?.notifyDataSetChanged() }
actionHelper.setOnRemoveHandler { removeSelected(it) }
setHasOptionsMenu(actionHelper.inMenu)
binding.recyclerview.setHasFixedSize(true)
binding.recyclerview.layoutManager = LinearLayoutManager(view.context)
@ -97,44 +97,30 @@ class BGSourceFragment : DaggerFragment() {
@Synchronized
override fun onPause() {
removeActionMode?.finish()
actionHelper.finish()
disposable.clear()
super.onPause()
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.menu_bgsource, menu)
actionHelper.onCreateOptionsMenu(menu, inflater)
}
override fun onPrepareOptionsMenu(menu: Menu) {
// Only show when tab bg source is shown
menu.findItem(R.id.nav_remove_items)?.isVisible = isResumed
super.onPrepareOptionsMenu(menu)
actionHelper.onPrepareOptionsMenu(menu)
}
@Synchronized
override fun onDestroyView() {
super.onDestroyView()
removeActionMode?.finish()
binding.recyclerview.adapter = null // avoid leaks
_binding = null
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.nav_remove_items -> {
if (toolbar != null) {
removeActionMode = toolbar?.startActionMode(RemoveActionModeCallback()) // in overview
} else {
removeActionMode = activity?.startActionMode(RemoveActionModeCallback()) // in Single FragmentActivity
}
true
}
else -> false
}
}
override fun onOptionsItemSelected(item: MenuItem) =
actionHelper.onOptionsItemSelected(item)
inner class RecyclerViewAdapter internal constructor(private var glucoseValues: List<GlucoseValue>) : RecyclerView.Adapter<RecyclerViewAdapter.GlucoseValuesViewHolder>() {
@ -159,34 +145,27 @@ class BGSourceFragment : DaggerFragment() {
if (diff < T.secs(20).msecs())
holder.binding.root.setBackgroundColor(rh.gc(R.color.errorAlertBackground))
}
fun updateSelection(selected: Boolean) {
if (selected) {
selectedItems.put(position, glucoseValue)
} else {
selectedItems.remove(position)
}
removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size())
}
holder.binding.root.setOnLongClickListener {
if (removeActionMode == null) {
removeActionMode = toolbar?.startActionMode(RemoveActionModeCallback())
}
actionHelper.startRemove()
holder.binding.cbRemove.toggle()
updateSelection(holder.binding.cbRemove.isChecked)
actionHelper.updateSelection(position, glucoseValue, holder.binding.cbRemove.isChecked)
true
}
holder.binding.root.setOnClickListener {
if (removeActionMode != null) {
if (actionHelper.isRemoving) {
holder.binding.cbRemove.toggle()
updateSelection(holder.binding.cbRemove.isChecked)
actionHelper.updateSelection(position, glucoseValue, holder.binding.cbRemove.isChecked)
}
}
holder.binding.cbRemove.setOnCheckedChangeListener { _, value -> updateSelection(value) }
holder.binding.cbRemove.isChecked = selectedItems.get(position) != null
holder.binding.cbRemove.visibility = (removeActionMode != null).toVisibility()
holder.binding.cbRemove.setOnCheckedChangeListener { _, value ->
actionHelper.updateSelection(position, glucoseValue, value)
}
holder.binding.cbRemove.isChecked = actionHelper.isSelected(position)
holder.binding.cbRemove.visibility = actionHelper.isRemoving.toVisibility()
}
override fun getItemCount(): Int = glucoseValues.size
override fun getItemCount() = glucoseValues.size
inner class GlucoseValuesViewHolder(view: View) : RecyclerView.ViewHolder(view) {
@ -194,36 +173,7 @@ class BGSourceFragment : DaggerFragment() {
}
}
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.recyclerview.adapter?.notifyDataSetChanged()
return true
}
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?) = false
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
return when (item.itemId) {
R.id.remove_selected -> {
removeSelected()
true
}
else -> false
}
}
override fun onDestroyActionMode(mode: ActionMode?) {
removeActionMode = null
binding.recyclerview.adapter?.notifyDataSetChanged()
}
}
private fun getConfirmationText(): String {
private fun getConfirmationText(selectedItems: SparseArray<GlucoseValue>): String {
if (selectedItems.size() == 1) {
val glucoseValue = selectedItems.valueAt(0)
return dateUtil.dateAndTimeString(glucoseValue.timestamp) + "\n" + glucoseValue.valueToUnitsString(profileFunction.getUnits())
@ -231,10 +181,9 @@ class BGSourceFragment : DaggerFragment() {
return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size())
}
private fun removeSelected() {
if (selectedItems.size() > 0)
private fun removeSelected(selectedItems: SparseArray<GlucoseValue>) {
activity?.let { activity ->
OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(), Runnable {
OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(selectedItems), Runnable {
selectedItems.forEach { _, glucoseValue ->
val source = when ((activePlugin.activeBgSource as PluginBase).pluginDescription.pluginName) {
R.string.dexcom_app_patched -> Sources.Dexcom
@ -257,10 +206,8 @@ class BGSourceFragment : DaggerFragment() {
.blockingGet()
.also { result -> result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated bg $it") } }
}
removeActionMode?.finish()
actionHelper.finish()
})
}
else
removeActionMode?.finish()
}
}

View file

@ -6,6 +6,6 @@
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="@android:color/white"
android:fillColor="?attr/colorControlNormal"
android:pathData="M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z"/>
</vector>

View file

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:autoMirrored="true"
android:tint="#FFFFFF"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M3,18h6v-2L3,16v2zM3,6v2h18L21,6L3,6zM3,13h12v-2L3,11v2z" />
</vector>

View file

@ -1,10 +0,0 @@
<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_items"
android:orderInCategory="0"
android:title="@string/remove_bg_readings"
app:showAsAction="never" />
</menu>

View file

@ -4,8 +4,8 @@
<item
android:id="@+id/remove_selected"
android:icon="@drawable/ic_trash"
android:iconTint="?attr/trashBinTintColor"
android:iconTint="?attr/colorControlNormal"
android:title="@string/remove_selected_items"
app:showAsAction="always" />
app:showAsAction="ifRoom" />
</menu>

View file

@ -2,9 +2,12 @@
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".MainActivity">
<item
android:id="@+id/nav_plugin_preferences"
android:orderInCategory="1"
app:showAsAction="ifRoom"
android:icon="@drawable/ic_settings"
android:title="@string/nav_plugin_preferences" />
</menu>

View file

@ -3,13 +3,15 @@ package info.nightscout.androidaps.plugins.general.automation
import android.annotation.SuppressLint
import android.content.Context
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.text.method.ScrollingMovementMethod
import android.util.SparseArray
import android.view.*
import androidx.core.util.forEach
import android.widget.ImageView
import android.widget.LinearLayout
import androidx.annotation.DrawableRes
import androidx.core.util.forEach
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
@ -20,24 +22,24 @@ import info.nightscout.androidaps.automation.databinding.AutomationEventItemBind
import info.nightscout.androidaps.automation.databinding.AutomationFragmentBinding
import info.nightscout.androidaps.database.entities.UserEntry.Action
import info.nightscout.androidaps.database.entities.UserEntry.Sources
import info.nightscout.androidaps.extensions.toVisibility
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.utils.dragHelpers.ItemTouchHelperAdapter
import info.nightscout.androidaps.utils.dragHelpers.OnStartDragListener
import info.nightscout.androidaps.utils.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
import info.nightscout.androidaps.utils.ActionHelper
import info.nightscout.androidaps.utils.FabricPrivacy
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
import java.util.*
import io.reactivex.rxjava3.kotlin.plusAssign
import javax.inject.Inject
class AutomationFragment : DaggerFragment(), OnStartDragListener {
@ -52,11 +54,8 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
private var disposable: CompositeDisposable = CompositeDisposable()
private lateinit var eventListAdapter: EventListAdapter
private var selectedItems: SparseArray<AutomationEvent> = SparseArray()
private var removeActionMode: ActionMode? = null
private var sortActionMode: ActionMode? = null
private lateinit var actionHelper: ActionHelper<AutomationEvent>
private val itemTouchHelper = ItemTouchHelper(SimpleItemTouchHelperCallback())
private var _binding: AutomationFragmentBinding? = null
// This property is only valid between onCreateView and onDestroyView.
@ -64,20 +63,22 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = AutomationFragmentBinding.inflate(inflater, container, false)
actionHelper = ActionHelper(rh, activity)
actionHelper.setUpdateListHandler { binding.eventListView.adapter?.notifyDataSetChanged() }
actionHelper.setOnRemoveHandler { removeSelected(it) }
actionHelper.enableSort = true
setHasOptionsMenu(actionHelper.inMenu)
return binding.root
}
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()
actionHelper.finish()
val dialog = EditEventDialog()
val args = Bundle()
args.putString("event", AutomationEvent(injector).toJSON())
@ -110,8 +111,7 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
@Synchronized
override fun onPause() {
removeActionMode?.finish()
sortActionMode?.finish()
actionHelper.finish()
super.onPause()
disposable.clear()
}
@ -119,38 +119,16 @@ 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)
actionHelper.onCreateOptionsMenu(menu, inflater)
}
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
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean =
actionHelper.onOptionsItemSelected(item)
@Synchronized
private fun updateGui() {
@ -235,52 +213,74 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
}
holder.binding.aapsLogo.visibility = (automation.systemAction).toVisibility()
fun updateSelection(selected: Boolean) {
if (selected) {
selectedItems.put(position, automation)
} else {
selectedItems.remove(position)
}
removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size())
var longPress = false
val handler = Handler(Looper.getMainLooper())
val mLongPressed = Runnable {
longPress = true
actionHelper.startAction()
}
holder.binding.rootLayout.setOnTouchListener { _, touchEvent ->
if (touchEvent.actionMasked == MotionEvent.ACTION_UP && sortActionMode == null && removeActionMode == null) {
fun click() {
if (actionHelper.isNoAction && !longPress) {
val dialog = EditEventDialog()
val args = Bundle()
args.putString("event", automation.toJSON())
args.putInt("position", position)
dialog.arguments = args
dialog.show(childFragmentManager, "EditEventDialog")
} else if (actionHelper.isRemoving) {
holder.binding.cbRemove.toggle()
actionHelper.updateSelection(position, automation, holder.binding.cbRemove.isChecked)
}
if (touchEvent.actionMasked == MotionEvent.ACTION_DOWN && sortActionMode != null) {
}
// Implement click listeners and touch handler for accessibility, unfortunately drag and drop for sorting can not be made accessible
holder.binding.rootLayout.setOnClickListener {
click()
}
holder.binding.rootLayout.setOnLongClickListener {
actionHelper.startAction()
true
}
holder.binding.rootLayout.setOnTouchListener { _, touchEvent ->
when (touchEvent.actionMasked) {
MotionEvent.ACTION_UP -> {
handler.removeCallbacks(mLongPressed)
click()
longPress = false
}
MotionEvent.ACTION_DOWN -> {
if (actionHelper.isSorting) {
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())
if (actionHelper.isNoAction && !actionHelper.inMenu) {
handler.postDelayed(mLongPressed, ViewConfiguration.getLongPressTimeout().toLong())
}
}
}
return@setOnTouchListener true
}
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
holder.binding.cbRemove.isChecked = actionHelper.isSelected(position)
holder.binding.cbRemove.setOnCheckedChangeListener { _, value ->
actionHelper.updateSelection(position, automation, value)
}
holder.binding.iconSort.visibility = actionHelper.isSorting.toVisibility()
holder.binding.cbRemove.visibility = actionHelper.isRemoving.toVisibility()
holder.binding.cbRemove.isEnabled = automation.readOnly.not()
holder.binding.enabled.visibility = if (actionHelper.isRemoving) View.INVISIBLE else View.VISIBLE
}
override fun getItemCount(): Int = automationPlugin.size()
override fun getItemCount() = automationPlugin.size()
override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
binding.eventListView.adapter?.notifyItemMoved(fromPosition, toPosition)
automationPlugin.swap(fromPosition, toPosition)
return true
}
override fun onDrop() {
rxBus.send(EventAutomationDataChanged())
}
override fun onDrop() = rxBus.send(EventAutomationDataChanged())
inner class ViewHolder(view: View, val context: Context) : RecyclerView.ViewHolder(view) {
@ -288,54 +288,7 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
}
}
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
}
}
override fun onDestroyActionMode(mode: ActionMode?) {
removeActionMode = null
binding.eventListView.adapter?.notifyDataSetChanged()
}
}
private fun getConfirmationText(): String {
private fun getConfirmationText(selectedItems: SparseArray<AutomationEvent>): String {
if (selectedItems.size() == 1) {
val event = selectedItems.valueAt(0)
return rh.gs(R.string.removerecord) + " " + event.title
@ -343,20 +296,17 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size())
}
private fun removeSelected() {
if (selectedItems.size() > 0) {
private fun removeSelected(selectedItems: SparseArray<AutomationEvent>) {
activity?.let { activity ->
OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(), Runnable {
OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(selectedItems), Runnable {
selectedItems.forEach { _, event ->
uel.log(Action.AUTOMATION_REMOVED, Sources.Automation, event.title)
automationPlugin.removeAt(event.position)
rxBus.send(EventAutomationDataChanged())
}
removeActionMode?.finish()
actionHelper.finish()
})
}
} else {
removeActionMode?.finish()
}
}
}

View file

@ -1,16 +0,0 @@
<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,185 @@
package info.nightscout.androidaps.utils
import android.util.SparseArray
import android.view.ActionMode
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import androidx.fragment.app.FragmentActivity
import info.nightscout.androidaps.core.R
import info.nightscout.androidaps.utils.resources.ResourceHelper
class ActionHelper<T>(val rh: ResourceHelper, val activity: FragmentActivity?) {
var enableSort = false
private var selectedItems: SparseArray<T> = SparseArray()
private var actionMode: ActionMode? = null
private var removeActionMode: ActionMode? = null
private var sortActionMode: ActionMode? = null
private var onRemove: ((selectedItems: SparseArray<T>) -> Unit)? = null
private var onUpdate: (() -> Unit)? = null
val inMenu: Boolean
get() {
val parentClass = this.activity?.let { it::class.simpleName }
return parentClass == "SingleFragmentActivity"
}
val enableRemove: Boolean
get() = onRemove != null
val isNoAction: Boolean
get() = actionMode == null && removeActionMode == null && sortActionMode == null
val isAction: Boolean
get() = actionMode != null
val isSorting: Boolean
get() = sortActionMode != null
val isRemoving: Boolean
get() = removeActionMode != null
fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.nav_remove_items -> {
removeActionMode = activity?.startActionMode(RemoveActionModeCallback())
true
}
R.id.nav_sort_items -> {
sortActionMode = activity?.startActionMode(SortActionModeCallback())
true
}
else -> false
}
}
fun updateSelection(position: Int, item: T, selected: Boolean) {
if (selected) {
selectedItems.put(position, item)
} else {
selectedItems.remove(position)
}
removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size())
}
fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
if (inMenu) {
inflater.inflate(R.menu.menu_actions, menu)
}
}
fun onPrepareOptionsMenu(menu: Menu) {
menu.findItem(R.id.nav_remove_items)?.isVisible = enableRemove
menu.findItem(R.id.nav_sort_items)?.isVisible = enableSort
}
fun startAction() {
if (actionMode == null) {
actionMode = activity?.startActionMode(ActionModeCallback())
}
}
fun startRemove() {
if (removeActionMode == null) {
removeActionMode = activity?.startActionMode(RemoveActionModeCallback())
}
}
fun startSort() {
if (sortActionMode == null) {
sortActionMode = activity?.startActionMode(SortActionModeCallback())
}
}
fun isSelected(position: Int) =
selectedItems.get(position) != null
fun setOnRemoveHandler(onRemove: (selectedItems: SparseArray<T>) -> Unit) {
this.onRemove = onRemove
}
fun setUpdateListHandler(onUpdate: () -> Unit) {
this.onUpdate = onUpdate
}
fun finish() {
actionMode?.finish()
removeActionMode?.finish()
sortActionMode?.finish()
}
private inner class ActionModeCallback : ActionMode.Callback {
override fun onCreateActionMode(mode: ActionMode, menu: Menu?): Boolean {
mode.title = activity?.title
mode.menuInflater.inflate(R.menu.menu_actions, menu)
onUpdate?.let { it() }
return true
}
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?) = false
override fun onActionItemClicked(mode: ActionMode, item: MenuItem) =
onOptionsItemSelected(item)
override fun onDestroyActionMode(mode: ActionMode?) {
actionMode = null
}
}
private inner class SortActionModeCallback : ActionMode.Callback {
override fun onCreateActionMode(mode: ActionMode, menu: Menu?): Boolean {
mode.title = rh.gs(R.string.sort_label)
onUpdate?.let { it() }
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
onUpdate?.let { it() }
}
}
private 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())
onUpdate?.let { it() }
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 -> {
if (selectedItems.size() > 0) {
onRemove?.let { it(selectedItems) }
} else {
finish()
}
true
}
else -> false
}
}
override fun onDestroyActionMode(mode: ActionMode?) {
removeActionMode = null
onUpdate?.let { it() }
}
}
}

View file

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:autoMirrored="true"
android:tint="#FFFFFF"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M3,18h6v-2L3,16v2zM3,6v2h18L21,6L3,6zM3,13h12v-2L3,11v2z" />
</vector>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z" />
</vector>

View file

@ -3,12 +3,17 @@
<item
android:id="@+id/nav_remove_items"
android:icon="@drawable/ic_trash"
android:iconTint="?attr/colorControlNormal"
android:orderInCategory="0"
android:title="@string/remove_items"
app:showAsAction="never" />
app:showAsAction="ifRoom" />
<item
android:id="@+id/nav_sort_items"
android:icon="@drawable/ic_sort"
android:orderInCategory="0"
android:title="@string/sort_items"
app:showAsAction="never" />
app:showAsAction="ifRoom" />
</menu>

View file

@ -4,8 +4,8 @@
<item
android:id="@+id/remove_selected"
android:icon="@drawable/ic_trash"
android:iconTint="@color/white"
android:iconTint="?attr/colorControlNormal"
android:title="@string/remove_selected_items"
app:showAsAction="always" />
app:showAsAction="ifRoom" />
</menu>

View file

@ -556,6 +556,11 @@
<string name="formatPercent">%1$.0f%%</string>
<string name="basal">Basal</string>
<string name="basalpct">Basal %</string>
<string name="count_selected">%1$d selected</string>
<string name="sort_label">Sort</string>
<string name="remove_items">Remove Items</string>
<string name="sort_items">Sort Items</string>
<string name="remove_selected_items">Remove Selected Items</string>
<plurals name="days">
<item quantity="one">%1$d day</item>