Merge pull request #1511 from Andries-Smit/chore/shared-action-helper

chore: Unified experience for sort and delete in QuickWizard, BG Source, Automation and treatments
This commit is contained in:
Milos Kozak 2022-03-29 13:06:20 +02:00 committed by GitHub
commit fa2543bc7c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
45 changed files with 974 additions and 1050 deletions

View file

@ -5,9 +5,6 @@ import android.graphics.Paint
import android.os.Bundle import android.os.Bundle
import android.util.SparseArray import android.util.SparseArray
import android.view.* import android.view.*
import android.widget.CompoundButton
import android.view.ActionMode
import androidx.appcompat.widget.Toolbar
import androidx.core.util.forEach import androidx.core.util.forEach
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -38,9 +35,11 @@ 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.nsclient.events.EventNSClientRestart import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventNewHistoryData import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventNewHistoryData
import info.nightscout.androidaps.utils.ActionModeHelper
import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.T import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.ToastUtils
import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.buildHelper.BuildHelper import info.nightscout.androidaps.utils.buildHelper.BuildHelper
import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.resources.ResourceHelper
@ -71,8 +70,10 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() {
@Inject lateinit var activePlugin: ActivePlugin @Inject lateinit var activePlugin: ActivePlugin
private var _binding: TreatmentsBolusCarbsFragmentBinding? = null private var _binding: TreatmentsBolusCarbsFragmentBinding? = null
// This property is only valid between onCreateView and onDestroyView. // This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!! private val binding get() = _binding!!
private var menu: Menu? = null
class MealLink( class MealLink(
val bolus: Bolus? = null, val bolus: Bolus? = null,
@ -81,21 +82,22 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() {
) )
private val disposable = CompositeDisposable() private val disposable = CompositeDisposable()
private lateinit var actionHelper: ActionModeHelper<MealLink>
private val millsToThePast = T.days(30).msecs() private val millsToThePast = T.days(30).msecs()
private var selectedItems: SparseArray<MealLink> = SparseArray() // private var selectedItems: SparseArray<MealLink> = SparseArray()
private var showInvalidated = false private var showInvalidated = false
private var removeActionMode: ActionMode? = null
private var toolbar: Toolbar? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
TreatmentsBolusCarbsFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root TreatmentsBolusCarbsFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root
@SuppressLint("CheckResult") @SuppressLint("NotifyDataSetChanged")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
actionHelper = ActionModeHelper(rh, activity)
actionHelper.setUpdateListHandler { binding.recyclerview.adapter?.notifyDataSetChanged() }
actionHelper.setOnRemoveHandler { removeSelected(it) }
setHasOptionsMenu(true) setHasOptionsMenu(true)
toolbar = activity?.findViewById(R.id.toolbar)
binding.recyclerview.setHasFixedSize(true) binding.recyclerview.setHasFixedSize(true)
binding.recyclerview.layoutManager = LinearLayoutManager(view.context) binding.recyclerview.layoutManager = LinearLayoutManager(view.context)
} }
@ -127,7 +129,7 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() {
fun swapAdapter() { fun swapAdapter() {
val now = System.currentTimeMillis() val now = System.currentTimeMillis()
disposable += disposable +=
if (showInvalidated) if (showInvalidated)
carbsMealLinksWithInvalid(now) carbsMealLinksWithInvalid(now)
.zipWith(bolusMealLinksWithInvalid(now)) { first, second -> first + second } .zipWith(bolusMealLinksWithInvalid(now)) { first, second -> first + second }
@ -183,13 +185,13 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() {
@Synchronized @Synchronized
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
actionHelper.finish()
disposable.clear() disposable.clear()
} }
@Synchronized @Synchronized
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
removeActionMode?.finish()
binding.recyclerview.adapter = null // avoid leaks binding.recyclerview.adapter = null // avoid leaks
_binding = null _binding = null
} }
@ -243,6 +245,17 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() {
Bolus.Type.NORMAL -> rh.gs(R.string.mealbolus) Bolus.Type.NORMAL -> rh.gs(R.string.mealbolus)
Bolus.Type.PRIMING -> rh.gs(R.string.prime) Bolus.Type.PRIMING -> rh.gs(R.string.prime)
} }
holder.binding.cbBolusRemove.visibility = (ml.bolus.isValid && actionHelper.isRemoving).toVisibility()
if (actionHelper.isRemoving) {
holder.binding.cbBolusRemove.setOnCheckedChangeListener { _, value ->
actionHelper.updateSelection(position, ml, value)
}
holder.binding.root.setOnClickListener {
holder.binding.cbBolusRemove.toggle()
actionHelper.updateSelection(position, ml, holder.binding.cbBolusRemove.isChecked)
}
holder.binding.cbBolusRemove.isChecked = actionHelper.isSelected(position)
}
} }
// Carbs // Carbs
holder.binding.carbsLayout.visibility = (ml.carbs != null && (ml.carbs.isValid || showInvalidated)).toVisibility() holder.binding.carbsLayout.visibility = (ml.carbs != null && (ml.carbs.isValid || showInvalidated)).toVisibility()
@ -253,23 +266,19 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() {
holder.binding.carbsNs.visibility = (carbs.interfaceIDs.nightscoutId != null).toVisibility() holder.binding.carbsNs.visibility = (carbs.interfaceIDs.nightscoutId != null).toVisibility()
holder.binding.carbsPump.visibility = (carbs.interfaceIDs.pumpId != null).toVisibility() holder.binding.carbsPump.visibility = (carbs.interfaceIDs.pumpId != null).toVisibility()
holder.binding.carbsInvalid.visibility = carbs.isValid.not().toVisibility() holder.binding.carbsInvalid.visibility = carbs.isValid.not().toVisibility()
} holder.binding.cbCarbsRemove.visibility = (ml.carbs.isValid && actionHelper.isRemoving).toVisibility()
holder.binding.cbBolusRemove.visibility = (ml.bolus?.isValid == true && removeActionMode != null).toVisibility() if (actionHelper.isRemoving) {
holder.binding.cbCarbsRemove.visibility = (ml.carbs?.isValid == true && removeActionMode != null).toVisibility() holder.binding.cbCarbsRemove.setOnCheckedChangeListener { _, value ->
if (removeActionMode != null) { actionHelper.updateSelection(position, ml, value)
val onChange = CompoundButton.OnCheckedChangeListener { _, value ->
if (value) {
selectedItems.put(position, ml)
} else {
selectedItems.remove(position)
} }
removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size()) holder.binding.root.setOnClickListener {
holder.binding.cbBolusRemove.toggle()
actionHelper.updateSelection(position, ml, holder.binding.cbBolusRemove.isChecked)
}
holder.binding.cbCarbsRemove.isChecked = actionHelper.isSelected(position)
} }
holder.binding.cbBolusRemove.setOnCheckedChangeListener(onChange)
holder.binding.cbBolusRemove.isChecked = selectedItems.get(position) != null
holder.binding.cbCarbsRemove.setOnCheckedChangeListener(onChange)
holder.binding.cbCarbsRemove.isChecked = selectedItems.get(position) != null
} }
holder.binding.calculation.tag = ml holder.binding.calculation.tag = ml
val nextTimestamp = if (mealLinks.size != position + 1) timestamp(mealLinks[position + 1]) else 0L val nextTimestamp = if (mealLinks.size != position + 1) timestamp(mealLinks[position + 1]) else 0L
holder.binding.delimiter.visibility = dateUtil.isSameDayGroup(timestamp(ml), nextTimestamp).toVisibility() holder.binding.delimiter.visibility = dateUtil.isSameDayGroup(timestamp(ml), nextTimestamp).toVisibility()
@ -297,13 +306,18 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() {
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
this.menu = menu
inflater.inflate(R.menu.menu_treatments_carbs_bolus, menu) inflater.inflate(R.menu.menu_treatments_carbs_bolus, menu)
super.onCreateOptionsMenu(menu, inflater) super.onCreateOptionsMenu(menu, inflater)
} }
private fun updateMenuVisibility() {
menu?.findItem(R.id.nav_hide_invalidated)?.isVisible = showInvalidated
menu?.findItem(R.id.nav_show_invalidated)?.isVisible = !showInvalidated
}
override fun onPrepareOptionsMenu(menu: Menu) { override fun onPrepareOptionsMenu(menu: Menu) {
menu.findItem(R.id.nav_hide_invalidated)?.isVisible = showInvalidated updateMenuVisibility()
menu.findItem(R.id.nav_show_invalidated)?.isVisible = !showInvalidated
val nsUploadOnly = !sp.getBoolean(R.string.key_ns_receive_insulin, false) || !sp.getBoolean(R.string.key_ns_receive_carbs, false) || !buildHelper.isEngineeringMode() val nsUploadOnly = !sp.getBoolean(R.string.key_ns_receive_insulin, false) || !sp.getBoolean(R.string.key_ns_receive_carbs, false) || !buildHelper.isEngineeringMode()
menu.findItem(R.id.nav_refresh_ns)?.isVisible = !nsUploadOnly menu.findItem(R.id.nav_refresh_ns)?.isVisible = !nsUploadOnly
val hasItems = (binding.recyclerview.adapter?.itemCount ?: 0) > 0 val hasItems = (binding.recyclerview.adapter?.itemCount ?: 0) > 0
@ -314,19 +328,20 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() {
override fun onOptionsItemSelected(item: MenuItem): Boolean = override fun onOptionsItemSelected(item: MenuItem): Boolean =
when (item.itemId) { when (item.itemId) {
R.id.nav_remove_items -> { R.id.nav_remove_items -> actionHelper.startRemove()
removeActionMode = toolbar?.startActionMode(RemoveActionModeCallback())
true
}
R.id.nav_show_invalidated -> { R.id.nav_show_invalidated -> {
showInvalidated = true showInvalidated = true
updateMenuVisibility()
ToastUtils.showToastInUiThread(context, rh.gs(R.string.show_invalidated_records))
rxBus.send(EventTreatmentUpdateGui()) rxBus.send(EventTreatmentUpdateGui())
true true
} }
R.id.nav_hide_invalidated -> { R.id.nav_hide_invalidated -> {
showInvalidated = false showInvalidated = false
updateMenuVisibility()
ToastUtils.showToastInUiThread(context, rh.gs(R.string.hide_invalidated_records))
rxBus.send(EventTreatmentUpdateGui()) rxBus.send(EventTreatmentUpdateGui())
true true
} }
@ -423,36 +438,7 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() {
} }
} }
inner class RemoveActionModeCallback : ActionMode.Callback { private fun getConfirmationText(selectedItems: SparseArray<MealLink>): String {
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 {
if (selectedItems.size() == 1) { if (selectedItems.size() == 1) {
val mealLink = selectedItems.valueAt(0) val mealLink = selectedItems.valueAt(0)
val bolus = mealLink.bolus val bolus = mealLink.bolus
@ -467,40 +453,38 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() {
return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size()) return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size())
} }
fun removeSelected() { private fun removeSelected(selectedItems: SparseArray<MealLink>) {
if (selectedItems.size() > 0) activity?.let { activity ->
activity?.let { activity -> OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(selectedItems), Runnable {
OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(), Runnable { selectedItems.forEach { _, ml ->
selectedItems.forEach { _, ml -> ml.bolus?.let { bolus ->
ml.bolus?.let { bolus -> uel.log(
uel.log( Action.BOLUS_REMOVED, Sources.Treatments,
Action.BOLUS_REMOVED, Sources.Treatments, ValueWithUnit.Timestamp(bolus.timestamp),
ValueWithUnit.Timestamp(bolus.timestamp), ValueWithUnit.Insulin(bolus.amount)
ValueWithUnit.Insulin(bolus.amount) )
disposable += repository.runTransactionForResult(InvalidateBolusTransaction(bolus.id))
.subscribe(
{ result -> result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated bolus $it") } },
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating bolus", it) }
) )
disposable += repository.runTransactionForResult(InvalidateBolusTransaction(bolus.id))
.subscribe(
{ result -> result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated bolus $it") } },
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating bolus", it) }
)
}
ml.carbs?.let { carb ->
uel.log(
Action.CARBS_REMOVED, Sources.Treatments,
ValueWithUnit.Timestamp(carb.timestamp),
ValueWithUnit.Gram(carb.amount.toInt())
)
disposable += repository.runTransactionForResult(InvalidateCarbsTransaction(carb.id))
.subscribe(
{ result -> result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated carbs $it") } },
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating carbs", it) }
)
}
} }
removeActionMode?.finish() ml.carbs?.let { carb ->
}) uel.log(
} Action.CARBS_REMOVED, Sources.Treatments,
else ValueWithUnit.Timestamp(carb.timestamp),
removeActionMode?.finish() ValueWithUnit.Gram(carb.amount.toInt())
)
disposable += repository.runTransactionForResult(InvalidateCarbsTransaction(carb.id))
.subscribe(
{ result -> result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated carbs $it") } },
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating carbs", it) }
)
}
}
actionHelper.finish()
})
}
} }
} }

View file

@ -3,39 +3,34 @@ package info.nightscout.androidaps.activities.fragments
import android.os.Bundle import android.os.Bundle
import android.util.SparseArray import android.util.SparseArray
import android.view.* import android.view.*
import android.view.ActionMode
import androidx.appcompat.widget.Toolbar
import androidx.core.util.forEach import androidx.core.util.forEach
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import dagger.android.support.DaggerFragment import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.activities.fragments.TreatmentsCareportalFragment.RecyclerViewAdapter.TherapyEventsViewHolder
import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.entities.ValueWithUnit
import info.nightscout.androidaps.database.entities.TherapyEvent import info.nightscout.androidaps.database.entities.TherapyEvent
import info.nightscout.androidaps.database.entities.UserEntry.Action import info.nightscout.androidaps.database.entities.UserEntry.Action
import info.nightscout.androidaps.database.entities.UserEntry.Sources import info.nightscout.androidaps.database.entities.UserEntry.Sources
import info.nightscout.androidaps.database.entities.ValueWithUnit
import info.nightscout.androidaps.database.transactions.InvalidateAAPSStartedTherapyEventTransaction import info.nightscout.androidaps.database.transactions.InvalidateAAPSStartedTherapyEventTransaction
import info.nightscout.androidaps.database.transactions.InvalidateTherapyEventTransaction import info.nightscout.androidaps.database.transactions.InvalidateTherapyEventTransaction
import info.nightscout.androidaps.databinding.TreatmentsCareportalFragmentBinding import info.nightscout.androidaps.databinding.TreatmentsCareportalFragmentBinding
import info.nightscout.androidaps.databinding.TreatmentsCareportalItemBinding import info.nightscout.androidaps.databinding.TreatmentsCareportalItemBinding
import info.nightscout.androidaps.events.EventTherapyEventChange import info.nightscout.androidaps.events.EventTherapyEventChange
import info.nightscout.shared.logging.AAPSLogger import info.nightscout.androidaps.events.EventTreatmentUpdateGui
import info.nightscout.shared.logging.LTag import info.nightscout.androidaps.extensions.toVisibility
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.nsclient.events.EventNSClientRestart import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart
import info.nightscout.androidaps.events.EventTreatmentUpdateGui import info.nightscout.androidaps.utils.*
import info.nightscout.androidaps.activities.fragments.TreatmentsCareportalFragment.RecyclerViewAdapter.TherapyEventsViewHolder
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.Translator
import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.buildHelper.BuildHelper import info.nightscout.androidaps.utils.buildHelper.BuildHelper
import info.nightscout.androidaps.extensions.toVisibility
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 info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP import info.nightscout.shared.sharedPreferences.SP
import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.disposables.CompositeDisposable
@ -59,23 +54,23 @@ class TreatmentsCareportalFragment : DaggerFragment() {
@Inject lateinit var uel: UserEntryLogger @Inject lateinit var uel: UserEntryLogger
private var _binding: TreatmentsCareportalFragmentBinding? = null private var _binding: TreatmentsCareportalFragmentBinding? = null
// This property is only valid between onCreateView and onDestroyView. // This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!! private val binding get() = _binding!!
private var menu: Menu? = null
private val disposable = CompositeDisposable() private val disposable = CompositeDisposable()
private val millsToThePast = T.days(30).msecs() private val millsToThePast = T.days(30).msecs()
private var selectedItems: SparseArray<TherapyEvent> = SparseArray() private lateinit var actionHelper: ActionModeHelper<TherapyEvent>
private var showInvalidated = false private var showInvalidated = false
private var toolbar: Toolbar? = null
private var removeActionMode: ActionMode? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
TreatmentsCareportalFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root TreatmentsCareportalFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
toolbar = activity?.findViewById(R.id.toolbar) actionHelper = ActionModeHelper(rh, activity)
actionHelper.setUpdateListHandler { binding.recyclerview.adapter?.notifyDataSetChanged() }
actionHelper.setOnRemoveHandler { removeSelected(it) }
setHasOptionsMenu(true) setHasOptionsMenu(true)
binding.recyclerview.setHasFixedSize(true) binding.recyclerview.setHasFixedSize(true)
binding.recyclerview.layoutManager = LinearLayoutManager(view.context) binding.recyclerview.layoutManager = LinearLayoutManager(view.context)
@ -143,13 +138,13 @@ class TreatmentsCareportalFragment : DaggerFragment() {
@Synchronized @Synchronized
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
actionHelper.finish()
disposable.clear() disposable.clear()
} }
@Synchronized @Synchronized
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
removeActionMode?.finish()
binding.recyclerview.adapter = null // avoid leaks binding.recyclerview.adapter = null // avoid leaks
_binding = null _binding = null
} }
@ -172,18 +167,15 @@ class TreatmentsCareportalFragment : DaggerFragment() {
holder.binding.duration.text = if (therapyEvent.duration == 0L) "" else dateUtil.niceTimeScalar(therapyEvent.duration, rh) holder.binding.duration.text = if (therapyEvent.duration == 0L) "" else dateUtil.niceTimeScalar(therapyEvent.duration, rh)
holder.binding.note.text = therapyEvent.note holder.binding.note.text = therapyEvent.note
holder.binding.type.text = translator.translate(therapyEvent.type) holder.binding.type.text = translator.translate(therapyEvent.type)
holder.binding.cbRemove.visibility = (therapyEvent.isValid && removeActionMode != null).toVisibility() holder.binding.cbRemove.visibility = (therapyEvent.isValid && actionHelper.isRemoving).toVisibility()
if (removeActionMode != null) { holder.binding.cbRemove.setOnCheckedChangeListener { _, value ->
holder.binding.cbRemove.setOnCheckedChangeListener { _, value -> actionHelper.updateSelection(position, therapyEvent, value)
if (value) {
selectedItems.put(position, therapyEvent)
} else {
selectedItems.remove(position)
}
removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size())
}
holder.binding.cbRemove.isChecked = selectedItems.get(position) != null
} }
holder.binding.root.setOnClickListener {
holder.binding.cbRemove.toggle()
actionHelper.updateSelection(position, therapyEvent, holder.binding.cbRemove.isChecked)
}
holder.binding.cbRemove.isChecked = actionHelper.isSelected(position)
val nextTimestamp = if (therapyList.size != position + 1) therapyList[position + 1].timestamp else 0L val nextTimestamp = if (therapyList.size != position + 1) therapyList[position + 1].timestamp else 0L
holder.binding.delimiter.visibility = dateUtil.isSameDayGroup(therapyEvent.timestamp, nextTimestamp).toVisibility() holder.binding.delimiter.visibility = dateUtil.isSameDayGroup(therapyEvent.timestamp, nextTimestamp).toVisibility()
} }
@ -198,34 +190,40 @@ class TreatmentsCareportalFragment : DaggerFragment() {
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
this.menu = menu
inflater.inflate(R.menu.menu_treatments_careportal, menu) inflater.inflate(R.menu.menu_treatments_careportal, menu)
super.onCreateOptionsMenu(menu, inflater) super.onCreateOptionsMenu(menu, inflater)
} }
override fun onPrepareOptionsMenu(menu: Menu) { override fun onPrepareOptionsMenu(menu: Menu) {
menu.findItem(R.id.nav_hide_invalidated)?.isVisible = showInvalidated updateMenuVisibility()
menu.findItem(R.id.nav_show_invalidated)?.isVisible = !showInvalidated
val nsUploadOnly = !sp.getBoolean(R.string.key_ns_receive_therapy_events, false) || !buildHelper.isEngineeringMode() val nsUploadOnly = !sp.getBoolean(R.string.key_ns_receive_therapy_events, false) || !buildHelper.isEngineeringMode()
menu.findItem(R.id.nav_refresh_ns)?.isVisible = !nsUploadOnly menu.findItem(R.id.nav_refresh_ns)?.isVisible = !nsUploadOnly
return super.onPrepareOptionsMenu(menu) return super.onPrepareOptionsMenu(menu)
} }
private fun updateMenuVisibility() {
menu?.findItem(R.id.nav_hide_invalidated)?.isVisible = showInvalidated
menu?.findItem(R.id.nav_show_invalidated)?.isVisible = !showInvalidated
}
override fun onOptionsItemSelected(item: MenuItem): Boolean = override fun onOptionsItemSelected(item: MenuItem): Boolean =
when (item.itemId) { when (item.itemId) {
R.id.nav_remove_items -> { R.id.nav_remove_items -> actionHelper.startRemove()
removeActionMode = toolbar?.startActionMode(RemoveActionModeCallback())
true
}
R.id.nav_show_invalidated -> { R.id.nav_show_invalidated -> {
showInvalidated = true showInvalidated = true
updateMenuVisibility()
ToastUtils.showToastInUiThread(context, rh.gs(R.string.show_invalidated_records))
rxBus.send(EventTreatmentUpdateGui()) rxBus.send(EventTreatmentUpdateGui())
true true
} }
R.id.nav_hide_invalidated -> { R.id.nav_hide_invalidated -> {
showInvalidated = false showInvalidated = false
updateMenuVisibility()
ToastUtils.showToastInUiThread(context, rh.gs(R.string.hide_invalidated_records))
rxBus.send(EventTreatmentUpdateGui()) rxBus.send(EventTreatmentUpdateGui())
true true
} }
@ -243,36 +241,7 @@ class TreatmentsCareportalFragment : DaggerFragment() {
else -> false else -> false
} }
inner class RemoveActionModeCallback : ActionMode.Callback { private fun getConfirmationText(selectedItems: SparseArray<TherapyEvent>): String {
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 {
if (selectedItems.size() == 1) { if (selectedItems.size() == 1) {
val therapyEvent = selectedItems.valueAt(0) val therapyEvent = selectedItems.valueAt(0)
return rh.gs(R.string.eventtype) + ": " + translator.translate(therapyEvent.type) + "\n" + return rh.gs(R.string.eventtype) + ": " + translator.translate(therapyEvent.type) + "\n" +
@ -282,27 +251,24 @@ class TreatmentsCareportalFragment : DaggerFragment() {
return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size()) return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size())
} }
private fun removeSelected() { private fun removeSelected(selectedItems: SparseArray<TherapyEvent>) {
if (selectedItems.size() > 0) activity?.let { activity ->
activity?.let { activity -> OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(selectedItems), Runnable {
OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(), Runnable { selectedItems.forEach { _, therapyEvent ->
selectedItems.forEach { _, therapyEvent -> uel.log(
uel.log( Action.CAREPORTAL_REMOVED, Sources.Treatments, therapyEvent.note,
Action.CAREPORTAL_REMOVED, Sources.Treatments, therapyEvent.note, ValueWithUnit.Timestamp(therapyEvent.timestamp),
ValueWithUnit.Timestamp(therapyEvent.timestamp), ValueWithUnit.TherapyEventType(therapyEvent.type)
ValueWithUnit.TherapyEventType(therapyEvent.type) )
disposable += repository.runTransactionForResult(InvalidateTherapyEventTransaction(therapyEvent.id))
.subscribe(
{ result -> result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated therapy event $it") } },
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating therapy event", it) }
) )
disposable += repository.runTransactionForResult(InvalidateTherapyEventTransaction(therapyEvent.id)) }
.subscribe( actionHelper.finish()
{ result -> result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated therapy event $it") } }, })
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating therapy event", it) } }
)
}
removeActionMode?.finish()
})
}
else
removeActionMode?.finish()
} }
} }

View file

@ -1,15 +1,15 @@
package info.nightscout.androidaps.activities.fragments package info.nightscout.androidaps.activities.fragments
import android.annotation.SuppressLint
import android.os.Bundle import android.os.Bundle
import android.util.SparseArray import android.util.SparseArray
import android.view.* import android.view.*
import android.view.ActionMode
import androidx.appcompat.widget.Toolbar
import androidx.core.util.forEach import androidx.core.util.forEach
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import dagger.android.support.DaggerFragment import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.activities.fragments.TreatmentsExtendedBolusesFragment.RecyclerViewAdapter.ExtendedBolusesViewHolder
import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.entities.ExtendedBolus import info.nightscout.androidaps.database.entities.ExtendedBolus
import info.nightscout.androidaps.database.entities.UserEntry.Action import info.nightscout.androidaps.database.entities.UserEntry.Action
@ -20,25 +20,26 @@ import info.nightscout.androidaps.database.transactions.InvalidateExtendedBolusT
import info.nightscout.androidaps.databinding.TreatmentsExtendedbolusFragmentBinding import info.nightscout.androidaps.databinding.TreatmentsExtendedbolusFragmentBinding
import info.nightscout.androidaps.databinding.TreatmentsExtendedbolusItemBinding import info.nightscout.androidaps.databinding.TreatmentsExtendedbolusItemBinding
import info.nightscout.androidaps.events.EventExtendedBolusChange import info.nightscout.androidaps.events.EventExtendedBolusChange
import info.nightscout.androidaps.events.EventTreatmentUpdateGui
import info.nightscout.androidaps.extensions.iobCalc import info.nightscout.androidaps.extensions.iobCalc
import info.nightscout.androidaps.extensions.isInProgress import info.nightscout.androidaps.extensions.isInProgress
import info.nightscout.androidaps.extensions.toVisibility import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.interfaces.ActivePlugin import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.ProfileFunction import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.shared.logging.AAPSLogger
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.activities.fragments.TreatmentsExtendedBolusesFragment.RecyclerViewAdapter.ExtendedBolusesViewHolder import info.nightscout.androidaps.utils.ActionModeHelper
import info.nightscout.androidaps.events.EventTreatmentUpdateGui
import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.T import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.ToastUtils
import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.utils.alertDialogs.OKDialog
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 info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign import io.reactivex.rxjava3.kotlin.plusAssign
import info.nightscout.shared.logging.LTag
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
@ -60,20 +61,23 @@ class TreatmentsExtendedBolusesFragment : DaggerFragment() {
@Inject lateinit var repository: AppRepository @Inject lateinit var repository: AppRepository
private var _binding: TreatmentsExtendedbolusFragmentBinding? = null private var _binding: TreatmentsExtendedbolusFragmentBinding? = null
// This property is only valid between onCreateView and onDestroyView. // This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!! private val binding get() = _binding!!
private var menu: Menu? = null
private var selectedItems: SparseArray<ExtendedBolus> = SparseArray() private lateinit var actionHelper: ActionModeHelper<ExtendedBolus>
private var showInvalidated = false private var showInvalidated = false
private var removeActionMode: ActionMode? = null
private var toolbar: Toolbar? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
TreatmentsExtendedbolusFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root TreatmentsExtendedbolusFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root
@SuppressLint("NotifyDataSetChanged")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
actionHelper = ActionModeHelper(rh, activity)
actionHelper.setUpdateListHandler { binding.recyclerview.adapter?.notifyDataSetChanged() }
actionHelper.setOnRemoveHandler { removeSelected(it) }
setHasOptionsMenu(true) setHasOptionsMenu(true)
toolbar = activity?.findViewById(R.id.toolbar)
binding.recyclerview.setHasFixedSize(true) binding.recyclerview.setHasFixedSize(true)
binding.recyclerview.layoutManager = LinearLayoutManager(view.context) binding.recyclerview.layoutManager = LinearLayoutManager(view.context)
} }
@ -96,24 +100,28 @@ class TreatmentsExtendedBolusesFragment : DaggerFragment() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
swapAdapter() swapAdapter()
disposable += rxBus disposable += rxBus
.toObservable(EventExtendedBolusChange::class.java) .toObservable(EventExtendedBolusChange::class.java)
.observeOn(aapsSchedulers.io) .observeOn(aapsSchedulers.io)
.debounce(1L, TimeUnit.SECONDS) .debounce(1L, TimeUnit.SECONDS)
.subscribe({ swapAdapter() }, fabricPrivacy::logException) .subscribe({ swapAdapter() }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventTreatmentUpdateGui::class.java) // TODO join with above
.observeOn(aapsSchedulers.io)
.debounce(1L, TimeUnit.SECONDS)
.subscribe({ swapAdapter() }, fabricPrivacy::logException)
} }
@Synchronized @Synchronized
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
actionHelper.finish()
disposable.clear() disposable.clear()
} }
@Synchronized @Synchronized
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
removeActionMode?.finish()
binding.recyclerview.adapter = null // avoid leaks binding.recyclerview.adapter = null // avoid leaks
_binding = null _binding = null
} }
@ -147,17 +155,16 @@ class TreatmentsExtendedBolusesFragment : DaggerFragment() {
holder.binding.iob.text = rh.gs(R.string.formatinsulinunits, iob.iob) holder.binding.iob.text = rh.gs(R.string.formatinsulinunits, iob.iob)
holder.binding.ratio.text = rh.gs(R.string.pump_basebasalrate, extendedBolus.rate) holder.binding.ratio.text = rh.gs(R.string.pump_basebasalrate, extendedBolus.rate)
if (iob.iob != 0.0) holder.binding.iob.setTextColor(rh.gac(context , R.attr.activeColor)) else holder.binding.iob.setTextColor(holder.binding.insulin.currentTextColor) if (iob.iob != 0.0) holder.binding.iob.setTextColor(rh.gac(context , R.attr.activeColor)) else holder.binding.iob.setTextColor(holder.binding.insulin.currentTextColor)
holder.binding.cbRemove.visibility = (extendedBolus.isValid && removeActionMode != null).toVisibility() holder.binding.cbRemove.visibility = (extendedBolus.isValid && actionHelper.isRemoving).toVisibility()
if (removeActionMode != null) { if (actionHelper.isRemoving) {
holder.binding.cbRemove.setOnCheckedChangeListener { _, value -> holder.binding.cbRemove.setOnCheckedChangeListener { _, value ->
if (value) { actionHelper.updateSelection(position, extendedBolus, value)
selectedItems.put(position, extendedBolus)
} else {
selectedItems.remove(position)
}
removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size())
} }
holder.binding.cbRemove.isChecked = selectedItems.get(position) != null holder.binding.root.setOnClickListener {
holder.binding.cbRemove.toggle()
actionHelper.updateSelection(position, extendedBolus, holder.binding.cbRemove.isChecked)
}
holder.binding.cbRemove.isChecked = actionHelper.isSelected(position)
} }
val nextTimestamp = if (extendedBolusList.size != position + 1) extendedBolusList[position + 1].timestamp else 0L val nextTimestamp = if (extendedBolusList.size != position + 1) extendedBolusList[position + 1].timestamp else 0L
holder.binding.delimiter.visibility = dateUtil.isSameDayGroup(extendedBolus.timestamp, nextTimestamp).toVisibility() holder.binding.delimiter.visibility = dateUtil.isSameDayGroup(extendedBolus.timestamp, nextTimestamp).toVisibility()
@ -173,32 +180,37 @@ class TreatmentsExtendedBolusesFragment : DaggerFragment() {
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
this.menu = menu
inflater.inflate(R.menu.menu_treatments_extended_bolus, menu) inflater.inflate(R.menu.menu_treatments_extended_bolus, menu)
super.onCreateOptionsMenu(menu, inflater) super.onCreateOptionsMenu(menu, inflater)
} }
override fun onPrepareOptionsMenu(menu: Menu) { private fun updateMenuVisibility() {
menu.findItem(R.id.nav_hide_invalidated)?.isVisible = showInvalidated menu?.findItem(R.id.nav_hide_invalidated)?.isVisible = showInvalidated
menu.findItem(R.id.nav_show_invalidated)?.isVisible = !showInvalidated menu?.findItem(R.id.nav_show_invalidated)?.isVisible = !showInvalidated
}
override fun onPrepareOptionsMenu(menu: Menu) {
updateMenuVisibility()
return super.onPrepareOptionsMenu(menu) return super.onPrepareOptionsMenu(menu)
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) { return when (item.itemId) {
R.id.nav_remove_items -> { R.id.nav_remove_items -> actionHelper.startRemove()
removeActionMode = toolbar?.startActionMode(RemoveActionModeCallback())
true
}
R.id.nav_show_invalidated -> { R.id.nav_show_invalidated -> {
showInvalidated = true showInvalidated = true
updateMenuVisibility()
ToastUtils.showToastInUiThread(context, rh.gs(R.string.show_invalidated_records))
rxBus.send(EventTreatmentUpdateGui()) rxBus.send(EventTreatmentUpdateGui())
true true
} }
R.id.nav_hide_invalidated -> { R.id.nav_hide_invalidated -> {
showInvalidated = false showInvalidated = false
updateMenuVisibility()
ToastUtils.showToastInUiThread(context, rh.gs(R.string.hide_invalidated_records))
rxBus.send(EventTreatmentUpdateGui()) rxBus.send(EventTreatmentUpdateGui())
true true
} }
@ -207,36 +219,7 @@ class TreatmentsExtendedBolusesFragment : DaggerFragment() {
} }
} }
inner class RemoveActionModeCallback : ActionMode.Callback { private fun getConfirmationText(selectedItems: SparseArray<ExtendedBolus>): String {
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 {
if (selectedItems.size() == 1) { if (selectedItems.size() == 1) {
val bolus = selectedItems.valueAt(0) val bolus = selectedItems.valueAt(0)
return rh.gs(R.string.extended_bolus) + "\n" + return rh.gs(R.string.extended_bolus) + "\n" +
@ -245,27 +228,25 @@ class TreatmentsExtendedBolusesFragment : DaggerFragment() {
return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size()) return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size())
} }
private fun removeSelected() { private fun removeSelected(selectedItems: SparseArray<ExtendedBolus>) {
if (selectedItems.size() > 0) activity?.let { activity ->
activity?.let { activity -> OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(selectedItems), Runnable {
OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(), Runnable { selectedItems.forEach { _, extendedBolus ->
selectedItems.forEach { _, extendedBolus -> uel.log(
uel.log( Action.EXTENDED_BOLUS_REMOVED, Sources.Treatments,
Action.EXTENDED_BOLUS_REMOVED, Sources.Treatments, ValueWithUnit.Timestamp(extendedBolus.timestamp),
ValueWithUnit.Timestamp(extendedBolus.timestamp), ValueWithUnit.Insulin(extendedBolus.amount),
ValueWithUnit.Insulin(extendedBolus.amount), ValueWithUnit.UnitPerHour(extendedBolus.rate),
ValueWithUnit.UnitPerHour(extendedBolus.rate), ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(extendedBolus.duration).toInt())
ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(extendedBolus.duration).toInt()) )
) disposable += repository.runTransactionForResult(InvalidateExtendedBolusTransaction(extendedBolus.id))
disposable += repository.runTransactionForResult(InvalidateExtendedBolusTransaction(extendedBolus.id)) .subscribe(
.subscribe( { aapsLogger.debug(LTag.DATABASE, "Removed extended bolus $extendedBolus") },
{ aapsLogger.debug(LTag.DATABASE, "Removed extended bolus $extendedBolus") }, { aapsLogger.error(LTag.DATABASE, "Error while invalidating extended bolus", it) })
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating extended bolus", it) }) }
} actionHelper.finish()
removeActionMode?.finish() })
}) }
}
else
removeActionMode?.finish()
} }
} }

View file

@ -1,11 +1,10 @@
package info.nightscout.androidaps.activities.fragments package info.nightscout.androidaps.activities.fragments
import android.annotation.SuppressLint
import android.graphics.Paint import android.graphics.Paint
import android.os.Bundle import android.os.Bundle
import android.util.SparseArray import android.util.SparseArray
import android.view.* import android.view.*
import android.view.ActionMode
import androidx.appcompat.widget.Toolbar
import androidx.core.util.forEach import androidx.core.util.forEach
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -32,9 +31,11 @@ import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientR
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventNewHistoryData import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventNewHistoryData
import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin
import info.nightscout.androidaps.plugins.profile.local.events.EventLocalProfileChanged import info.nightscout.androidaps.plugins.profile.local.events.EventLocalProfileChanged
import info.nightscout.androidaps.utils.ActionModeHelper
import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.T import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.ToastUtils
import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.buildHelper.BuildHelper import info.nightscout.androidaps.utils.buildHelper.BuildHelper
import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.resources.ResourceHelper
@ -46,6 +47,7 @@ import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign import io.reactivex.rxjava3.kotlin.plusAssign
import io.reactivex.rxjava3.kotlin.subscribeBy import io.reactivex.rxjava3.kotlin.subscribeBy
import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
class TreatmentsProfileSwitchFragment : DaggerFragment() { class TreatmentsProfileSwitchFragment : DaggerFragment() {
@ -63,24 +65,25 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() {
@Inject lateinit var uel: UserEntryLogger @Inject lateinit var uel: UserEntryLogger
private var _binding: TreatmentsProfileswitchFragmentBinding? = null private var _binding: TreatmentsProfileswitchFragmentBinding? = null
// This property is only valid between onCreateView and onDestroyView. // This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!! private val binding get() = _binding!!
private var menu: Menu? = null
private lateinit var actionHelper: ActionModeHelper<ProfileSealed>
private val disposable = CompositeDisposable() private val disposable = CompositeDisposable()
private val millsToThePast = T.days(30).msecs() private val millsToThePast = T.days(30).msecs()
private var selectedItems: SparseArray<ProfileSealed> = SparseArray()
private var showInvalidated = false private var showInvalidated = false
private var removeActionMode: ActionMode? = null
private var toolbar: Toolbar? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
TreatmentsProfileswitchFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root TreatmentsProfileswitchFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root
@SuppressLint("NotifyDataSetChanged")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
actionHelper = ActionModeHelper(rh, activity)
actionHelper.setUpdateListHandler { binding.recyclerview.adapter?.notifyDataSetChanged() }
actionHelper.setOnRemoveHandler { removeSelected(it) }
setHasOptionsMenu(true) setHasOptionsMenu(true)
toolbar = activity?.findViewById(R.id.toolbar)
binding.recyclerview.setHasFixedSize(true) binding.recyclerview.setHasFixedSize(true)
binding.recyclerview.layoutManager = LinearLayoutManager(view.context) binding.recyclerview.layoutManager = LinearLayoutManager(view.context)
} }
@ -159,18 +162,23 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() {
.toObservable(EventEffectiveProfileSwitchChanged::class.java) .toObservable(EventEffectiveProfileSwitchChanged::class.java)
.observeOn(aapsSchedulers.main) .observeOn(aapsSchedulers.main)
.subscribe({ swapAdapter() }, fabricPrivacy::logException) .subscribe({ swapAdapter() }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventTreatmentUpdateGui::class.java) // TODO join with above
.observeOn(aapsSchedulers.io)
.debounce(1L, TimeUnit.SECONDS)
.subscribe({ swapAdapter() }, fabricPrivacy::logException)
} }
@Synchronized @Synchronized
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
actionHelper.finish()
disposable.clear() disposable.clear()
} }
@Synchronized @Synchronized
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
removeActionMode?.finish()
binding.recyclerview.adapter = null // avoid leaks binding.recyclerview.adapter = null // avoid leaks
_binding = null _binding = null
} }
@ -198,17 +206,16 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() {
holder.binding.date.tag = profileSwitch holder.binding.date.tag = profileSwitch
holder.binding.invalid.visibility = profileSwitch.isValid.not().toVisibility() holder.binding.invalid.visibility = profileSwitch.isValid.not().toVisibility()
holder.binding.duration.visibility = (profileSwitch.duration != 0L && profileSwitch.duration != null).toVisibility() holder.binding.duration.visibility = (profileSwitch.duration != 0L && profileSwitch.duration != null).toVisibility()
holder.binding.cbRemove.visibility = (removeActionMode != null && profileSwitch is ProfileSealed.PS).toVisibility() holder.binding.cbRemove.visibility = (actionHelper.isRemoving && profileSwitch is ProfileSealed.PS).toVisibility()
if (removeActionMode != null) { if (actionHelper.isRemoving) {
holder.binding.cbRemove.setOnCheckedChangeListener { _, value -> holder.binding.cbRemove.setOnCheckedChangeListener { _, value ->
if (value) { actionHelper.updateSelection(position, profileSwitch, value)
selectedItems.put(position, profileSwitch)
} else {
selectedItems.remove(position)
}
removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size())
} }
holder.binding.cbRemove.isChecked = selectedItems.get(position) != null holder.binding.root.setOnClickListener {
holder.binding.cbRemove.toggle()
actionHelper.updateSelection(position, profileSwitch, holder.binding.cbRemove.isChecked)
}
holder.binding.cbRemove.isChecked = actionHelper.isSelected(position)
} }
holder.binding.clone.visibility = (profileSwitch is ProfileSealed.PS).toVisibility() holder.binding.clone.visibility = (profileSwitch is ProfileSealed.PS).toVisibility()
holder.binding.spacer.visibility = (profileSwitch is ProfileSealed.PS).toVisibility() holder.binding.spacer.visibility = (profileSwitch is ProfileSealed.PS).toVisibility()
@ -273,13 +280,18 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() {
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
this.menu = menu
inflater.inflate(R.menu.menu_treatments_profile_switch, menu) inflater.inflate(R.menu.menu_treatments_profile_switch, menu)
super.onCreateOptionsMenu(menu, inflater) super.onCreateOptionsMenu(menu, inflater)
} }
private fun updateMenuVisibility() {
menu?.findItem(R.id.nav_hide_invalidated)?.isVisible = showInvalidated
menu?.findItem(R.id.nav_show_invalidated)?.isVisible = !showInvalidated
}
override fun onPrepareOptionsMenu(menu: Menu) { override fun onPrepareOptionsMenu(menu: Menu) {
menu.findItem(R.id.nav_hide_invalidated)?.isVisible = showInvalidated updateMenuVisibility()
menu.findItem(R.id.nav_show_invalidated)?.isVisible = !showInvalidated
val nsUploadOnly = !sp.getBoolean(R.string.key_ns_receive_profile_switch, false) || !buildHelper.isEngineeringMode() val nsUploadOnly = !sp.getBoolean(R.string.key_ns_receive_profile_switch, false) || !buildHelper.isEngineeringMode()
menu.findItem(R.id.nav_refresh_ns)?.isVisible = !nsUploadOnly menu.findItem(R.id.nav_refresh_ns)?.isVisible = !nsUploadOnly
@ -288,19 +300,20 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() {
override fun onOptionsItemSelected(item: MenuItem): Boolean = override fun onOptionsItemSelected(item: MenuItem): Boolean =
when (item.itemId) { when (item.itemId) {
R.id.nav_remove_items -> { R.id.nav_remove_items -> actionHelper.startRemove()
removeActionMode = toolbar?.startActionMode(RemoveActionModeCallback())
true
}
R.id.nav_show_invalidated -> { R.id.nav_show_invalidated -> {
showInvalidated = true showInvalidated = true
updateMenuVisibility()
ToastUtils.showToastInUiThread(context, rh.gs(R.string.show_invalidated_records))
rxBus.send(EventTreatmentUpdateGui()) rxBus.send(EventTreatmentUpdateGui())
true true
} }
R.id.nav_hide_invalidated -> { R.id.nav_hide_invalidated -> {
showInvalidated = false showInvalidated = false
updateMenuVisibility()
ToastUtils.showToastInUiThread(context, rh.gs(R.string.hide_invalidated_records))
rxBus.send(EventTreatmentUpdateGui()) rxBus.send(EventTreatmentUpdateGui())
true true
} }
@ -313,36 +326,7 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() {
else -> false else -> false
} }
inner class RemoveActionModeCallback : ActionMode.Callback { private fun getConfirmationText(selectedItems: SparseArray<ProfileSealed>): String {
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 {
if (selectedItems.size() == 1) { if (selectedItems.size() == 1) {
val profileSwitch = selectedItems.valueAt(0) val profileSwitch = selectedItems.valueAt(0)
return rh.gs(R.string.careportal_profileswitch) + ": " + profileSwitch.profileName + "\n" + rh.gs(R.string.date) + ": " + dateUtil.dateAndTimeString(profileSwitch.timestamp) return rh.gs(R.string.careportal_profileswitch) + ": " + profileSwitch.profileName + "\n" + rh.gs(R.string.date) + ": " + dateUtil.dateAndTimeString(profileSwitch.timestamp)
@ -350,25 +334,22 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() {
return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size()) return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size())
} }
private fun removeSelected() { private fun removeSelected(selectedItems: SparseArray<ProfileSealed>) {
if (selectedItems.size() > 0) activity?.let { activity ->
activity?.let { activity -> OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(selectedItems), Runnable {
OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(), Runnable { selectedItems.forEach { _, profileSwitch ->
selectedItems.forEach { _, profileSwitch -> uel.log(
uel.log( Action.PROFILE_SWITCH_REMOVED, Sources.Treatments, profileSwitch.profileName,
Action.PROFILE_SWITCH_REMOVED, Sources.Treatments, profileSwitch.profileName, ValueWithUnit.Timestamp(profileSwitch.timestamp)
ValueWithUnit.Timestamp(profileSwitch.timestamp) )
disposable += repository.runTransactionForResult(InvalidateProfileSwitchTransaction(profileSwitch.id))
.subscribe(
{ result -> result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated ProfileSwitch $it") } },
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating ProfileSwitch", it) }
) )
disposable += repository.runTransactionForResult(InvalidateProfileSwitchTransaction(profileSwitch.id)) }
.subscribe( actionHelper.finish()
{ result -> result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated ProfileSwitch $it") } }, })
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating ProfileSwitch", it) } }
)
}
removeActionMode?.finish()
})
}
else
removeActionMode?.finish()
} }
} }

View file

@ -4,46 +4,42 @@ import android.annotation.SuppressLint
import android.os.Bundle import android.os.Bundle
import android.util.SparseArray import android.util.SparseArray
import android.view.* import android.view.*
import androidx.appcompat.widget.Toolbar
import androidx.core.util.forEach import androidx.core.util.forEach
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import dagger.android.support.DaggerFragment import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.activities.fragments.TreatmentsTempTargetFragment.RecyclerViewAdapter.TempTargetsViewHolder
import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.ValueWrapper import info.nightscout.androidaps.database.ValueWrapper
import info.nightscout.androidaps.database.entities.ValueWithUnit
import info.nightscout.androidaps.database.entities.TemporaryTarget import info.nightscout.androidaps.database.entities.TemporaryTarget
import info.nightscout.androidaps.database.entities.UserEntry.Action import info.nightscout.androidaps.database.entities.UserEntry.Action
import info.nightscout.androidaps.database.entities.UserEntry.Sources import info.nightscout.androidaps.database.entities.UserEntry.Sources
import info.nightscout.androidaps.database.entities.ValueWithUnit
import info.nightscout.androidaps.database.interfaces.end import info.nightscout.androidaps.database.interfaces.end
import info.nightscout.androidaps.database.transactions.InvalidateTemporaryTargetTransaction import info.nightscout.androidaps.database.transactions.InvalidateTemporaryTargetTransaction
import info.nightscout.androidaps.databinding.TreatmentsTemptargetFragmentBinding import info.nightscout.androidaps.databinding.TreatmentsTemptargetFragmentBinding
import info.nightscout.androidaps.databinding.TreatmentsTemptargetItemBinding import info.nightscout.androidaps.databinding.TreatmentsTemptargetItemBinding
import info.nightscout.androidaps.events.EventTempTargetChange
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart
import info.nightscout.androidaps.events.EventTreatmentUpdateGui
import info.nightscout.androidaps.activities.fragments.TreatmentsTempTargetFragment.RecyclerViewAdapter.TempTargetsViewHolder
import info.nightscout.androidaps.events.EventEffectiveProfileSwitchChanged import info.nightscout.androidaps.events.EventEffectiveProfileSwitchChanged
import info.nightscout.androidaps.events.EventProfileSwitchChanged import info.nightscout.androidaps.events.EventProfileSwitchChanged
import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.events.EventTempTargetChange
import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.events.EventTreatmentUpdateGui
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.Translator
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.buildHelper.BuildHelper
import info.nightscout.androidaps.extensions.friendlyDescription import info.nightscout.androidaps.extensions.friendlyDescription
import info.nightscout.androidaps.extensions.highValueToUnitsToString import info.nightscout.androidaps.extensions.highValueToUnitsToString
import info.nightscout.androidaps.extensions.lowValueToUnitsToString import info.nightscout.androidaps.extensions.lowValueToUnitsToString
import info.nightscout.androidaps.extensions.toVisibility import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventNewHistoryData import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventNewHistoryData
import info.nightscout.androidaps.utils.*
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.buildHelper.BuildHelper
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 info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP import info.nightscout.shared.sharedPreferences.SP
import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.disposables.CompositeDisposable
@ -68,22 +64,24 @@ class TreatmentsTempTargetFragment : DaggerFragment() {
@Inject lateinit var repository: AppRepository @Inject lateinit var repository: AppRepository
private var _binding: TreatmentsTemptargetFragmentBinding? = null private var _binding: TreatmentsTemptargetFragmentBinding? = null
// This property is only valid between onCreateView and onDestroyView. // This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!! private val binding get() = _binding!!
private var menu: Menu? = null
private lateinit var actionHelper: ActionModeHelper<TemporaryTarget>
private val disposable = CompositeDisposable() private val disposable = CompositeDisposable()
private val millsToThePast = T.days(30).msecs() private val millsToThePast = T.days(30).msecs()
private var selectedItems: SparseArray<TemporaryTarget> = SparseArray()
private var showInvalidated = false private var showInvalidated = false
private var toolbar: Toolbar? = null
private var removeActionMode: ActionMode? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
TreatmentsTemptargetFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root TreatmentsTemptargetFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root
@SuppressLint("NotifyDataSetChanged")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.recyclerview.setHasFixedSize(true) binding.recyclerview.setHasFixedSize(true)
toolbar = activity?.findViewById(R.id.toolbar) actionHelper = ActionModeHelper(rh, activity)
actionHelper.setUpdateListHandler { binding.recyclerview.adapter?.notifyDataSetChanged() }
actionHelper.setOnRemoveHandler { removeSelected(it) }
setHasOptionsMenu(true) setHasOptionsMenu(true)
binding.recyclerview.layoutManager = LinearLayoutManager(view.context) binding.recyclerview.layoutManager = LinearLayoutManager(view.context)
} }
@ -131,13 +129,11 @@ class TreatmentsTempTargetFragment : DaggerFragment() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
swapAdapter() swapAdapter()
disposable += rxBus disposable += rxBus
.toObservable(EventTempTargetChange::class.java) .toObservable(EventTempTargetChange::class.java)
.observeOn(aapsSchedulers.io) .observeOn(aapsSchedulers.io)
.debounce(1L, TimeUnit.SECONDS) .debounce(1L, TimeUnit.SECONDS)
.subscribe({ swapAdapter() }, fabricPrivacy::logException) .subscribe({ swapAdapter() }, fabricPrivacy::logException)
disposable += rxBus disposable += rxBus
.toObservable(EventTreatmentUpdateGui::class.java) // TODO join with above .toObservable(EventTreatmentUpdateGui::class.java) // TODO join with above
.observeOn(aapsSchedulers.io) .observeOn(aapsSchedulers.io)
@ -148,13 +144,13 @@ class TreatmentsTempTargetFragment : DaggerFragment() {
@Synchronized @Synchronized
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
actionHelper.finish()
disposable.clear() disposable.clear()
} }
@Synchronized @Synchronized
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
removeActionMode?.finish()
binding.recyclerview.adapter = null // avoid leaks binding.recyclerview.adapter = null // avoid leaks
_binding = null _binding = null
} }
@ -173,17 +169,16 @@ class TreatmentsTempTargetFragment : DaggerFragment() {
val tempTarget = tempTargetList[position] val tempTarget = tempTargetList[position]
holder.binding.ns.visibility = (tempTarget.interfaceIDs.nightscoutId != null).toVisibility() holder.binding.ns.visibility = (tempTarget.interfaceIDs.nightscoutId != null).toVisibility()
holder.binding.invalid.visibility = tempTarget.isValid.not().toVisibility() holder.binding.invalid.visibility = tempTarget.isValid.not().toVisibility()
holder.binding.cbRemove.visibility = (tempTarget.isValid && removeActionMode != null).toVisibility() holder.binding.cbRemove.visibility = (tempTarget.isValid && actionHelper.isRemoving).toVisibility()
if (removeActionMode != null) { if (actionHelper.isRemoving) {
holder.binding.cbRemove.setOnCheckedChangeListener { _, value -> holder.binding.cbRemove.setOnCheckedChangeListener { _, value ->
if (value) { actionHelper.updateSelection(position, tempTarget, value)
selectedItems.put(position, tempTarget)
} else {
selectedItems.remove(position)
}
removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size())
} }
holder.binding.cbRemove.isChecked = selectedItems.get(position) != null holder.binding.root.setOnClickListener {
holder.binding.cbRemove.toggle()
actionHelper.updateSelection(position, tempTarget, holder.binding.cbRemove.isChecked)
}
holder.binding.cbRemove.isChecked = actionHelper.isSelected(position)
} }
val newDay = position == 0 || !dateUtil.isSameDayGroup(tempTarget.timestamp, tempTargetList[position - 1].timestamp) val newDay = position == 0 || !dateUtil.isSameDayGroup(tempTarget.timestamp, tempTargetList[position - 1].timestamp)
holder.binding.date.visibility = newDay.toVisibility() holder.binding.date.visibility = newDay.toVisibility()
@ -213,39 +208,19 @@ class TreatmentsTempTargetFragment : DaggerFragment() {
} }
} }
private fun removeSelected() {
if (selectedItems.size() > 0)
activity?.let { activity ->
OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(), Runnable {
selectedItems.forEach { _, tempTarget ->
uel.log(
Action.TT_REMOVED, Sources.Treatments,
ValueWithUnit.Timestamp(tempTarget.timestamp),
ValueWithUnit.TherapyEventTTReason(tempTarget.reason),
ValueWithUnit.Mgdl(tempTarget.lowTarget),
ValueWithUnit.Mgdl(tempTarget.highTarget).takeIf { tempTarget.lowTarget != tempTarget.highTarget },
ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(tempTarget.duration).toInt())
)
disposable += repository.runTransactionForResult(InvalidateTemporaryTargetTransaction(tempTarget.id))
.subscribe(
{ aapsLogger.debug(LTag.DATABASE, "Removed temp target $tempTarget") },
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating temporary target", it) })
}
removeActionMode?.finish()
})
}
else
removeActionMode?.finish()
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
this.menu = menu
inflater.inflate(R.menu.menu_treatments_temp_target, menu) inflater.inflate(R.menu.menu_treatments_temp_target, menu)
super.onCreateOptionsMenu(menu, inflater) super.onCreateOptionsMenu(menu, inflater)
} }
private fun updateMenuVisibility() {
menu?.findItem(R.id.nav_hide_invalidated)?.isVisible = showInvalidated
menu?.findItem(R.id.nav_show_invalidated)?.isVisible = !showInvalidated
}
override fun onPrepareOptionsMenu(menu: Menu) { override fun onPrepareOptionsMenu(menu: Menu) {
menu.findItem(R.id.nav_hide_invalidated)?.isVisible = showInvalidated updateMenuVisibility()
menu.findItem(R.id.nav_show_invalidated)?.isVisible = !showInvalidated
val nsUploadOnly = !sp.getBoolean(R.string.key_ns_receive_temp_target, false) || !buildHelper.isEngineeringMode() val nsUploadOnly = !sp.getBoolean(R.string.key_ns_receive_temp_target, false) || !buildHelper.isEngineeringMode()
menu.findItem(R.id.nav_refresh_ns)?.isVisible = !nsUploadOnly menu.findItem(R.id.nav_refresh_ns)?.isVisible = !nsUploadOnly
@ -254,19 +229,20 @@ class TreatmentsTempTargetFragment : DaggerFragment() {
override fun onOptionsItemSelected(item: MenuItem): Boolean = override fun onOptionsItemSelected(item: MenuItem): Boolean =
when (item.itemId) { when (item.itemId) {
R.id.nav_remove_items -> { R.id.nav_remove_items -> actionHelper.startRemove()
removeActionMode = toolbar?.startActionMode(RemoveActionModeCallback())
true
}
R.id.nav_show_invalidated -> { R.id.nav_show_invalidated -> {
showInvalidated = true showInvalidated = true
updateMenuVisibility()
ToastUtils.showToastInUiThread(context, rh.gs(R.string.show_invalidated_records))
rxBus.send(EventTreatmentUpdateGui()) rxBus.send(EventTreatmentUpdateGui())
true true
} }
R.id.nav_hide_invalidated -> { R.id.nav_hide_invalidated -> {
showInvalidated = false showInvalidated = false
updateMenuVisibility()
ToastUtils.showToastInUiThread(context, rh.gs(R.string.show_invalidated_records))
rxBus.send(EventTreatmentUpdateGui()) rxBus.send(EventTreatmentUpdateGui())
true true
} }
@ -279,36 +255,7 @@ class TreatmentsTempTargetFragment : DaggerFragment() {
else -> false else -> false
} }
inner class RemoveActionModeCallback : ActionMode.Callback { private fun getConfirmationText(selectedItems: SparseArray<TemporaryTarget>): String {
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 {
if (selectedItems.size() == 1) { if (selectedItems.size() == 1) {
val tempTarget = selectedItems.valueAt(0) val tempTarget = selectedItems.valueAt(0)
return "${rh.gs(R.string.careportal_temporarytarget)}: ${tempTarget.friendlyDescription(profileFunction.getUnits(), rh)}\n" + return "${rh.gs(R.string.careportal_temporarytarget)}: ${tempTarget.friendlyDescription(profileFunction.getUnits(), rh)}\n" +
@ -317,4 +264,26 @@ class TreatmentsTempTargetFragment : DaggerFragment() {
return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size()) return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size())
} }
private fun removeSelected(selectedItems: SparseArray<TemporaryTarget>) {
activity?.let { activity ->
OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(selectedItems), Runnable {
selectedItems.forEach { _, tempTarget ->
uel.log(
Action.TT_REMOVED, Sources.Treatments,
ValueWithUnit.Timestamp(tempTarget.timestamp),
ValueWithUnit.TherapyEventTTReason(tempTarget.reason),
ValueWithUnit.Mgdl(tempTarget.lowTarget),
ValueWithUnit.Mgdl(tempTarget.highTarget).takeIf { tempTarget.lowTarget != tempTarget.highTarget },
ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(tempTarget.duration).toInt())
)
disposable += repository.runTransactionForResult(InvalidateTemporaryTargetTransaction(tempTarget.id))
.subscribe(
{ aapsLogger.debug(LTag.DATABASE, "Removed temp target $tempTarget") },
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating temporary target", it) })
}
actionHelper.finish()
})
}
}
} }

View file

@ -1,14 +1,15 @@
package info.nightscout.androidaps.activities.fragments package info.nightscout.androidaps.activities.fragments
import android.annotation.SuppressLint
import android.os.Bundle import android.os.Bundle
import android.util.SparseArray import android.util.SparseArray
import android.view.* import android.view.*
import androidx.appcompat.widget.Toolbar
import androidx.core.util.forEach import androidx.core.util.forEach
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import dagger.android.support.DaggerFragment import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.activities.fragments.TreatmentsTemporaryBasalsFragment.RecyclerViewAdapter.TempBasalsViewHolder
import info.nightscout.androidaps.data.IobTotal import info.nightscout.androidaps.data.IobTotal
import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.ValueWrapper import info.nightscout.androidaps.database.ValueWrapper
@ -24,24 +25,25 @@ import info.nightscout.androidaps.databinding.TreatmentsTempbasalsFragmentBindin
import info.nightscout.androidaps.databinding.TreatmentsTempbasalsItemBinding import info.nightscout.androidaps.databinding.TreatmentsTempbasalsItemBinding
import info.nightscout.androidaps.events.EventAutosensCalculationFinished import info.nightscout.androidaps.events.EventAutosensCalculationFinished
import info.nightscout.androidaps.events.EventTempBasalChange import info.nightscout.androidaps.events.EventTempBasalChange
import info.nightscout.androidaps.events.EventTreatmentUpdateGui
import info.nightscout.androidaps.extensions.iobCalc import info.nightscout.androidaps.extensions.iobCalc
import info.nightscout.androidaps.extensions.toStringFull import info.nightscout.androidaps.extensions.toStringFull
import info.nightscout.androidaps.extensions.toTemporaryBasal import info.nightscout.androidaps.extensions.toTemporaryBasal
import info.nightscout.androidaps.extensions.toVisibility import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.interfaces.ActivePlugin import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.ProfileFunction import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
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.activities.fragments.TreatmentsTemporaryBasalsFragment.RecyclerViewAdapter.TempBasalsViewHolder import info.nightscout.androidaps.utils.ActionModeHelper
import info.nightscout.androidaps.events.EventTreatmentUpdateGui
import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.T import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.ToastUtils
import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.utils.alertDialogs.OKDialog
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 info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign import io.reactivex.rxjava3.kotlin.plusAssign
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -64,21 +66,23 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() {
@Inject lateinit var repository: AppRepository @Inject lateinit var repository: AppRepository
private var _binding: TreatmentsTempbasalsFragmentBinding? = null private var _binding: TreatmentsTempbasalsFragmentBinding? = null
// This property is only valid between onCreateView and onDestroyView. // This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!! private val binding get() = _binding!!
private var menu: Menu? = null
private lateinit var actionHelper: ActionModeHelper<TemporaryBasal>
private val millsToThePast = T.days(30).msecs() private val millsToThePast = T.days(30).msecs()
private var selectedItems: SparseArray<TemporaryBasal> = SparseArray()
private var showInvalidated = false private var showInvalidated = false
private var toolbar: Toolbar? = null
private var removeActionMode: ActionMode? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
TreatmentsTempbasalsFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root TreatmentsTempbasalsFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root
@SuppressLint("NotifyDataSetChanged")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
toolbar = activity?.findViewById(R.id.toolbar) actionHelper = ActionModeHelper(rh, activity)
actionHelper.setUpdateListHandler { binding.recyclerview.adapter?.notifyDataSetChanged() }
actionHelper.setOnRemoveHandler { removeSelected(it) }
setHasOptionsMenu(true) setHasOptionsMenu(true)
binding.recyclerview.setHasFixedSize(true) binding.recyclerview.setHasFixedSize(true)
binding.recyclerview.layoutManager = LinearLayoutManager(view.context) binding.recyclerview.layoutManager = LinearLayoutManager(view.context)
@ -133,28 +137,31 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
swapAdapter() swapAdapter()
disposable += rxBus disposable += rxBus
.toObservable(EventTempBasalChange::class.java) .toObservable(EventTempBasalChange::class.java)
.observeOn(aapsSchedulers.main) .observeOn(aapsSchedulers.main)
.subscribe({ swapAdapter() }, fabricPrivacy::logException) .subscribe({ swapAdapter() }, fabricPrivacy::logException)
disposable += rxBus disposable += rxBus
.toObservable(EventAutosensCalculationFinished::class.java) .toObservable(EventAutosensCalculationFinished::class.java)
.observeOn(aapsSchedulers.main) .observeOn(aapsSchedulers.main)
.subscribe({ swapAdapter() }, fabricPrivacy::logException) .subscribe({ swapAdapter() }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventTreatmentUpdateGui::class.java) // TODO join with above
.observeOn(aapsSchedulers.io)
.debounce(1L, TimeUnit.SECONDS)
.subscribe({ swapAdapter() }, fabricPrivacy::logException)
} }
@Synchronized @Synchronized
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
actionHelper.finish()
disposable.clear() disposable.clear()
} }
@Synchronized @Synchronized
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
removeActionMode?.finish()
binding.recyclerview.adapter = null // avoid leaks binding.recyclerview.adapter = null // avoid leaks
_binding = null _binding = null
} }
@ -194,17 +201,16 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() {
holder.binding.emulatedSuspendFlag.visibility = (tempBasal.type == TemporaryBasal.Type.EMULATED_PUMP_SUSPEND).toVisibility() holder.binding.emulatedSuspendFlag.visibility = (tempBasal.type == TemporaryBasal.Type.EMULATED_PUMP_SUSPEND).toVisibility()
holder.binding.superBolusFlag.visibility = (tempBasal.type == TemporaryBasal.Type.SUPERBOLUS).toVisibility() holder.binding.superBolusFlag.visibility = (tempBasal.type == TemporaryBasal.Type.SUPERBOLUS).toVisibility()
if (abs(iob.basaliob) > 0.01) holder.binding.iob.setTextColor(rh.gac(context , R.attr.activeColor)) else holder.binding.iob.setTextColor(holder.binding.duration.currentTextColor) if (abs(iob.basaliob) > 0.01) holder.binding.iob.setTextColor(rh.gac(context , R.attr.activeColor)) else holder.binding.iob.setTextColor(holder.binding.duration.currentTextColor)
holder.binding.cbRemove.visibility = (tempBasal.isValid && removeActionMode != null).toVisibility() holder.binding.cbRemove.visibility = (tempBasal.isValid && actionHelper.isRemoving).toVisibility()
if (removeActionMode != null) { if (actionHelper.isRemoving) {
holder.binding.cbRemove.setOnCheckedChangeListener { _, value -> holder.binding.cbRemove.setOnCheckedChangeListener { _, value ->
if (value) { actionHelper.updateSelection(position, tempBasal, value)
selectedItems.put(position, tempBasal)
} else {
selectedItems.remove(position)
}
removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size())
} }
holder.binding.cbRemove.isChecked = selectedItems.get(position) != null holder.binding.root.setOnClickListener {
holder.binding.cbRemove.toggle()
actionHelper.updateSelection(position, tempBasal, holder.binding.cbRemove.isChecked)
}
holder.binding.cbRemove.isChecked = actionHelper.isSelected(position)
} }
val nextTimestamp = if (tempBasalList.size != position + 1) tempBasalList[position + 1].timestamp else 0L val nextTimestamp = if (tempBasalList.size != position + 1) tempBasalList[position + 1].timestamp else 0L
holder.binding.delimiter.visibility = dateUtil.isSameDayGroup(tempBasal.timestamp, nextTimestamp).toVisibility() holder.binding.delimiter.visibility = dateUtil.isSameDayGroup(tempBasal.timestamp, nextTimestamp).toVisibility()
@ -221,32 +227,38 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() {
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
this.menu = menu
inflater.inflate(R.menu.menu_treatments_temp_basal, menu) inflater.inflate(R.menu.menu_treatments_temp_basal, menu)
super.onCreateOptionsMenu(menu, inflater) super.onCreateOptionsMenu(menu, inflater)
} }
private fun updateMenuVisibility() {
menu?.findItem(R.id.nav_hide_invalidated)?.isVisible = showInvalidated
menu?.findItem(R.id.nav_show_invalidated)?.isVisible = !showInvalidated
}
override fun onPrepareOptionsMenu(menu: Menu) { override fun onPrepareOptionsMenu(menu: Menu) {
menu.findItem(R.id.nav_hide_invalidated)?.isVisible = showInvalidated updateMenuVisibility()
menu.findItem(R.id.nav_show_invalidated)?.isVisible = !showInvalidated
return super.onPrepareOptionsMenu(menu) return super.onPrepareOptionsMenu(menu)
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean = override fun onOptionsItemSelected(item: MenuItem): Boolean =
when (item.itemId) { when (item.itemId) {
R.id.nav_remove_items -> { R.id.nav_remove_items -> actionHelper.startRemove()
removeActionMode = toolbar?.startActionMode(RemoveActionModeCallback())
true
}
R.id.nav_show_invalidated -> { R.id.nav_show_invalidated -> {
showInvalidated = true showInvalidated = true
updateMenuVisibility()
ToastUtils.showToastInUiThread(context, rh.gs(R.string.show_invalidated_records))
rxBus.send(EventTreatmentUpdateGui()) rxBus.send(EventTreatmentUpdateGui())
true true
} }
R.id.nav_hide_invalidated -> { R.id.nav_hide_invalidated -> {
showInvalidated = false showInvalidated = false
updateMenuVisibility()
ToastUtils.showToastInUiThread(context, rh.gs(R.string.hide_invalidated_records))
rxBus.send(EventTreatmentUpdateGui()) rxBus.send(EventTreatmentUpdateGui())
true true
} }
@ -254,36 +266,7 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() {
else -> false else -> false
} }
inner class RemoveActionModeCallback : ActionMode.Callback { private fun getConfirmationText(selectedItems: SparseArray<TemporaryBasal>): String {
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 {
if (selectedItems.size() == 1) { if (selectedItems.size() == 1) {
val tempBasal = selectedItems.valueAt(0) val tempBasal = selectedItems.valueAt(0)
val isFakeExtended = tempBasal.type == TemporaryBasal.Type.FAKE_EXTENDED val isFakeExtended = tempBasal.type == TemporaryBasal.Type.FAKE_EXTENDED
@ -295,46 +278,44 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() {
return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size()) return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size())
} }
private fun removeSelected() { private fun removeSelected(selectedItems: SparseArray<TemporaryBasal>) {
if (selectedItems.size() > 0) if (selectedItems.size() > 0)
activity?.let { activity -> 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 {_, tempBasal -> selectedItems.forEach {_, tempBasal ->
var extendedBolus: ExtendedBolus? = null var extendedBolus: ExtendedBolus? = null
val isFakeExtended = tempBasal.type == TemporaryBasal.Type.FAKE_EXTENDED val isFakeExtended = tempBasal.type == TemporaryBasal.Type.FAKE_EXTENDED
if (isFakeExtended) { if (isFakeExtended) {
val eb = repository.getExtendedBolusActiveAt(tempBasal.timestamp).blockingGet() val eb = repository.getExtendedBolusActiveAt(tempBasal.timestamp).blockingGet()
extendedBolus = if (eb is ValueWrapper.Existing) eb.value else null extendedBolus = if (eb is ValueWrapper.Existing) eb.value else null
}
if (isFakeExtended && extendedBolus != null) {
uel.log(
Action.EXTENDED_BOLUS_REMOVED, Sources.Treatments,
ValueWithUnit.Timestamp(extendedBolus.timestamp),
ValueWithUnit.Insulin(extendedBolus.amount),
ValueWithUnit.UnitPerHour(extendedBolus.rate),
ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(extendedBolus.duration).toInt())
)
disposable += repository.runTransactionForResult(InvalidateExtendedBolusTransaction(extendedBolus.id))
.subscribe(
{ aapsLogger.debug(LTag.DATABASE, "Removed extended bolus $extendedBolus") },
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating extended bolus", it) })
} else if (!isFakeExtended) {
uel.log(
Action.TEMP_BASAL_REMOVED, Sources.Treatments,
ValueWithUnit.Timestamp(tempBasal.timestamp),
if (tempBasal.isAbsolute) ValueWithUnit.UnitPerHour(tempBasal.rate) else ValueWithUnit.Percent(tempBasal.rate.toInt()),
ValueWithUnit.Minute(T.msecs(tempBasal.duration).mins().toInt())
)
disposable += repository.runTransactionForResult(InvalidateTemporaryBasalTransaction(tempBasal.id))
.subscribe(
{ aapsLogger.debug(LTag.DATABASE, "Removed temporary basal $tempBasal") },
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating temporary basal", it) })
}
} }
removeActionMode?.finish() if (isFakeExtended && extendedBolus != null) {
}) uel.log(
} Action.EXTENDED_BOLUS_REMOVED, Sources.Treatments,
else ValueWithUnit.Timestamp(extendedBolus.timestamp),
removeActionMode?.finish() ValueWithUnit.Insulin(extendedBolus.amount),
ValueWithUnit.UnitPerHour(extendedBolus.rate),
ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(extendedBolus.duration).toInt())
)
disposable += repository.runTransactionForResult(InvalidateExtendedBolusTransaction(extendedBolus.id))
.subscribe(
{ aapsLogger.debug(LTag.DATABASE, "Removed extended bolus $extendedBolus") },
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating extended bolus", it) })
} else if (!isFakeExtended) {
uel.log(
Action.TEMP_BASAL_REMOVED, Sources.Treatments,
ValueWithUnit.Timestamp(tempBasal.timestamp),
if (tempBasal.isAbsolute) ValueWithUnit.UnitPerHour(tempBasal.rate) else ValueWithUnit.Percent(tempBasal.rate.toInt()),
ValueWithUnit.Minute(T.msecs(tempBasal.duration).mins().toInt())
)
disposable += repository.runTransactionForResult(InvalidateTemporaryBasalTransaction(tempBasal.id))
.subscribe(
{ aapsLogger.debug(LTag.DATABASE, "Removed temporary basal $tempBasal") },
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating temporary basal", it) })
}
}
actionHelper.finish()
})
}
} }
} }

View file

@ -13,20 +13,21 @@ import info.nightscout.androidaps.database.entities.UserEntry.Sources
import info.nightscout.androidaps.databinding.TreatmentsUserEntryFragmentBinding import info.nightscout.androidaps.databinding.TreatmentsUserEntryFragmentBinding
import info.nightscout.androidaps.databinding.TreatmentsUserEntryItemBinding import info.nightscout.androidaps.databinding.TreatmentsUserEntryItemBinding
import info.nightscout.androidaps.events.EventPreferenceChange import info.nightscout.androidaps.events.EventPreferenceChange
import info.nightscout.androidaps.events.EventTreatmentUpdateGui
import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.interfaces.ImportExportPrefs import info.nightscout.androidaps.interfaces.ImportExportPrefs
import info.nightscout.androidaps.interfaces.ProfileFunction import info.nightscout.androidaps.interfaces.ProfileFunction
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.events.EventTreatmentUpdateGui
import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.T import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.ToastUtils
import info.nightscout.androidaps.utils.Translator import info.nightscout.androidaps.utils.Translator
import info.nightscout.androidaps.utils.userEntry.UserEntryPresentationHelper
import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.utils.alertDialogs.OKDialog
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 info.nightscout.androidaps.utils.userEntry.UserEntryPresentationHelper
import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign import io.reactivex.rxjava3.kotlin.plusAssign
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -47,9 +48,9 @@ class TreatmentsUserEntryFragment : DaggerFragment() {
@Inject lateinit var userEntryPresentationHelper: UserEntryPresentationHelper @Inject lateinit var userEntryPresentationHelper: UserEntryPresentationHelper
private val disposable = CompositeDisposable() private val disposable = CompositeDisposable()
private val millsToThePastFiltered = T.days(30).msecs() private val millsToThePastFiltered = T.days(30).msecs()
private val millsToThePastUnFiltered = T.days(3).msecs() private val millsToThePastUnFiltered = T.days(3).msecs()
private var menu: Menu? = null
private var showLoop = false private var showLoop = false
private var _binding: TreatmentsUserEntryFragmentBinding? = null private var _binding: TreatmentsUserEntryFragmentBinding? = null
@ -66,7 +67,7 @@ class TreatmentsUserEntryFragment : DaggerFragment() {
binding.recyclerview.layoutManager = LinearLayoutManager(view.context) binding.recyclerview.layoutManager = LinearLayoutManager(view.context)
} }
fun exportUserEnteries() { private fun exportUserEntries() {
activity?.let { activity -> activity?.let { activity ->
OKDialog.showConfirmation(activity, rh.gs(R.string.ue_export_to_csv) + "?") { OKDialog.showConfirmation(activity, rh.gs(R.string.ue_export_to_csv) + "?") {
uel.log(Action.EXPORT_CSV, Sources.Treatments) uel.log(Action.EXPORT_CSV, Sources.Treatments)
@ -94,7 +95,6 @@ class TreatmentsUserEntryFragment : DaggerFragment() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
swapAdapter() swapAdapter()
disposable += rxBus disposable += rxBus
.toObservable(EventPreferenceChange::class.java) .toObservable(EventPreferenceChange::class.java)
.observeOn(aapsSchedulers.io) .observeOn(aapsSchedulers.io)
@ -134,11 +134,10 @@ class TreatmentsUserEntryFragment : DaggerFragment() {
holder.binding.time.text = dateUtil.timeStringWithSeconds(current.timestamp) holder.binding.time.text = dateUtil.timeStringWithSeconds(current.timestamp)
holder.binding.action.text = userEntryPresentationHelper.actionToColoredString(current.action) holder.binding.action.text = userEntryPresentationHelper.actionToColoredString(current.action)
holder.binding.notes.text = current.note holder.binding.notes.text = current.note
holder.binding.notes.visibility = if (current.note != "") View.VISIBLE else View.GONE holder.binding.notes.visibility = (current.note != "").toVisibility()
holder.binding.iconSource.setImageResource(userEntryPresentationHelper.iconId(current.source)) holder.binding.iconSource.setImageResource(userEntryPresentationHelper.iconId(current.source))
holder.binding.iconSource.visibility = View.VISIBLE
holder.binding.values.text = userEntryPresentationHelper.listToPresentationString(current.values) holder.binding.values.text = userEntryPresentationHelper.listToPresentationString(current.values)
holder.binding.values.visibility = if (holder.binding.values.text != "") View.VISIBLE else View.GONE holder.binding.values.visibility = (holder.binding.values.text != "").toVisibility()
val nextTimestamp = if (entries.size != position + 1) entries[position + 1].timestamp else 0L val nextTimestamp = if (entries.size != position + 1) entries[position + 1].timestamp else 0L
holder.binding.delimiter.visibility = dateUtil.isSameDayGroup(current.timestamp, nextTimestamp).toVisibility() holder.binding.delimiter.visibility = dateUtil.isSameDayGroup(current.timestamp, nextTimestamp).toVisibility()
} }
@ -152,14 +151,18 @@ class TreatmentsUserEntryFragment : DaggerFragment() {
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
this.menu = menu
inflater.inflate(R.menu.menu_treatments_user_entry, menu) inflater.inflate(R.menu.menu_treatments_user_entry, menu)
super.onCreateOptionsMenu(menu, inflater) super.onCreateOptionsMenu(menu, inflater)
} }
override fun onPrepareOptionsMenu(menu: Menu) { private fun updateMenuVisibility() {
menu.findItem(R.id.nav_hide_loop)?.isVisible = showLoop menu?.findItem(R.id.nav_hide_loop)?.isVisible = showLoop
menu.findItem(R.id.nav_show_loop)?.isVisible = !showLoop menu?.findItem(R.id.nav_show_loop)?.isVisible = !showLoop
}
override fun onPrepareOptionsMenu(menu: Menu) {
updateMenuVisibility()
return super.onPrepareOptionsMenu(menu) return super.onPrepareOptionsMenu(menu)
} }
@ -167,21 +170,26 @@ class TreatmentsUserEntryFragment : DaggerFragment() {
when (item.itemId) { when (item.itemId) {
R.id.nav_show_loop -> { R.id.nav_show_loop -> {
showLoop = true showLoop = true
updateMenuVisibility()
ToastUtils.showToastInUiThread(context, rh.gs(R.string.show_loop_records))
rxBus.send(EventTreatmentUpdateGui()) rxBus.send(EventTreatmentUpdateGui())
true true
} }
R.id.nav_hide_loop -> { R.id.nav_hide_loop -> {
showLoop = false showLoop = false
updateMenuVisibility()
ToastUtils.showToastInUiThread(context, rh.gs(R.string.show_hide_records))
rxBus.send(EventTreatmentUpdateGui()) rxBus.send(EventTreatmentUpdateGui())
true true
} }
R.id.nav_export -> { R.id.nav_export -> {
exportUserEnteries() exportUserEntries()
true true
} }
else -> false else -> false
} }
} }

View file

@ -7,11 +7,6 @@ import android.view.*
import androidx.core.util.forEach import androidx.core.util.forEach
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.ItemTouchHelper 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.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import info.nightscout.androidaps.R 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.databinding.OverviewQuickwizardlistItemBinding
import info.nightscout.androidaps.extensions.toVisibility import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.plugins.bus.RxBus 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.dialogs.EditQuickWizardDialog
import info.nightscout.androidaps.plugins.general.overview.events.EventQuickWizardChange import info.nightscout.androidaps.plugins.general.overview.events.EventQuickWizardChange
import info.nightscout.androidaps.utils.ActionModeHelper
import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.utils.alertDialogs.OKDialog
@ -33,7 +32,7 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign import io.reactivex.rxjava3.kotlin.plusAssign
import javax.inject.Inject import javax.inject.Inject
class QuickWizardListActivity : DaggerAppCompatActivityWithResult() { class QuickWizardListActivity : DaggerAppCompatActivityWithResult(), OnStartDragListener {
@Inject lateinit var aapsSchedulers: AapsSchedulers @Inject lateinit var aapsSchedulers: AapsSchedulers
@Inject lateinit var rxBus: RxBus @Inject lateinit var rxBus: RxBus
@ -43,49 +42,15 @@ class QuickWizardListActivity : DaggerAppCompatActivityWithResult() {
@Inject lateinit var sp: SP @Inject lateinit var sp: SP
private var disposable: CompositeDisposable = CompositeDisposable() private var disposable: CompositeDisposable = CompositeDisposable()
private var selectedItems: SparseArray<QuickWizardEntry> = SparseArray() private lateinit var actionHelper: ActionModeHelper<QuickWizardEntry>
private var removeActionMode: ActionMode? = null private val itemTouchHelper = ItemTouchHelper(SimpleItemTouchHelperCallback())
private var sortActionMode: ActionMode? = null
private lateinit var binding: OverviewQuickwizardlistActivityBinding private lateinit var binding: OverviewQuickwizardlistActivityBinding
private val itemTouchHelper by lazy { override fun onStartDrag(viewHolder: RecyclerView.ViewHolder) {
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) {
itemTouchHelper.startDrag(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) private inner class QuickWizardEntryViewHolder(val binding: OverviewQuickwizardlistItemBinding, val fragmentManager: FragmentManager) : RecyclerView.ViewHolder(binding.root)
@ -112,61 +77,43 @@ class QuickWizardListActivity : DaggerAppCompatActivityWithResult() {
} }
) )
} }
holder.binding.root.setOnClickListener {
if (sortActionMode != null && removeActionMode != null) { if (actionHelper.isNoAction) {
holder.binding.cardview.setOnClickListener {
val manager = fragmentManager val manager = fragmentManager
val editQuickWizardDialog = EditQuickWizardDialog() val editQuickWizardDialog = EditQuickWizardDialog()
val bundle = Bundle() val bundle = Bundle()
bundle.putInt("position", position) bundle.putInt("position", position)
editQuickWizardDialog.arguments = bundle editQuickWizardDialog.arguments = bundle
editQuickWizardDialog.show(manager, "EditQuickWizardDialog") editQuickWizardDialog.show(manager, "EditQuickWizardDialog")
} } else if (actionHelper.isRemoving) {
}
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) {
holder.binding.cbRemove.toggle() holder.binding.cbRemove.toggle()
updateSelection(holder.binding.cbRemove.isChecked) actionHelper.updateSelection(position, entry, holder.binding.cbRemove.isChecked)
} }
return@setOnTouchListener true
} }
holder.binding.cbRemove.isChecked = selectedItems.get(position) != null holder.binding.sortHandle.setOnTouchListener { _, event ->
holder.binding.cbRemove.setOnCheckedChangeListener { _, value -> updateSelection(value) } if (event.actionMasked == MotionEvent.ACTION_DOWN) {
holder.binding.handleView.visibility = (sortActionMode != null).toVisibility() onStartDrag(holder)
holder.binding.cbRemove.visibility = (removeActionMode != null).toVisibility() return@setOnTouchListener true
removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size()) }
return@setOnTouchListener false
}
holder.binding.cbRemove.isChecked = actionHelper.isSelected(position)
holder.binding.cbRemove.setOnCheckedChangeListener { _, value ->
actionHelper.updateSelection(position, entry, value)
}
holder.binding.sortHandle.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) { override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
quickWizard.move(from, to) binding.recyclerview.adapter?.notifyItemMoved(fromPosition, toPosition)
quickWizard.move(fromPosition, toPosition)
return true
} }
fun onDrop() { override fun onDrop() = rxBus.send(EventQuickWizardChange())
rxBus.send(EventQuickWizardChange())
}
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -174,6 +121,11 @@ class QuickWizardListActivity : DaggerAppCompatActivityWithResult() {
binding = OverviewQuickwizardlistActivityBinding.inflate(layoutInflater) binding = OverviewQuickwizardlistActivityBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
actionHelper = ActionModeHelper(rh, this)
actionHelper.setUpdateListHandler { binding.recyclerview.adapter?.notifyDataSetChanged() }
actionHelper.setOnRemoveHandler { removeSelected(it) }
actionHelper.enableSort = true
title = rh.gs(R.string.quickwizard) title = rh.gs(R.string.quickwizard)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true) supportActionBar?.setDisplayShowHomeEnabled(true)
@ -184,6 +136,7 @@ class QuickWizardListActivity : DaggerAppCompatActivityWithResult() {
itemTouchHelper.attachToRecyclerView(binding.recyclerview) itemTouchHelper.attachToRecyclerView(binding.recyclerview)
binding.addButton.setOnClickListener { binding.addButton.setOnClickListener {
actionHelper.finish()
val manager = supportFragmentManager val manager = supportFragmentManager
val editQuickWizardDialog = EditQuickWizardDialog() val editQuickWizardDialog = EditQuickWizardDialog()
editQuickWizardDialog.show(manager, "EditQuickWizardDialog") editQuickWizardDialog.show(manager, "EditQuickWizardDialog")
@ -203,105 +156,37 @@ class QuickWizardListActivity : DaggerAppCompatActivityWithResult() {
override fun onPause() { override fun onPause() {
disposable.clear() disposable.clear()
actionHelper.finish()
super.onPause() super.onPause()
} }
private fun removeSelected() { private fun removeSelected(selectedItems: SparseArray<QuickWizardEntry>) {
if (selectedItems.size() > 0) OKDialog.showConfirmation(this, rh.gs(R.string.removerecord), getConfirmationText(selectedItems), Runnable {
OKDialog.showConfirmation(this, rh.gs(R.string.removerecord), getConfirmationText(), Runnable { selectedItems.forEach { _, item ->
selectedItems.forEach { _, item -> quickWizard.remove(item.position)
quickWizard.remove(item.position) rxBus.send(EventQuickWizardChange())
rxBus.send(EventQuickWizardChange()) }
} actionHelper.finish()
removeActionMode?.finish() })
})
else
removeActionMode?.finish()
} }
override fun onCreateOptionsMenu(menu: Menu?): Boolean { override fun onCreateOptionsMenu(menu: Menu?): Boolean {
val inflater = menuInflater menuInflater.inflate(R.menu.menu_actions, menu)
inflater.inflate(R.menu.menu_quickwizard, menu)
return super.onCreateOptionsMenu(menu) return super.onCreateOptionsMenu(menu)
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean = override fun onOptionsItemSelected(item: MenuItem): Boolean =
when (item.itemId) { when (item.itemId) {
android.R.id.home -> { android.R.id.home -> {
finish() finish()
true true
} }
R.id.nav_remove_items -> { else -> actionHelper.onOptionsItemSelected(item)
removeActionMode = startActionMode(RemoveActionModeCallback())
true
}
R.id.nav_sort_items -> {
sortActionMode = startActionMode(SortActionModeCallback())
true
}
else -> false
} }
inner class RemoveActionModeCallback : ActionMode.Callback { private fun getConfirmationText(selectedItems: SparseArray<QuickWizardEntry>): String {
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 {
if (selectedItems.size() == 1) { if (selectedItems.size() == 1) {
val entry = selectedItems.valueAt(0) val entry = selectedItems.valueAt(0)
return "${rh.gs(R.string.remove_button)} ${entry.buttonText()} ${rh.gs(R.string.format_carbs, entry.carbs())}\n" + return "${rh.gs(R.string.remove_button)} ${entry.buttonText()} ${rh.gs(R.string.format_carbs, entry.carbs())}\n" +
@ -309,4 +194,5 @@ class QuickWizardListActivity : DaggerAppCompatActivityWithResult() {
} }
return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size()) 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.os.Bundle
import android.util.SparseArray import android.util.SparseArray
import android.view.* import android.view.*
import androidx.appcompat.widget.Toolbar
import androidx.core.util.forEach import androidx.core.util.forEach
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView 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.interfaces.ProfileFunction
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.utils.ActionModeHelper
import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.T import info.nightscout.androidaps.utils.T
@ -54,11 +54,9 @@ class BGSourceFragment : DaggerFragment() {
private val disposable = CompositeDisposable() private val disposable = CompositeDisposable()
private val millsToThePast = T.hours(36).msecs() private val millsToThePast = T.hours(36).msecs()
private var selectedItems: SparseArray<GlucoseValue> = SparseArray() private lateinit var actionHelper: ActionModeHelper<GlucoseValue>
private var toolbar: Toolbar? = null
private var removeActionMode: ActionMode? = null
private var _binding: BgsourceFragmentBinding? = null private var _binding: BgsourceFragmentBinding? = null
// This property is only valid between onCreateView and onDestroyView. // This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!! private val binding get() = _binding!!
@ -67,8 +65,10 @@ class BGSourceFragment : DaggerFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
toolbar = activity?.findViewById(R.id.toolbar) actionHelper = ActionModeHelper(rh, activity)
setHasOptionsMenu(true) actionHelper.setUpdateListHandler { binding.recyclerview.adapter?.notifyDataSetChanged() }
actionHelper.setOnRemoveHandler { removeSelected(it) }
setHasOptionsMenu(actionHelper.inMenu)
binding.recyclerview.setHasFixedSize(true) binding.recyclerview.setHasFixedSize(true)
binding.recyclerview.layoutManager = LinearLayoutManager(view.context) binding.recyclerview.layoutManager = LinearLayoutManager(view.context)
@ -97,44 +97,30 @@ class BGSourceFragment : DaggerFragment() {
@Synchronized @Synchronized
override fun onPause() { override fun onPause() {
removeActionMode?.finish() actionHelper.finish()
disposable.clear() disposable.clear()
super.onPause() super.onPause()
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater) super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.menu_bgsource, menu) actionHelper.onCreateOptionsMenu(menu, inflater)
} }
override fun onPrepareOptionsMenu(menu: Menu) { 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) super.onPrepareOptionsMenu(menu)
actionHelper.onPrepareOptionsMenu(menu)
} }
@Synchronized @Synchronized
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
removeActionMode?.finish()
binding.recyclerview.adapter = null // avoid leaks binding.recyclerview.adapter = null // avoid leaks
_binding = null _binding = null
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem) =
return when (item.itemId) { actionHelper.onOptionsItemSelected(item)
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
}
}
inner class RecyclerViewAdapter internal constructor(private var glucoseValues: List<GlucoseValue>) : RecyclerView.Adapter<RecyclerViewAdapter.GlucoseValuesViewHolder>() { inner class RecyclerViewAdapter internal constructor(private var glucoseValues: List<GlucoseValue>) : RecyclerView.Adapter<RecyclerViewAdapter.GlucoseValuesViewHolder>() {
@ -159,34 +145,29 @@ class BGSourceFragment : DaggerFragment() {
if (diff < T.secs(20).msecs()) if (diff < T.secs(20).msecs())
holder.binding.root.setBackgroundColor(rh.gc(R.color.errorAlertBackground)) 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 { holder.binding.root.setOnLongClickListener {
if (removeActionMode == null) { if (actionHelper.startRemove()) {
removeActionMode = toolbar?.startActionMode(RemoveActionModeCallback()) holder.binding.cbRemove.toggle()
actionHelper.updateSelection(position, glucoseValue, holder.binding.cbRemove.isChecked)
return@setOnLongClickListener true
} }
holder.binding.cbRemove.toggle() false
updateSelection(holder.binding.cbRemove.isChecked)
true
} }
holder.binding.root.setOnClickListener { holder.binding.root.setOnClickListener {
if (removeActionMode != null) { if (actionHelper.isRemoving) {
holder.binding.cbRemove.toggle() 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.setOnCheckedChangeListener { _, value ->
holder.binding.cbRemove.isChecked = selectedItems.get(position) != null actionHelper.updateSelection(position, glucoseValue, value)
holder.binding.cbRemove.visibility = (removeActionMode != null).toVisibility() }
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) { inner class GlucoseValuesViewHolder(view: View) : RecyclerView.ViewHolder(view) {
@ -194,36 +175,7 @@ class BGSourceFragment : DaggerFragment() {
} }
} }
inner class RemoveActionModeCallback : ActionMode.Callback { private fun getConfirmationText(selectedItems: SparseArray<GlucoseValue>): String {
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 {
if (selectedItems.size() == 1) { if (selectedItems.size() == 1) {
val glucoseValue = selectedItems.valueAt(0) val glucoseValue = selectedItems.valueAt(0)
return dateUtil.dateAndTimeString(glucoseValue.timestamp) + "\n" + glucoseValue.valueToUnitsString(profileFunction.getUnits()) return dateUtil.dateAndTimeString(glucoseValue.timestamp) + "\n" + glucoseValue.valueToUnitsString(profileFunction.getUnits())
@ -231,36 +183,33 @@ class BGSourceFragment : DaggerFragment() {
return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size()) return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size())
} }
private fun removeSelected() { private fun removeSelected(selectedItems: SparseArray<GlucoseValue>) {
if (selectedItems.size() > 0) activity?.let { activity ->
activity?.let { activity -> OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(selectedItems), Runnable {
OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(), Runnable { selectedItems.forEach { _, glucoseValue ->
selectedItems.forEach { _, glucoseValue -> val source = when ((activePlugin.activeBgSource as PluginBase).pluginDescription.pluginName) {
val source = when ((activePlugin.activeBgSource as PluginBase).pluginDescription.pluginName) { R.string.dexcom_app_patched -> Sources.Dexcom
R.string.dexcom_app_patched -> Sources.Dexcom R.string.eversense -> Sources.Eversense
R.string.eversense -> Sources.Eversense R.string.Glimp -> Sources.Glimp
R.string.Glimp -> Sources.Glimp R.string.MM640g -> Sources.MM640g
R.string.MM640g -> Sources.MM640g R.string.nsclientbg -> Sources.NSClientSource
R.string.nsclientbg -> Sources.NSClientSource R.string.poctech -> Sources.PocTech
R.string.poctech -> Sources.PocTech R.string.tomato -> Sources.Tomato
R.string.tomato -> Sources.Tomato R.string.glunovo -> Sources.Glunovo
R.string.glunovo -> Sources.Glunovo R.string.xdrip -> Sources.Xdrip
R.string.xdrip -> Sources.Xdrip else -> Sources.Unknown
else -> Sources.Unknown
}
uel.log(
Action.BG_REMOVED, source,
ValueWithUnit.Timestamp(glucoseValue.timestamp)
)
repository.runTransactionForResult(InvalidateGlucoseValueTransaction(glucoseValue.id))
.doOnError { aapsLogger.error(LTag.DATABASE, "Error while invalidating BG value", it) }
.blockingGet()
.also { result -> result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated bg $it") } }
} }
removeActionMode?.finish() uel.log(
}) Action.BG_REMOVED, source,
} ValueWithUnit.Timestamp(glucoseValue.timestamp)
else )
removeActionMode?.finish() repository.runTransactionForResult(InvalidateGlucoseValueTransaction(glucoseValue.id))
.doOnError { aapsLogger.error(LTag.DATABASE, "Error while invalidating BG value", it) }
.blockingGet()
.also { result -> result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated bg $it") } }
}
actionHelper.finish()
})
}
} }
} }

View file

@ -6,6 +6,6 @@
android:viewportHeight="24" android:viewportHeight="24"
android:viewportWidth="24"> android:viewportWidth="24">
<path <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"/> 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> </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

@ -5,8 +5,8 @@
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:pathData="M12,4.531c-4.971,0 -9.209,3.1 -10.912,7.469C2.791,16.368 7.029,19.469 12,19.469s9.209,-3.1 10.912,-7.469C21.209,7.632 16.971,4.531 12,4.531zM12,17.063c-2.796,0 -5.063,-2.267 -5.063,-5.063S9.204,6.938 12,6.938S17.063,9.204 17.063,12S14.796,17.063 12,17.063z" android:pathData="M12,4.531c-4.971,0 -9.209,3.1 -10.912,7.469C2.791,16.368 7.029,19.469 12,19.469s9.209,-3.1 10.912,-7.469C21.209,7.632 16.971,4.531 12,4.531zM12,17.063c-2.796,0 -5.063,-2.267 -5.063,-5.063S9.204,6.938 12,6.938S17.063,9.204 17.063,12S14.796,17.063 12,17.063z"
android:fillColor="#FFFFFF"/> android:fillColor="?attr/colorControlNormal"/>
<path <path
android:pathData="M12,12m-2.938,0a2.938,2.938 0,1 1,5.876 0a2.938,2.938 0,1 1,-5.876 0" android:pathData="M12,12m-2.938,0a2.938,2.938 0,1 1,5.876 0a2.938,2.938 0,1 1,-5.876 0"
android:fillColor="#FFFFFF"/> android:fillColor="?attr/colorControlNormal"/>
</vector> </vector>

View file

@ -0,0 +1,9 @@
<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="M12,7c2.76,0 5,2.24 5,5 0,0.65 -0.13,1.26 -0.36,1.83l2.92,2.92c1.51,-1.26 2.7,-2.89 3.43,-4.75 -1.73,-4.39 -6,-7.5 -11,-7.5 -1.4,0 -2.74,0.25 -3.98,0.7l2.16,2.16C10.74,7.13 11.35,7 12,7zM2,4.27l2.28,2.28 0.46,0.46C3.08,8.3 1.78,10.02 1,12c1.73,4.39 6,7.5 11,7.5 1.55,0 3.03,-0.3 4.38,-0.84l0.42,0.42L19.73,22 21,20.73 3.27,3 2,4.27zM7.53,9.8l1.55,1.55c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.66 1.34,3 3,3 0.22,0 0.44,-0.03 0.65,-0.08l1.55,1.55c-0.67,0.33 -1.41,0.53 -2.2,0.53 -2.76,0 -5,-2.24 -5,-5 0,-0.79 0.2,-1.53 0.53,-2.2zM11.84,9.02l3.15,3.15 0.02,-0.16c0,-1.66 -1.34,-3 -3,-3l-0.17,0.01z" />
</vector>

View file

@ -81,7 +81,7 @@
tools:ignore="RtlSymmetry" /> tools:ignore="RtlSymmetry" />
<ImageView <ImageView
android:id="@+id/handleView" android:id="@+id/sortHandle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:adjustViewBounds="false" android:adjustViewBounds="false"

View file

@ -165,8 +165,9 @@
<CheckBox <CheckBox
android:id="@+id/cb_bolus_remove" android:id="@+id/cb_bolus_remove"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="19dp"
android:contentDescription="@string/select_for_removal" android:contentDescription="@string/select_for_removal"
android:minWidth="0dp"
android:visibility="gone" /> android:visibility="gone" />
<TextView <TextView
@ -254,22 +255,13 @@
android:text="@string/invalid" android:text="@string/invalid"
android:textColor="@android:color/holo_red_light" /> android:textColor="@android:color/holo_red_light" />
<TextView
android:id="@+id/carbs_remove"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:text="@string/remove_button"
android:textAlignment="viewEnd"
android:textColor="@android:color/holo_orange_light" />
<CheckBox <CheckBox
android:id="@+id/cb_carbs_remove" android:id="@+id/cb_carbs_remove"
android:visibility="gone" android:layout_width="wrap_content"
android:layout_width="wrap_content" android:layout_height="19dp"
android:layout_height="wrap_content"/> android:contentDescription="@string/select_for_removal"
android:minWidth="0dp"
android:visibility="gone" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"

View file

@ -115,8 +115,9 @@
<CheckBox <CheckBox
android:id="@+id/cb_remove" android:id="@+id/cb_remove"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="19dp"
android:contentDescription="@string/select_for_removal" android:contentDescription="@string/select_for_removal"
android:minWidth="0dp"
android:visibility="gone" /> android:visibility="gone" />
</LinearLayout> </LinearLayout>

View file

@ -156,8 +156,9 @@
<CheckBox <CheckBox
android:id="@+id/cb_remove" android:id="@+id/cb_remove"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="19dp"
android:contentDescription="@string/select_for_removal" android:contentDescription="@string/select_for_removal"
android:minWidth="0dp"
android:visibility="gone" /> android:visibility="gone" />
<TextView <TextView

View file

@ -127,8 +127,9 @@
<CheckBox <CheckBox
android:id="@+id/cb_remove" android:id="@+id/cb_remove"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="19dp"
android:contentDescription="@string/select_for_removal" android:contentDescription="@string/select_for_removal"
android:minWidth="0dp"
android:visibility="gone" /> android:visibility="gone" />
</LinearLayout> </LinearLayout>

View file

@ -175,8 +175,9 @@
<CheckBox <CheckBox
android:id="@+id/cb_remove" android:id="@+id/cb_remove"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="19dp"
android:contentDescription="@string/select_for_removal" android:contentDescription="@string/select_for_removal"
android:minWidth="0dp"
android:visibility="gone" /> android:visibility="gone" />
<TextView <TextView

View file

@ -150,8 +150,9 @@
<CheckBox <CheckBox
android:id="@+id/cb_remove" android:id="@+id/cb_remove"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="19dp"
android:contentDescription="@string/select_for_removal" android:contentDescription="@string/select_for_removal"
android:minWidth="0dp"
android:visibility="gone" /> android:visibility="gone" />
</LinearLayout> </LinearLayout>

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 <item
android:id="@+id/remove_selected" android:id="@+id/remove_selected"
android:icon="@drawable/ic_trash" android:icon="@drawable/ic_trash"
android:iconTint="?attr/trashBinTintColor" android:iconTint="?attr/colorControlNormal"
android:title="@string/remove_selected_items" android:title="@string/remove_selected_items"
app:showAsAction="always" /> app:showAsAction="ifRoom" />
</menu> </menu>

View file

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

View file

@ -3,20 +3,27 @@
<item <item
android:id="@+id/nav_remove_items" android:id="@+id/nav_remove_items"
android:icon="@drawable/ic_trash"
android:title="@string/remove_items" android:title="@string/remove_items"
app:showAsAction="never" /> app:showAsAction="ifRoom" />
<item <item
android:id="@+id/nav_show_invalidated" android:id="@+id/nav_show_invalidated"
android:icon="@drawable/ic_visibility"
android:title="@string/show_invalidated" android:title="@string/show_invalidated"
app:showAsAction="never" /> app:showAsAction="ifRoom" />
<item <item
android:id="@+id/nav_hide_invalidated" android:id="@+id/nav_hide_invalidated"
android:icon="@drawable/ic_visibility_off"
android:title="@string/hide_invalidated" android:title="@string/hide_invalidated"
app:showAsAction="never" /> app:showAsAction="ifRoom" />
<item <item
android:id="@+id/nav_delete_future" android:id="@+id/nav_delete_future"
android:title="@string/delete_future_treatments" android:title="@string/delete_future_treatments"
app:showAsAction="never" /> app:showAsAction="never" />
<item <item
android:id="@+id/nav_refresh_ns" android:id="@+id/nav_refresh_ns"
android:title="@string/refresh_from_nightscout" android:title="@string/refresh_from_nightscout"

View file

@ -3,20 +3,27 @@
<item <item
android:id="@+id/nav_remove_items" android:id="@+id/nav_remove_items"
android:icon="@drawable/ic_trash"
android:title="@string/remove_items" android:title="@string/remove_items"
app:showAsAction="never" /> app:showAsAction="ifRoom" />
<item <item
android:id="@+id/nav_show_invalidated" android:id="@+id/nav_show_invalidated"
android:icon="@drawable/ic_visibility"
android:title="@string/show_invalidated" android:title="@string/show_invalidated"
app:showAsAction="never" /> app:showAsAction="ifRoom" />
<item <item
android:id="@+id/nav_hide_invalidated" android:id="@+id/nav_hide_invalidated"
android:icon="@drawable/ic_visibility_off"
android:title="@string/hide_invalidated" android:title="@string/hide_invalidated"
app:showAsAction="never" /> app:showAsAction="ifRoom" />
<item <item
android:id="@+id/nav_refresh_ns" android:id="@+id/nav_refresh_ns"
android:title="@string/refresh_from_nightscout" android:title="@string/refresh_from_nightscout"
app:showAsAction="never" /> app:showAsAction="never" />
<item <item
android:id="@+id/nav_remove_started_events" android:id="@+id/nav_remove_started_events"
android:title="@string/careportal_removestartedevents" android:title="@string/careportal_removestartedevents"

View file

@ -3,15 +3,20 @@
<item <item
android:id="@+id/nav_remove_items" android:id="@+id/nav_remove_items"
android:icon="@drawable/ic_trash"
android:title="@string/remove_items" android:title="@string/remove_items"
app:showAsAction="never" /> app:showAsAction="ifRoom" />
<item <item
android:id="@+id/nav_show_invalidated" android:id="@+id/nav_show_invalidated"
android:icon="@drawable/ic_visibility"
android:title="@string/show_invalidated" android:title="@string/show_invalidated"
app:showAsAction="never" /> app:showAsAction="ifRoom" />
<item <item
android:id="@+id/nav_hide_invalidated" android:id="@+id/nav_hide_invalidated"
android:icon="@drawable/ic_visibility_off"
android:title="@string/hide_invalidated" android:title="@string/hide_invalidated"
app:showAsAction="never" /> app:showAsAction="ifRoom" />
</menu> </menu>

View file

@ -3,15 +3,20 @@
<item <item
android:id="@+id/nav_remove_items" android:id="@+id/nav_remove_items"
android:icon="@drawable/ic_trash"
android:title="@string/remove_items" android:title="@string/remove_items"
app:showAsAction="never" /> app:showAsAction="ifRoom" />
<item <item
android:id="@+id/nav_show_invalidated" android:id="@+id/nav_show_invalidated"
android:icon="@drawable/ic_visibility"
android:title="@string/show_invalidated" android:title="@string/show_invalidated"
app:showAsAction="never" /> app:showAsAction="ifRoom" />
<item <item
android:id="@+id/nav_hide_invalidated" android:id="@+id/nav_hide_invalidated"
android:icon="@drawable/ic_visibility_off"
android:title="@string/hide_invalidated" android:title="@string/hide_invalidated"
app:showAsAction="never" /> app:showAsAction="ifRoom" />
</menu> </menu>

View file

@ -3,15 +3,20 @@
<item <item
android:id="@+id/nav_remove_items" android:id="@+id/nav_remove_items"
android:icon="@drawable/ic_trash"
android:title="@string/remove_items" android:title="@string/remove_items"
app:showAsAction="never" /> app:showAsAction="ifRoom" />
<item <item
android:id="@+id/nav_show_invalidated" android:id="@+id/nav_show_invalidated"
android:icon="@drawable/ic_visibility"
android:title="@string/show_invalidated" android:title="@string/show_invalidated"
app:showAsAction="never" /> app:showAsAction="ifRoom" />
<item <item
android:id="@+id/nav_hide_invalidated" android:id="@+id/nav_hide_invalidated"
android:icon="@drawable/ic_visibility_off"
android:title="@string/hide_invalidated" android:title="@string/hide_invalidated"
app:showAsAction="never" /> app:showAsAction="ifRoom" />
</menu> </menu>

View file

@ -3,16 +3,22 @@
<item <item
android:id="@+id/nav_remove_items" android:id="@+id/nav_remove_items"
android:icon="@drawable/ic_trash"
android:title="@string/remove_items" android:title="@string/remove_items"
app:showAsAction="never" /> app:showAsAction="ifRoom" />
<item <item
android:id="@+id/nav_show_invalidated" android:id="@+id/nav_show_invalidated"
android:icon="@drawable/ic_visibility"
android:title="@string/show_invalidated" android:title="@string/show_invalidated"
app:showAsAction="never" /> app:showAsAction="ifRoom" />
<item <item
android:id="@+id/nav_hide_invalidated" android:id="@+id/nav_hide_invalidated"
android:title="@string/hide_invalidated" android:title="@string/hide_invalidated"
app:showAsAction="never" /> android:icon="@drawable/ic_visibility_off"
app:showAsAction="ifRoom" />
<item <item
android:id="@+id/nav_refresh_ns" android:id="@+id/nav_refresh_ns"
android:title="@string/refresh_from_nightscout" android:title="@string/refresh_from_nightscout"

View file

@ -3,12 +3,17 @@
<item <item
android:id="@+id/nav_show_loop" android:id="@+id/nav_show_loop"
android:icon="@drawable/ic_loop_closed_white"
android:iconTint="?attr/colorControlNormal"
android:title="@string/show_loop" android:title="@string/show_loop"
app:showAsAction="never" /> app:showAsAction="ifRoom" />
<item <item
android:id="@+id/nav_hide_loop" android:id="@+id/nav_hide_loop"
android:icon="@drawable/ic_loop_closed_off"
android:title="@string/hide_loop" android:title="@string/hide_loop"
app:showAsAction="never" /> app:showAsAction="ifRoom" />
<item <item
android:id="@+id/nav_export" android:id="@+id/nav_export"
android:title="@string/ue_export_to_csv" android:title="@string/ue_export_to_csv"

View file

@ -1188,6 +1188,8 @@
<string name="wear_unknown_action_string">Unknown action command:</string> <string name="wear_unknown_action_string">Unknown action command:</string>
<string name="overview_editquickwizard_percentage">Percentage</string> <string name="overview_editquickwizard_percentage">Percentage</string>
<string name="app_default">Application default</string> <string name="app_default">Application default</string>
<string name="show_invalidated_records">Show invalidated / removed records</string>
<string name="hide_invalidated_records">Hide invalidated / removed records</string>
<string name="select_profile">Select profile to edit</string> <string name="select_profile">Select profile to edit</string>
<string name="refresh_from_nightscout">Refresh from Nightscout</string> <string name="refresh_from_nightscout">Refresh from Nightscout</string>
<string name="remove_selected_items">Remove selected items</string> <string name="remove_selected_items">Remove selected items</string>
@ -1204,4 +1206,6 @@
<string name="below" comment="below &quot;in range&quot;">Below</string> <string name="below" comment="below &quot;in range&quot;">Below</string>
<string name="in_range">In range</string> <string name="in_range">In range</string>
<string name="above" comment="above &quot;in range&quot;">Above</string> <string name="above" comment="above &quot;in range&quot;">Above</string>
<string name="show_loop_records">Show loop records</string>
<string name="show_hide_records">Hide loop records</string>
</resources> </resources>

View file

@ -6,10 +6,10 @@ import android.os.Bundle
import android.text.method.ScrollingMovementMethod import android.text.method.ScrollingMovementMethod
import android.util.SparseArray import android.util.SparseArray
import android.view.* import android.view.*
import androidx.core.util.forEach
import android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.core.util.forEach
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -20,24 +20,24 @@ import info.nightscout.androidaps.automation.databinding.AutomationEventItemBind
import info.nightscout.androidaps.automation.databinding.AutomationFragmentBinding import info.nightscout.androidaps.automation.databinding.AutomationFragmentBinding
import info.nightscout.androidaps.database.entities.UserEntry.Action import info.nightscout.androidaps.database.entities.UserEntry.Action
import info.nightscout.androidaps.database.entities.UserEntry.Sources import info.nightscout.androidaps.database.entities.UserEntry.Sources
import info.nightscout.androidaps.extensions.toVisibility
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.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
import info.nightscout.androidaps.utils.ActionModeHelper
import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.HtmlHelper 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 info.nightscout.androidaps.utils.dragHelpers.ItemTouchHelperAdapter
import info.nightscout.androidaps.extensions.toVisibility import info.nightscout.androidaps.utils.dragHelpers.OnStartDragListener
import info.nightscout.androidaps.plugins.general.automation.dragHelpers.ItemTouchHelperAdapter import info.nightscout.androidaps.utils.dragHelpers.SimpleItemTouchHelperCallback
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
import java.util.* import io.reactivex.rxjava3.kotlin.plusAssign
import javax.inject.Inject import javax.inject.Inject
class AutomationFragment : DaggerFragment(), OnStartDragListener { class AutomationFragment : DaggerFragment(), OnStartDragListener {
@ -52,11 +52,8 @@ 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 lateinit var actionHelper: ActionModeHelper<AutomationEvent>
private var removeActionMode: ActionMode? = null
private var sortActionMode: ActionMode? = null
private val itemTouchHelper = ItemTouchHelper(SimpleItemTouchHelperCallback()) private val itemTouchHelper = ItemTouchHelper(SimpleItemTouchHelperCallback())
private var _binding: AutomationFragmentBinding? = null private var _binding: AutomationFragmentBinding? = null
// This property is only valid between onCreateView and onDestroyView. // This property is only valid between onCreateView and onDestroyView.
@ -64,20 +61,22 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = AutomationFragmentBinding.inflate(inflater, container, false) _binding = AutomationFragmentBinding.inflate(inflater, container, false)
actionHelper = ActionModeHelper(rh, activity)
actionHelper.setUpdateListHandler { binding.eventListView.adapter?.notifyDataSetChanged() }
actionHelper.setOnRemoveHandler { removeSelected(it) }
actionHelper.enableSort = true
setHasOptionsMenu(actionHelper.inMenu)
return binding.root return binding.root
} }
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() actionHelper.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())
@ -110,8 +109,7 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
@Synchronized @Synchronized
override fun onPause() { override fun onPause() {
removeActionMode?.finish() actionHelper.finish()
sortActionMode?.finish()
super.onPause() super.onPause()
disposable.clear() disposable.clear()
} }
@ -119,38 +117,16 @@ 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) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater) super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.menu_automation, menu) actionHelper.onCreateOptionsMenu(menu, inflater)
} }
override fun onPrepareOptionsMenu(menu: Menu) { override fun onOptionsItemSelected(item: MenuItem): Boolean =
// Only show when tab automation is shown actionHelper.onOptionsItemSelected(item)
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() {
@ -228,59 +204,54 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
for (res in actionIcons) { for (res in actionIcons) {
addImage(res, holder.context, holder.binding.iconLayout) addImage(res, holder.context, holder.binding.iconLayout)
} }
// enabled event holder.binding.aapsLogo.visibility = (automation.systemAction).toVisibility()
// Enabled events
holder.binding.enabled.setOnClickListener { holder.binding.enabled.setOnClickListener {
automation.isEnabled = holder.binding.enabled.isChecked automation.isEnabled = holder.binding.enabled.isChecked
rxBus.send(EventAutomationDataChanged()) rxBus.send(EventAutomationDataChanged())
} }
holder.binding.aapsLogo.visibility = (automation.systemAction).toVisibility() holder.binding.rootLayout.setOnClickListener {
if (actionHelper.isNoAction) {
fun updateSelection(selected: Boolean) {
if (selected) {
selectedItems.put(position, automation)
} else {
selectedItems.remove(position)
}
removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size())
}
holder.binding.rootLayout.setOnTouchListener { _, touchEvent ->
if (touchEvent.actionMasked == MotionEvent.ACTION_UP && sortActionMode == null && removeActionMode == null) {
val dialog = EditEventDialog() val dialog = EditEventDialog()
val args = Bundle() val args = Bundle()
args.putString("event", automation.toJSON()) args.putString("event", automation.toJSON())
args.putInt("position", position) args.putInt("position", position)
dialog.arguments = args dialog.arguments = args
dialog.show(childFragmentManager, "EditEventDialog") dialog.show(childFragmentManager, "EditEventDialog")
} } else if (actionHelper.isRemoving) {
if (touchEvent.actionMasked == MotionEvent.ACTION_DOWN && sortActionMode != null) {
onStartDrag(holder)
}
if (touchEvent.actionMasked == MotionEvent.ACTION_UP && removeActionMode != null) {
holder.binding.cbRemove.toggle() holder.binding.cbRemove.toggle()
updateSelection(holder.binding.cbRemove.isChecked) actionHelper.updateSelection(position, automation, holder.binding.cbRemove.isChecked)
removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size())
} }
return@setOnTouchListener true
} }
holder.binding.cbRemove.isChecked = selectedItems.get(position) != null holder.binding.rootLayout.setOnLongClickListener {
holder.binding.cbRemove.setOnCheckedChangeListener { _, value -> updateSelection(value) } actionHelper.startAction()
holder.binding.iconSort.visibility = (sortActionMode != null).toVisibility() }
holder.binding.cbRemove.visibility = (removeActionMode != null).toVisibility() holder.binding.sortHandle.setOnTouchListener { _, touchEvent ->
holder.binding.cbRemove.isEnabled = !automation.readOnly if (touchEvent.actionMasked == MotionEvent.ACTION_DOWN) {
holder.binding.enabled.visibility = if (removeActionMode == null) View.VISIBLE else View.INVISIBLE onStartDrag(holder)
return@setOnTouchListener true
}
return@setOnTouchListener false
}
holder.binding.cbRemove.setOnCheckedChangeListener { _, value ->
actionHelper.updateSelection(position, automation, value)
}
holder.binding.cbRemove.isChecked = actionHelper.isSelected(position)
holder.binding.sortHandle.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 { override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
binding.eventListView.adapter?.notifyItemMoved(fromPosition, toPosition)
automationPlugin.swap(fromPosition, toPosition) automationPlugin.swap(fromPosition, toPosition)
return true return true
} }
override fun onDrop() { override fun onDrop() = rxBus.send(EventAutomationDataChanged())
rxBus.send(EventAutomationDataChanged())
}
inner class ViewHolder(view: View, val context: Context) : RecyclerView.ViewHolder(view) { inner class ViewHolder(view: View, val context: Context) : RecyclerView.ViewHolder(view) {
@ -288,54 +259,7 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
} }
} }
inner class SortActionModeCallback : ActionMode.Callback { private fun getConfirmationText(selectedItems: SparseArray<AutomationEvent>): String {
override fun onCreateActionMode(mode: ActionMode, menu: Menu?): Boolean {
mode.title = rh.gs(R.string.sort_label)
binding.eventListView.adapter?.notifyDataSetChanged()
return true
}
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?) = false
override fun onActionItemClicked(mode: ActionMode, item: MenuItem) = false
override fun onDestroyActionMode(mode: ActionMode?) {
sortActionMode = null
binding.eventListView.adapter?.notifyDataSetChanged()
}
}
inner class RemoveActionModeCallback : ActionMode.Callback {
override fun onCreateActionMode(mode: ActionMode, menu: Menu?): Boolean {
mode.menuInflater.inflate(R.menu.menu_delete_selection, menu)
selectedItems.clear()
mode.title = rh.gs(R.string.count_selected, selectedItems.size())
binding.eventListView.adapter?.notifyDataSetChanged()
return true
}
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?) = false
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
return when (item.itemId) {
R.id.remove_selected -> {
removeSelected()
true
}
else -> false
}
}
override fun onDestroyActionMode(mode: ActionMode?) {
removeActionMode = null
binding.eventListView.adapter?.notifyDataSetChanged()
}
}
private fun getConfirmationText(): String {
if (selectedItems.size() == 1) { if (selectedItems.size() == 1) {
val event = selectedItems.valueAt(0) val event = selectedItems.valueAt(0)
return rh.gs(R.string.removerecord) + " " + event.title return rh.gs(R.string.removerecord) + " " + event.title
@ -343,20 +267,17 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener {
return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size()) return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size())
} }
private fun removeSelected() { private fun removeSelected(selectedItems: SparseArray<AutomationEvent>) {
if (selectedItems.size() > 0) { activity?.let { activity ->
activity?.let { activity -> OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(selectedItems), Runnable {
OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(), Runnable { selectedItems.forEach { _, event ->
selectedItems.forEach { _, event -> uel.log(Action.AUTOMATION_REMOVED, Sources.Automation, event.title)
uel.log(Action.AUTOMATION_REMOVED, Sources.Automation, event.title) automationPlugin.removeAt(event.position)
automationPlugin.removeAt(event.position) rxBus.send(EventAutomationDataChanged())
rxBus.send(EventAutomationDataChanged()) }
} actionHelper.finish()
removeActionMode?.finish() })
})
}
} else {
removeActionMode?.finish()
} }
} }
} }

View file

@ -55,12 +55,12 @@
android:contentDescription="@string/remove_label" android:contentDescription="@string/remove_label"
android:orientation="horizontal" android:orientation="horizontal"
app:layout_constraintBottom_toTopOf="@+id/iconLayout" app:layout_constraintBottom_toTopOf="@+id/iconLayout"
app:layout_constraintEnd_toStartOf="@+id/iconSort" app:layout_constraintEnd_toStartOf="@+id/sortHandle"
app:layout_constraintStart_toEndOf="@+id/eventTitle" app:layout_constraintStart_toEndOf="@+id/eventTitle"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<ImageView <ImageView
android:id="@+id/iconSort" android:id="@+id/sortHandle"
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:layout_marginEnd="8dp"

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,191 @@
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 ActionModeHelper<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(): Boolean {
if (isNoAction) {
actionMode = activity?.startActionMode(ActionModeCallback())
return true
}
return false
}
fun startRemove(): Boolean {
if (removeActionMode == null) {
removeActionMode = activity?.startActionMode(RemoveActionModeCallback())
return true
}
return false
}
fun startSort(): Boolean {
if (sortActionMode == null) {
sortActionMode = activity?.startActionMode(SortActionModeCallback())
return true
}
return false
}
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

@ -1,4 +1,4 @@
package info.nightscout.androidaps.plugins.general.automation.dragHelpers package info.nightscout.androidaps.utils.dragHelpers
interface ItemTouchHelperAdapter { interface ItemTouchHelperAdapter {

View file

@ -1,4 +1,4 @@
package info.nightscout.androidaps.plugins.general.automation.dragHelpers package info.nightscout.androidaps.utils.dragHelpers
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -9,4 +9,4 @@ interface OnStartDragListener {
* @param viewHolder The holder of the view to drag. * @param viewHolder The holder of the view to drag.
*/ */
fun onStartDrag(viewHolder: RecyclerView.ViewHolder) fun onStartDrag(viewHolder: RecyclerView.ViewHolder)
} }

View file

@ -1,21 +1,25 @@
package info.nightscout.androidaps.plugins.general.automation.dragHelpers package info.nightscout.androidaps.utils.dragHelpers
import androidx.recyclerview.widget.ItemTouchHelper 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.RecyclerView import androidx.recyclerview.widget.RecyclerView
import info.nightscout.androidaps.plugins.general.automation.AutomationFragment
class SimpleItemTouchHelperCallback : ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP or ItemTouchHelper.DOWN or ItemTouchHelper.START or ItemTouchHelper.END, 0) { const val ALPHA_FULL = 1f
const val ALPHA_DRAGGING = 0.5f
override fun isLongPressDragEnabled(): Boolean { class SimpleItemTouchHelperCallback : ItemTouchHelper.SimpleCallback(UP or DOWN or START or END, 0) {
return false
} override fun isLongPressDragEnabled() = false
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean { override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
val adapter = recyclerView.adapter as AutomationFragment.EventListAdapter val adapter = recyclerView.adapter as ItemTouchHelperAdapter
val from = viewHolder.layoutPosition val from = viewHolder.layoutPosition
val to = target.layoutPosition val to = target.layoutPosition
adapter.onItemMove(from, to) adapter.onItemMove(from, to)
adapter.notifyItemMoved(from, to)
return true return true
} }
@ -24,14 +28,14 @@ class SimpleItemTouchHelperCallback : ItemTouchHelper.SimpleCallback(ItemTouchHe
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) { override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
super.onSelectedChanged(viewHolder, actionState) super.onSelectedChanged(viewHolder, actionState)
if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) { if (actionState == ACTION_STATE_DRAG) {
viewHolder?.itemView?.alpha = 0.5f viewHolder?.itemView?.alpha = ALPHA_DRAGGING
} }
} }
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 = 1.0f viewHolder.itemView.alpha = ALPHA_FULL
(recyclerView.adapter as AutomationFragment.EventListAdapter).onDrop() (recyclerView.adapter as ItemTouchHelperAdapter).onDrop()
} }
} }

View file

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M19.255,18.907l-1.967,-1.967l0,0L6.801,6.453L5.218,4.87l0,0l-1.88,-1.88L2.041,4.286l1.971,1.971C2.829,7.89 2.125,9.891 2.125,12.063c0,5.488 4.449,9.938 9.938,9.938c2.171,0 4.172,-0.704 5.805,-1.887l1.91,1.91l1.296,-1.296L19.255,18.907L19.255,18.907zM12.063,19.219c-3.952,0 -7.156,-3.204 -7.156,-7.156c0,-1.401 0.412,-2.7 1.109,-3.802l9.849,9.849C14.763,18.807 13.463,19.219 12.063,19.219z" />
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M12.063,4.906c1.072,0 2.085,0.242 2.998,0.665c0.325,0.151 0.639,0.321 0.936,0.517l0.002,-0.002l-0.352,-1.784l1.876,-0.538c-1.567,-1.033 -3.442,-1.639 -5.46,-1.639c-1.991,0 -3.842,0.592 -5.397,1.6l2.026,2.026C9.696,5.213 10.843,4.906 12.063,4.906z" />
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M24,9.343l-5.781,-3.968l-1.328,6.688l2.102,-1.757c0.014,0.056 0.03,0.111 0.043,0.168c0.116,0.512 0.183,1.042 0.183,1.589c0,1.219 -0.307,2.366 -0.844,3.371l2.025,2.025c1.009,-1.555 1.6,-3.405 1.6,-5.397c0,-0.759 -0.093,-1.496 -0.254,-2.206c-0.04,-0.176 -0.085,-0.35 -0.134,-0.523L24,9.343L24,9.343z" />
</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

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

@ -1,14 +1,19 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu 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">
<item <item
android:id="@+id/nav_remove_items" android:id="@+id/nav_remove_items"
android:title="@string/remove_items" android:icon="@drawable/ic_trash"
app:showAsAction="never" /> android:iconTint="?attr/colorControlNormal"
android:orderInCategory="0"
<item android:title="@string/remove_items"
android:id="@+id/nav_sort_items" app:showAsAction="ifRoom" />
android:title="@string/sort_items"
app:showAsAction="never" /> <item
android:id="@+id/nav_sort_items"
</menu> android:icon="@drawable/ic_sort"
android:orderInCategory="0"
android:title="@string/sort_items"
app:showAsAction="ifRoom" />
</menu>

View file

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

View file

@ -556,6 +556,11 @@
<string name="formatPercent">%1$.0f%%</string> <string name="formatPercent">%1$.0f%%</string>
<string name="basal">Basal</string> <string name="basal">Basal</string>
<string name="basalpct">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"> <plurals name="days">
<item quantity="one">%1$d day</item> <item quantity="one">%1$d day</item>

19
icons/loop_off.svg Normal file
View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="24px"
height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
<g id="loop_x5F_Off">
<rect width="24" height="24"/>
<g>
<path fill="#FFFFFF" d="M19.255,18.907l-1.967-1.967l0,0L6.801,6.453L5.218,4.87l0,0l-1.88-1.88L2.041,4.286l1.971,1.971
C2.829,7.89,2.125,9.891,2.125,12.063c0,5.488,4.449,9.938,9.938,9.938c2.171,0,4.172-0.704,5.805-1.887l1.91,1.91l1.296-1.296
L19.255,18.907L19.255,18.907z M12.063,19.219c-3.952,0-7.156-3.204-7.156-7.156c0-1.401,0.412-2.7,1.109-3.802l9.849,9.849
C14.763,18.807,13.463,19.219,12.063,19.219z"/>
<path fill="#FFFFFF" d="M12.063,4.906c1.072,0,2.085,0.242,2.998,0.665c0.325,0.151,0.639,0.321,0.936,0.517l0.002-0.002
l-0.352-1.784l1.876-0.538c-1.567-1.033-3.442-1.639-5.46-1.639c-1.991,0-3.842,0.592-5.397,1.6l2.026,2.026
C9.696,5.213,10.843,4.906,12.063,4.906z"/>
<path fill="#FFFFFF" d="M24,9.343l-5.781-3.968l-1.328,6.688l2.102-1.757c0.014,0.056,0.03,0.111,0.043,0.168
c0.116,0.512,0.183,1.042,0.183,1.589c0,1.219-0.307,2.366-0.844,3.371l2.025,2.025c1.009-1.555,1.6-3.405,1.6-5.397
c0-0.759-0.093-1.496-0.254-2.206c-0.04-0.176-0.085-0.35-0.134-0.523L24,9.343L24,9.343z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB