diff --git a/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsBolusCarbsFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsBolusCarbsFragment.kt index e9108341f6..8a88149f30 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsBolusCarbsFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsBolusCarbsFragment.kt @@ -5,9 +5,6 @@ import android.graphics.Paint import android.os.Bundle import android.util.SparseArray import android.view.* -import android.widget.CompoundButton -import android.view.ActionMode -import androidx.appcompat.widget.Toolbar import androidx.core.util.forEach import androidx.recyclerview.widget.LinearLayoutManager 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.general.nsclient.events.EventNSClientRestart 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.FabricPrivacy import info.nightscout.androidaps.utils.T +import info.nightscout.androidaps.utils.ToastUtils import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.utils.buildHelper.BuildHelper import info.nightscout.androidaps.utils.resources.ResourceHelper @@ -71,8 +70,10 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() { @Inject lateinit var activePlugin: ActivePlugin private var _binding: TreatmentsBolusCarbsFragmentBinding? = null + // This property is only valid between onCreateView and onDestroyView. private val binding get() = _binding!! + private var menu: Menu? = null class MealLink( val bolus: Bolus? = null, @@ -81,21 +82,22 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() { ) private val disposable = CompositeDisposable() + private lateinit var actionHelper: ActionModeHelper private val millsToThePast = T.days(30).msecs() - private var selectedItems: SparseArray = SparseArray() + // private var selectedItems: SparseArray = SparseArray() private var showInvalidated = false - private var removeActionMode: ActionMode? = null - private var toolbar: Toolbar? = null override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = TreatmentsBolusCarbsFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root - @SuppressLint("CheckResult") + @SuppressLint("NotifyDataSetChanged") 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) - toolbar = activity?.findViewById(R.id.toolbar) binding.recyclerview.setHasFixedSize(true) binding.recyclerview.layoutManager = LinearLayoutManager(view.context) } @@ -127,7 +129,7 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() { fun swapAdapter() { val now = System.currentTimeMillis() - disposable += + disposable += if (showInvalidated) carbsMealLinksWithInvalid(now) .zipWith(bolusMealLinksWithInvalid(now)) { first, second -> first + second } @@ -183,13 +185,13 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() { @Synchronized override fun onPause() { super.onPause() + actionHelper.finish() disposable.clear() } @Synchronized override fun onDestroyView() { super.onDestroyView() - removeActionMode?.finish() binding.recyclerview.adapter = null // avoid leaks _binding = null } @@ -243,6 +245,17 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() { Bolus.Type.NORMAL -> rh.gs(R.string.mealbolus) 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 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.carbsPump.visibility = (carbs.interfaceIDs.pumpId != null).toVisibility() holder.binding.carbsInvalid.visibility = carbs.isValid.not().toVisibility() - } - holder.binding.cbBolusRemove.visibility = (ml.bolus?.isValid == true && removeActionMode != null).toVisibility() - holder.binding.cbCarbsRemove.visibility = (ml.carbs?.isValid == true && removeActionMode != null).toVisibility() - if (removeActionMode != null) { - val onChange = CompoundButton.OnCheckedChangeListener { _, value -> - if (value) { - selectedItems.put(position, ml) - } else { - selectedItems.remove(position) + holder.binding.cbCarbsRemove.visibility = (ml.carbs.isValid && actionHelper.isRemoving).toVisibility() + if (actionHelper.isRemoving) { + holder.binding.cbCarbsRemove.setOnCheckedChangeListener { _, value -> + actionHelper.updateSelection(position, ml, value) } - 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 val nextTimestamp = if (mealLinks.size != position + 1) timestamp(mealLinks[position + 1]) else 0L holder.binding.delimiter.visibility = dateUtil.isSameDayGroup(timestamp(ml), nextTimestamp).toVisibility() @@ -297,13 +306,18 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() { } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + this.menu = menu inflater.inflate(R.menu.menu_treatments_carbs_bolus, menu) 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) { - menu.findItem(R.id.nav_hide_invalidated)?.isVisible = showInvalidated - menu.findItem(R.id.nav_show_invalidated)?.isVisible = !showInvalidated + updateMenuVisibility() 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 val hasItems = (binding.recyclerview.adapter?.itemCount ?: 0) > 0 @@ -314,19 +328,20 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() { override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) { - R.id.nav_remove_items -> { - removeActionMode = toolbar?.startActionMode(RemoveActionModeCallback()) - true - } + R.id.nav_remove_items -> actionHelper.startRemove() R.id.nav_show_invalidated -> { showInvalidated = true + updateMenuVisibility() + ToastUtils.showToastInUiThread(context, rh.gs(R.string.show_invalidated_records)) rxBus.send(EventTreatmentUpdateGui()) true } R.id.nav_hide_invalidated -> { showInvalidated = false + updateMenuVisibility() + ToastUtils.showToastInUiThread(context, rh.gs(R.string.hide_invalidated_records)) rxBus.send(EventTreatmentUpdateGui()) true } @@ -423,36 +438,7 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() { } } - inner class RemoveActionModeCallback : ActionMode.Callback { - - override fun onCreateActionMode(mode: ActionMode, menu: Menu?): Boolean { - mode.menuInflater.inflate(R.menu.menu_delete_selection, menu) - selectedItems.clear() - mode.title = rh.gs(R.string.count_selected, selectedItems.size()) - binding.recyclerview.adapter?.notifyDataSetChanged() - return true - } - - override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?) = false - - override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { - return when (item.itemId) { - R.id.remove_selected -> { - removeSelected() - true - } - - else -> false - } - } - - override fun onDestroyActionMode(mode: ActionMode?) { - removeActionMode = null - binding.recyclerview.adapter?.notifyDataSetChanged() - } - } - - private fun getConfirmationText(): String { + private fun getConfirmationText(selectedItems: SparseArray): String { if (selectedItems.size() == 1) { val mealLink = selectedItems.valueAt(0) val bolus = mealLink.bolus @@ -467,40 +453,38 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() { return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size()) } - fun removeSelected() { - if (selectedItems.size() > 0) - activity?.let { activity -> - OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(), Runnable { - selectedItems.forEach { _, ml -> - ml.bolus?.let { bolus -> - uel.log( - Action.BOLUS_REMOVED, Sources.Treatments, - ValueWithUnit.Timestamp(bolus.timestamp), - ValueWithUnit.Insulin(bolus.amount) + private fun removeSelected(selectedItems: SparseArray) { + activity?.let { activity -> + OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(selectedItems), Runnable { + selectedItems.forEach { _, ml -> + ml.bolus?.let { bolus -> + uel.log( + Action.BOLUS_REMOVED, Sources.Treatments, + ValueWithUnit.Timestamp(bolus.timestamp), + 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() - }) - } - else - removeActionMode?.finish() + 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) } + ) + } + } + actionHelper.finish() + }) + } } + } diff --git a/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsCareportalFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsCareportalFragment.kt index 0f7fb29725..32732b7ea9 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsCareportalFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsCareportalFragment.kt @@ -3,39 +3,34 @@ package info.nightscout.androidaps.activities.fragments import android.os.Bundle import android.util.SparseArray import android.view.* -import android.view.ActionMode -import androidx.appcompat.widget.Toolbar import androidx.core.util.forEach import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import dagger.android.support.DaggerFragment 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.entities.ValueWithUnit import info.nightscout.androidaps.database.entities.TherapyEvent import info.nightscout.androidaps.database.entities.UserEntry.Action 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.InvalidateTherapyEventTransaction import info.nightscout.androidaps.databinding.TreatmentsCareportalFragmentBinding import info.nightscout.androidaps.databinding.TreatmentsCareportalItemBinding import info.nightscout.androidaps.events.EventTherapyEventChange -import info.nightscout.shared.logging.AAPSLogger -import info.nightscout.shared.logging.LTag +import info.nightscout.androidaps.events.EventTreatmentUpdateGui +import info.nightscout.androidaps.extensions.toVisibility import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.plugins.bus.RxBus import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart -import info.nightscout.androidaps.events.EventTreatmentUpdateGui -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.* import info.nightscout.androidaps.utils.alertDialogs.OKDialog 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.rx.AapsSchedulers +import info.nightscout.shared.logging.AAPSLogger +import info.nightscout.shared.logging.LTag import info.nightscout.shared.sharedPreferences.SP import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.disposables.CompositeDisposable @@ -59,23 +54,23 @@ class TreatmentsCareportalFragment : DaggerFragment() { @Inject lateinit var uel: UserEntryLogger private var _binding: TreatmentsCareportalFragmentBinding? = null + // This property is only valid between onCreateView and onDestroyView. private val binding get() = _binding!! - + private var menu: Menu? = null private val disposable = CompositeDisposable() private val millsToThePast = T.days(30).msecs() - private var selectedItems: SparseArray = SparseArray() + private lateinit var actionHelper: ActionModeHelper private var showInvalidated = false - private var toolbar: Toolbar? = null - private var removeActionMode: ActionMode? = null - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = TreatmentsCareportalFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 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) binding.recyclerview.setHasFixedSize(true) binding.recyclerview.layoutManager = LinearLayoutManager(view.context) @@ -143,13 +138,13 @@ class TreatmentsCareportalFragment : DaggerFragment() { @Synchronized override fun onPause() { super.onPause() + actionHelper.finish() disposable.clear() } @Synchronized override fun onDestroyView() { super.onDestroyView() - removeActionMode?.finish() binding.recyclerview.adapter = null // avoid leaks _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.note.text = therapyEvent.note holder.binding.type.text = translator.translate(therapyEvent.type) - holder.binding.cbRemove.visibility = (therapyEvent.isValid && removeActionMode != null).toVisibility() - if (removeActionMode != null) { - holder.binding.cbRemove.setOnCheckedChangeListener { _, 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.cbRemove.visibility = (therapyEvent.isValid && actionHelper.isRemoving).toVisibility() + holder.binding.cbRemove.setOnCheckedChangeListener { _, value -> + actionHelper.updateSelection(position, therapyEvent, value) } + 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 holder.binding.delimiter.visibility = dateUtil.isSameDayGroup(therapyEvent.timestamp, nextTimestamp).toVisibility() } @@ -198,34 +190,40 @@ class TreatmentsCareportalFragment : DaggerFragment() { } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + this.menu = menu inflater.inflate(R.menu.menu_treatments_careportal, menu) super.onCreateOptionsMenu(menu, inflater) } override fun onPrepareOptionsMenu(menu: Menu) { - menu.findItem(R.id.nav_hide_invalidated)?.isVisible = showInvalidated - menu.findItem(R.id.nav_show_invalidated)?.isVisible = !showInvalidated + updateMenuVisibility() val nsUploadOnly = !sp.getBoolean(R.string.key_ns_receive_therapy_events, false) || !buildHelper.isEngineeringMode() menu.findItem(R.id.nav_refresh_ns)?.isVisible = !nsUploadOnly 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 = when (item.itemId) { - R.id.nav_remove_items -> { - removeActionMode = toolbar?.startActionMode(RemoveActionModeCallback()) - true - } + R.id.nav_remove_items -> actionHelper.startRemove() R.id.nav_show_invalidated -> { showInvalidated = true + updateMenuVisibility() + ToastUtils.showToastInUiThread(context, rh.gs(R.string.show_invalidated_records)) rxBus.send(EventTreatmentUpdateGui()) true } R.id.nav_hide_invalidated -> { showInvalidated = false + updateMenuVisibility() + ToastUtils.showToastInUiThread(context, rh.gs(R.string.hide_invalidated_records)) rxBus.send(EventTreatmentUpdateGui()) true } @@ -243,36 +241,7 @@ class TreatmentsCareportalFragment : DaggerFragment() { else -> false } - inner class RemoveActionModeCallback : ActionMode.Callback { - - override fun onCreateActionMode(mode: ActionMode, menu: Menu?): Boolean { - mode.menuInflater.inflate(R.menu.menu_delete_selection, menu) - selectedItems.clear() - mode.title = rh.gs(R.string.count_selected, selectedItems.size()) - binding.recyclerview.adapter?.notifyDataSetChanged() - return true - } - - override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?) = false - - override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { - return when (item.itemId) { - R.id.remove_selected -> { - removeSelected() - true - } - - else -> false - } - } - - override fun onDestroyActionMode(mode: ActionMode?) { - removeActionMode = null - binding.recyclerview.adapter?.notifyDataSetChanged() - } - } - - private fun getConfirmationText(): String { + private fun getConfirmationText(selectedItems: SparseArray): String { if (selectedItems.size() == 1) { val therapyEvent = selectedItems.valueAt(0) 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()) } - private fun removeSelected() { - if (selectedItems.size() > 0) - activity?.let { activity -> - OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(), Runnable { - selectedItems.forEach { _, therapyEvent -> - uel.log( - Action.CAREPORTAL_REMOVED, Sources.Treatments, therapyEvent.note, - ValueWithUnit.Timestamp(therapyEvent.timestamp), - ValueWithUnit.TherapyEventType(therapyEvent.type) + private fun removeSelected(selectedItems: SparseArray) { + activity?.let { activity -> + OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(selectedItems), Runnable { + selectedItems.forEach { _, therapyEvent -> + uel.log( + Action.CAREPORTAL_REMOVED, Sources.Treatments, therapyEvent.note, + ValueWithUnit.Timestamp(therapyEvent.timestamp), + 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( - { 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() + } + actionHelper.finish() + }) + } } } diff --git a/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsExtendedBolusesFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsExtendedBolusesFragment.kt index 10d09ca04d..fd098e3759 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsExtendedBolusesFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsExtendedBolusesFragment.kt @@ -1,15 +1,15 @@ package info.nightscout.androidaps.activities.fragments +import android.annotation.SuppressLint import android.os.Bundle import android.util.SparseArray import android.view.* -import android.view.ActionMode -import androidx.appcompat.widget.Toolbar import androidx.core.util.forEach import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import dagger.android.support.DaggerFragment 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.entities.ExtendedBolus 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.TreatmentsExtendedbolusItemBinding import info.nightscout.androidaps.events.EventExtendedBolusChange +import info.nightscout.androidaps.events.EventTreatmentUpdateGui import info.nightscout.androidaps.extensions.iobCalc import info.nightscout.androidaps.extensions.isInProgress import info.nightscout.androidaps.extensions.toVisibility import info.nightscout.androidaps.interfaces.ActivePlugin import info.nightscout.androidaps.interfaces.ProfileFunction -import info.nightscout.shared.logging.AAPSLogger import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.plugins.bus.RxBus -import info.nightscout.androidaps.activities.fragments.TreatmentsExtendedBolusesFragment.RecyclerViewAdapter.ExtendedBolusesViewHolder -import info.nightscout.androidaps.events.EventTreatmentUpdateGui +import info.nightscout.androidaps.utils.ActionModeHelper import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.T +import info.nightscout.androidaps.utils.ToastUtils import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.utils.resources.ResourceHelper 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.kotlin.plusAssign -import info.nightscout.shared.logging.LTag import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -60,20 +61,23 @@ class TreatmentsExtendedBolusesFragment : DaggerFragment() { @Inject lateinit var repository: AppRepository private var _binding: TreatmentsExtendedbolusFragmentBinding? = null + // This property is only valid between onCreateView and onDestroyView. private val binding get() = _binding!! - - private var selectedItems: SparseArray = SparseArray() + private var menu: Menu? = null + private lateinit var actionHelper: ActionModeHelper private var showInvalidated = false - private var removeActionMode: ActionMode? = null - private var toolbar: Toolbar? = null override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = TreatmentsExtendedbolusFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root + @SuppressLint("NotifyDataSetChanged") 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) - toolbar = activity?.findViewById(R.id.toolbar) binding.recyclerview.setHasFixedSize(true) binding.recyclerview.layoutManager = LinearLayoutManager(view.context) } @@ -96,24 +100,28 @@ class TreatmentsExtendedBolusesFragment : DaggerFragment() { override fun onResume() { super.onResume() swapAdapter() - disposable += rxBus .toObservable(EventExtendedBolusChange::class.java) .observeOn(aapsSchedulers.io) .debounce(1L, TimeUnit.SECONDS) .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 override fun onPause() { super.onPause() + actionHelper.finish() disposable.clear() } @Synchronized override fun onDestroyView() { super.onDestroyView() - removeActionMode?.finish() binding.recyclerview.adapter = null // avoid leaks _binding = null } @@ -147,17 +155,16 @@ class TreatmentsExtendedBolusesFragment : DaggerFragment() { holder.binding.iob.text = rh.gs(R.string.formatinsulinunits, iob.iob) 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) - holder.binding.cbRemove.visibility = (extendedBolus.isValid && removeActionMode != null).toVisibility() - if (removeActionMode != null) { + holder.binding.cbRemove.visibility = (extendedBolus.isValid && actionHelper.isRemoving).toVisibility() + if (actionHelper.isRemoving) { holder.binding.cbRemove.setOnCheckedChangeListener { _, value -> - if (value) { - selectedItems.put(position, extendedBolus) - } else { - selectedItems.remove(position) - } - removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size()) + actionHelper.updateSelection(position, extendedBolus, value) } - 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 holder.binding.delimiter.visibility = dateUtil.isSameDayGroup(extendedBolus.timestamp, nextTimestamp).toVisibility() @@ -173,32 +180,37 @@ class TreatmentsExtendedBolusesFragment : DaggerFragment() { } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + this.menu = menu inflater.inflate(R.menu.menu_treatments_extended_bolus, menu) super.onCreateOptionsMenu(menu, inflater) } - override fun onPrepareOptionsMenu(menu: Menu) { - menu.findItem(R.id.nav_hide_invalidated)?.isVisible = showInvalidated - menu.findItem(R.id.nav_show_invalidated)?.isVisible = !showInvalidated + 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) { + updateMenuVisibility() return super.onPrepareOptionsMenu(menu) } override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { - R.id.nav_remove_items -> { - removeActionMode = toolbar?.startActionMode(RemoveActionModeCallback()) - true - } + R.id.nav_remove_items -> actionHelper.startRemove() R.id.nav_show_invalidated -> { showInvalidated = true + updateMenuVisibility() + ToastUtils.showToastInUiThread(context, rh.gs(R.string.show_invalidated_records)) rxBus.send(EventTreatmentUpdateGui()) true } R.id.nav_hide_invalidated -> { showInvalidated = false + updateMenuVisibility() + ToastUtils.showToastInUiThread(context, rh.gs(R.string.hide_invalidated_records)) rxBus.send(EventTreatmentUpdateGui()) true } @@ -207,36 +219,7 @@ class TreatmentsExtendedBolusesFragment : DaggerFragment() { } } - inner class RemoveActionModeCallback : ActionMode.Callback { - - override fun onCreateActionMode(mode: ActionMode, menu: Menu?): Boolean { - mode.menuInflater.inflate(R.menu.menu_delete_selection, menu) - selectedItems.clear() - mode.title = rh.gs(R.string.count_selected, selectedItems.size()) - binding.recyclerview.adapter?.notifyDataSetChanged() - return true - } - - override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?) = false - - override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { - return when (item.itemId) { - R.id.remove_selected -> { - removeSelected() - true - } - - else -> false - } - } - - override fun onDestroyActionMode(mode: ActionMode?) { - removeActionMode = null - binding.recyclerview.adapter?.notifyDataSetChanged() - } - } - - private fun getConfirmationText(): String { + private fun getConfirmationText(selectedItems: SparseArray): String { if (selectedItems.size() == 1) { val bolus = selectedItems.valueAt(0) 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()) } - private fun removeSelected() { - if (selectedItems.size() > 0) - activity?.let { activity -> - OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(), Runnable { - selectedItems.forEach { _, extendedBolus -> - 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) }) - } - removeActionMode?.finish() - }) - } - else - removeActionMode?.finish() + private fun removeSelected(selectedItems: SparseArray) { + activity?.let { activity -> + OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(selectedItems), Runnable { + selectedItems.forEach { _, extendedBolus -> + 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) }) + } + actionHelper.finish() + }) + } } + } diff --git a/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsProfileSwitchFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsProfileSwitchFragment.kt index c825194033..12193867b2 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsProfileSwitchFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsProfileSwitchFragment.kt @@ -1,11 +1,10 @@ package info.nightscout.androidaps.activities.fragments +import android.annotation.SuppressLint import android.graphics.Paint import android.os.Bundle import android.util.SparseArray import android.view.* -import android.view.ActionMode -import androidx.appcompat.widget.Toolbar import androidx.core.util.forEach import androidx.recyclerview.widget.LinearLayoutManager 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.profile.local.LocalProfilePlugin 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.FabricPrivacy import info.nightscout.androidaps.utils.T +import info.nightscout.androidaps.utils.ToastUtils import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.utils.buildHelper.BuildHelper 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.kotlin.plusAssign import io.reactivex.rxjava3.kotlin.subscribeBy +import java.util.concurrent.TimeUnit import javax.inject.Inject class TreatmentsProfileSwitchFragment : DaggerFragment() { @@ -63,24 +65,25 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() { @Inject lateinit var uel: UserEntryLogger private var _binding: TreatmentsProfileswitchFragmentBinding? = null + // This property is only valid between onCreateView and onDestroyView. private val binding get() = _binding!! - + private var menu: Menu? = null + private lateinit var actionHelper: ActionModeHelper private val disposable = CompositeDisposable() private val millsToThePast = T.days(30).msecs() - private var selectedItems: SparseArray = SparseArray() private var showInvalidated = false - private var removeActionMode: ActionMode? = null - private var toolbar: Toolbar? = null - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = TreatmentsProfileswitchFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root + @SuppressLint("NotifyDataSetChanged") 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) - toolbar = activity?.findViewById(R.id.toolbar) binding.recyclerview.setHasFixedSize(true) binding.recyclerview.layoutManager = LinearLayoutManager(view.context) } @@ -159,18 +162,23 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() { .toObservable(EventEffectiveProfileSwitchChanged::class.java) .observeOn(aapsSchedulers.main) .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 override fun onPause() { super.onPause() + actionHelper.finish() disposable.clear() } @Synchronized override fun onDestroyView() { super.onDestroyView() - removeActionMode?.finish() binding.recyclerview.adapter = null // avoid leaks _binding = null } @@ -198,17 +206,16 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() { holder.binding.date.tag = profileSwitch holder.binding.invalid.visibility = profileSwitch.isValid.not().toVisibility() holder.binding.duration.visibility = (profileSwitch.duration != 0L && profileSwitch.duration != null).toVisibility() - holder.binding.cbRemove.visibility = (removeActionMode != null && profileSwitch is ProfileSealed.PS).toVisibility() - if (removeActionMode != null) { + holder.binding.cbRemove.visibility = (actionHelper.isRemoving && profileSwitch is ProfileSealed.PS).toVisibility() + if (actionHelper.isRemoving) { holder.binding.cbRemove.setOnCheckedChangeListener { _, value -> - if (value) { - selectedItems.put(position, profileSwitch) - } else { - selectedItems.remove(position) - } - removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size()) + actionHelper.updateSelection(position, profileSwitch, value) } - 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.spacer.visibility = (profileSwitch is ProfileSealed.PS).toVisibility() @@ -273,13 +280,18 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() { } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + this.menu = menu inflater.inflate(R.menu.menu_treatments_profile_switch, menu) 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) { - menu.findItem(R.id.nav_hide_invalidated)?.isVisible = showInvalidated - menu.findItem(R.id.nav_show_invalidated)?.isVisible = !showInvalidated + updateMenuVisibility() val nsUploadOnly = !sp.getBoolean(R.string.key_ns_receive_profile_switch, false) || !buildHelper.isEngineeringMode() menu.findItem(R.id.nav_refresh_ns)?.isVisible = !nsUploadOnly @@ -288,19 +300,20 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() { override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) { - R.id.nav_remove_items -> { - removeActionMode = toolbar?.startActionMode(RemoveActionModeCallback()) - true - } + R.id.nav_remove_items -> actionHelper.startRemove() R.id.nav_show_invalidated -> { showInvalidated = true + updateMenuVisibility() + ToastUtils.showToastInUiThread(context, rh.gs(R.string.show_invalidated_records)) rxBus.send(EventTreatmentUpdateGui()) true } R.id.nav_hide_invalidated -> { showInvalidated = false + updateMenuVisibility() + ToastUtils.showToastInUiThread(context, rh.gs(R.string.hide_invalidated_records)) rxBus.send(EventTreatmentUpdateGui()) true } @@ -313,36 +326,7 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() { else -> false } - inner class RemoveActionModeCallback : ActionMode.Callback { - - override fun onCreateActionMode(mode: ActionMode, menu: Menu?): Boolean { - mode.menuInflater.inflate(R.menu.menu_delete_selection, menu) - selectedItems.clear() - mode.title = rh.gs(R.string.count_selected, selectedItems.size()) - binding.recyclerview.adapter?.notifyDataSetChanged() - return true - } - - override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?) = false - - override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { - return when (item.itemId) { - R.id.remove_selected -> { - removeSelected() - true - } - - else -> false - } - } - - override fun onDestroyActionMode(mode: ActionMode?) { - removeActionMode = null - binding.recyclerview.adapter?.notifyDataSetChanged() - } - } - - private fun getConfirmationText(): String { + private fun getConfirmationText(selectedItems: SparseArray): String { if (selectedItems.size() == 1) { val profileSwitch = selectedItems.valueAt(0) 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()) } - private fun removeSelected() { - if (selectedItems.size() > 0) - activity?.let { activity -> - OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(), Runnable { - selectedItems.forEach { _, profileSwitch -> - uel.log( - Action.PROFILE_SWITCH_REMOVED, Sources.Treatments, profileSwitch.profileName, - ValueWithUnit.Timestamp(profileSwitch.timestamp) + private fun removeSelected(selectedItems: SparseArray) { + activity?.let { activity -> + OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(selectedItems), Runnable { + selectedItems.forEach { _, profileSwitch -> + uel.log( + Action.PROFILE_SWITCH_REMOVED, Sources.Treatments, profileSwitch.profileName, + 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( - { 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() + } + actionHelper.finish() + }) + } } } diff --git a/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsTempTargetFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsTempTargetFragment.kt index 9a66a04dd6..9a28f5a220 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsTempTargetFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsTempTargetFragment.kt @@ -4,46 +4,42 @@ import android.annotation.SuppressLint import android.os.Bundle import android.util.SparseArray import android.view.* -import androidx.appcompat.widget.Toolbar import androidx.core.util.forEach import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import dagger.android.support.DaggerFragment 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.ValueWrapper -import info.nightscout.androidaps.database.entities.ValueWithUnit import info.nightscout.androidaps.database.entities.TemporaryTarget import info.nightscout.androidaps.database.entities.UserEntry.Action 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.transactions.InvalidateTemporaryTargetTransaction import info.nightscout.androidaps.databinding.TreatmentsTemptargetFragmentBinding 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.EventProfileSwitchChanged -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.buildHelper.BuildHelper +import info.nightscout.androidaps.events.EventTempTargetChange +import info.nightscout.androidaps.events.EventTreatmentUpdateGui import info.nightscout.androidaps.extensions.friendlyDescription import info.nightscout.androidaps.extensions.highValueToUnitsToString import info.nightscout.androidaps.extensions.lowValueToUnitsToString 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.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.rx.AapsSchedulers +import info.nightscout.shared.logging.AAPSLogger +import info.nightscout.shared.logging.LTag import info.nightscout.shared.sharedPreferences.SP import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.disposables.CompositeDisposable @@ -68,22 +64,24 @@ class TreatmentsTempTargetFragment : DaggerFragment() { @Inject lateinit var repository: AppRepository private var _binding: TreatmentsTemptargetFragmentBinding? = null + // This property is only valid between onCreateView and onDestroyView. private val binding get() = _binding!! - + private var menu: Menu? = null + private lateinit var actionHelper: ActionModeHelper private val disposable = CompositeDisposable() private val millsToThePast = T.days(30).msecs() - private var selectedItems: SparseArray = SparseArray() private var showInvalidated = false - private var toolbar: Toolbar? = null - private var removeActionMode: ActionMode? = null override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = TreatmentsTemptargetFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root + @SuppressLint("NotifyDataSetChanged") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 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) binding.recyclerview.layoutManager = LinearLayoutManager(view.context) } @@ -131,13 +129,11 @@ class TreatmentsTempTargetFragment : DaggerFragment() { override fun onResume() { super.onResume() swapAdapter() - disposable += rxBus .toObservable(EventTempTargetChange::class.java) .observeOn(aapsSchedulers.io) .debounce(1L, TimeUnit.SECONDS) .subscribe({ swapAdapter() }, fabricPrivacy::logException) - disposable += rxBus .toObservable(EventTreatmentUpdateGui::class.java) // TODO join with above .observeOn(aapsSchedulers.io) @@ -148,13 +144,13 @@ class TreatmentsTempTargetFragment : DaggerFragment() { @Synchronized override fun onPause() { super.onPause() + actionHelper.finish() disposable.clear() } @Synchronized override fun onDestroyView() { super.onDestroyView() - removeActionMode?.finish() binding.recyclerview.adapter = null // avoid leaks _binding = null } @@ -173,17 +169,16 @@ class TreatmentsTempTargetFragment : DaggerFragment() { val tempTarget = tempTargetList[position] holder.binding.ns.visibility = (tempTarget.interfaceIDs.nightscoutId != null).toVisibility() holder.binding.invalid.visibility = tempTarget.isValid.not().toVisibility() - holder.binding.cbRemove.visibility = (tempTarget.isValid && removeActionMode != null).toVisibility() - if (removeActionMode != null) { + holder.binding.cbRemove.visibility = (tempTarget.isValid && actionHelper.isRemoving).toVisibility() + if (actionHelper.isRemoving) { holder.binding.cbRemove.setOnCheckedChangeListener { _, value -> - if (value) { - selectedItems.put(position, tempTarget) - } else { - selectedItems.remove(position) - } - removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size()) + actionHelper.updateSelection(position, tempTarget, value) } - 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) 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) { + this.menu = menu inflater.inflate(R.menu.menu_treatments_temp_target, menu) 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) { - menu.findItem(R.id.nav_hide_invalidated)?.isVisible = showInvalidated - menu.findItem(R.id.nav_show_invalidated)?.isVisible = !showInvalidated + updateMenuVisibility() val nsUploadOnly = !sp.getBoolean(R.string.key_ns_receive_temp_target, false) || !buildHelper.isEngineeringMode() menu.findItem(R.id.nav_refresh_ns)?.isVisible = !nsUploadOnly @@ -254,19 +229,20 @@ class TreatmentsTempTargetFragment : DaggerFragment() { override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) { - R.id.nav_remove_items -> { - removeActionMode = toolbar?.startActionMode(RemoveActionModeCallback()) - true - } + R.id.nav_remove_items -> actionHelper.startRemove() R.id.nav_show_invalidated -> { showInvalidated = true + updateMenuVisibility() + ToastUtils.showToastInUiThread(context, rh.gs(R.string.show_invalidated_records)) rxBus.send(EventTreatmentUpdateGui()) true } R.id.nav_hide_invalidated -> { showInvalidated = false + updateMenuVisibility() + ToastUtils.showToastInUiThread(context, rh.gs(R.string.show_invalidated_records)) rxBus.send(EventTreatmentUpdateGui()) true } @@ -279,36 +255,7 @@ class TreatmentsTempTargetFragment : DaggerFragment() { else -> false } - inner class RemoveActionModeCallback : ActionMode.Callback { - - override fun onCreateActionMode(mode: ActionMode, menu: Menu?): Boolean { - mode.menuInflater.inflate(R.menu.menu_delete_selection, menu) - selectedItems.clear() - mode.title = rh.gs(R.string.count_selected, selectedItems.size()) - binding.recyclerview.adapter?.notifyDataSetChanged() - return true - } - - override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?) = false - - override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { - return when (item.itemId) { - R.id.remove_selected -> { - removeSelected() - true - } - - else -> false - } - } - - override fun onDestroyActionMode(mode: ActionMode?) { - removeActionMode = null - binding.recyclerview.adapter?.notifyDataSetChanged() - } - } - - private fun getConfirmationText(): String { + private fun getConfirmationText(selectedItems: SparseArray): String { if (selectedItems.size() == 1) { val tempTarget = selectedItems.valueAt(0) 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()) } + private fun removeSelected(selectedItems: SparseArray) { + 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() + }) + } + } + } diff --git a/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsTemporaryBasalsFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsTemporaryBasalsFragment.kt index 2a4f3cee26..d867c87ba9 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsTemporaryBasalsFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsTemporaryBasalsFragment.kt @@ -1,14 +1,15 @@ package info.nightscout.androidaps.activities.fragments +import android.annotation.SuppressLint import android.os.Bundle import android.util.SparseArray import android.view.* -import androidx.appcompat.widget.Toolbar import androidx.core.util.forEach import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import dagger.android.support.DaggerFragment import info.nightscout.androidaps.R +import info.nightscout.androidaps.activities.fragments.TreatmentsTemporaryBasalsFragment.RecyclerViewAdapter.TempBasalsViewHolder import info.nightscout.androidaps.data.IobTotal import info.nightscout.androidaps.database.AppRepository 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.events.EventAutosensCalculationFinished import info.nightscout.androidaps.events.EventTempBasalChange +import info.nightscout.androidaps.events.EventTreatmentUpdateGui import info.nightscout.androidaps.extensions.iobCalc import info.nightscout.androidaps.extensions.toStringFull import info.nightscout.androidaps.extensions.toTemporaryBasal import info.nightscout.androidaps.extensions.toVisibility import info.nightscout.androidaps.interfaces.ActivePlugin 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.activities.fragments.TreatmentsTemporaryBasalsFragment.RecyclerViewAdapter.TempBasalsViewHolder -import info.nightscout.androidaps.events.EventTreatmentUpdateGui +import info.nightscout.androidaps.utils.ActionModeHelper import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.T +import info.nightscout.androidaps.utils.ToastUtils import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.utils.resources.ResourceHelper 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.kotlin.plusAssign import java.util.concurrent.TimeUnit @@ -64,21 +66,23 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() { @Inject lateinit var repository: AppRepository private var _binding: TreatmentsTempbasalsFragmentBinding? = null + // This property is only valid between onCreateView and onDestroyView. private val binding get() = _binding!! - + private var menu: Menu? = null + private lateinit var actionHelper: ActionModeHelper private val millsToThePast = T.days(30).msecs() - private var selectedItems: SparseArray = SparseArray() private var showInvalidated = false - private var toolbar: Toolbar? = null - private var removeActionMode: ActionMode? = null override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = TreatmentsTempbasalsFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root + @SuppressLint("NotifyDataSetChanged") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 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) binding.recyclerview.setHasFixedSize(true) binding.recyclerview.layoutManager = LinearLayoutManager(view.context) @@ -133,28 +137,31 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() { override fun onResume() { super.onResume() swapAdapter() - disposable += rxBus .toObservable(EventTempBasalChange::class.java) .observeOn(aapsSchedulers.main) .subscribe({ swapAdapter() }, fabricPrivacy::logException) - disposable += rxBus .toObservable(EventAutosensCalculationFinished::class.java) .observeOn(aapsSchedulers.main) .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 override fun onPause() { super.onPause() + actionHelper.finish() disposable.clear() } @Synchronized override fun onDestroyView() { super.onDestroyView() - removeActionMode?.finish() binding.recyclerview.adapter = null // avoid leaks _binding = null } @@ -194,17 +201,16 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() { holder.binding.emulatedSuspendFlag.visibility = (tempBasal.type == TemporaryBasal.Type.EMULATED_PUMP_SUSPEND).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) - holder.binding.cbRemove.visibility = (tempBasal.isValid && removeActionMode != null).toVisibility() - if (removeActionMode != null) { + holder.binding.cbRemove.visibility = (tempBasal.isValid && actionHelper.isRemoving).toVisibility() + if (actionHelper.isRemoving) { holder.binding.cbRemove.setOnCheckedChangeListener { _, value -> - if (value) { - selectedItems.put(position, tempBasal) - } else { - selectedItems.remove(position) - } - removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size()) + actionHelper.updateSelection(position, tempBasal, value) } - 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 holder.binding.delimiter.visibility = dateUtil.isSameDayGroup(tempBasal.timestamp, nextTimestamp).toVisibility() @@ -221,32 +227,38 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() { } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + this.menu = menu inflater.inflate(R.menu.menu_treatments_temp_basal, menu) 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) { - menu.findItem(R.id.nav_hide_invalidated)?.isVisible = showInvalidated - menu.findItem(R.id.nav_show_invalidated)?.isVisible = !showInvalidated + updateMenuVisibility() return super.onPrepareOptionsMenu(menu) } override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) { - R.id.nav_remove_items -> { - removeActionMode = toolbar?.startActionMode(RemoveActionModeCallback()) - true - } + R.id.nav_remove_items -> actionHelper.startRemove() R.id.nav_show_invalidated -> { showInvalidated = true + updateMenuVisibility() + ToastUtils.showToastInUiThread(context, rh.gs(R.string.show_invalidated_records)) rxBus.send(EventTreatmentUpdateGui()) true } R.id.nav_hide_invalidated -> { showInvalidated = false + updateMenuVisibility() + ToastUtils.showToastInUiThread(context, rh.gs(R.string.hide_invalidated_records)) rxBus.send(EventTreatmentUpdateGui()) true } @@ -254,36 +266,7 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() { else -> false } - inner class RemoveActionModeCallback : ActionMode.Callback { - - override fun onCreateActionMode(mode: ActionMode, menu: Menu?): Boolean { - mode.menuInflater.inflate(R.menu.menu_delete_selection, menu) - selectedItems.clear() - mode.title = rh.gs(R.string.count_selected, selectedItems.size()) - binding.recyclerview.adapter?.notifyDataSetChanged() - return true - } - - override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?) = false - - override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { - return when (item.itemId) { - R.id.remove_selected -> { - removeSelected() - true - } - - else -> false - } - } - - override fun onDestroyActionMode(mode: ActionMode?) { - removeActionMode = null - binding.recyclerview.adapter?.notifyDataSetChanged() - } - } - - private fun getConfirmationText(): String { + private fun getConfirmationText(selectedItems: SparseArray): String { if (selectedItems.size() == 1) { val tempBasal = selectedItems.valueAt(0) 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()) } - private fun removeSelected() { + private fun removeSelected(selectedItems: SparseArray) { if (selectedItems.size() > 0) activity?.let { activity -> - OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(), Runnable { - selectedItems.forEach {_, tempBasal -> - var extendedBolus: ExtendedBolus? = null - val isFakeExtended = tempBasal.type == TemporaryBasal.Type.FAKE_EXTENDED - if (isFakeExtended) { - val eb = repository.getExtendedBolusActiveAt(tempBasal.timestamp).blockingGet() - 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) }) - } + OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(selectedItems), Runnable { + selectedItems.forEach {_, tempBasal -> + var extendedBolus: ExtendedBolus? = null + val isFakeExtended = tempBasal.type == TemporaryBasal.Type.FAKE_EXTENDED + if (isFakeExtended) { + val eb = repository.getExtendedBolusActiveAt(tempBasal.timestamp).blockingGet() + extendedBolus = if (eb is ValueWrapper.Existing) eb.value else null } - removeActionMode?.finish() - }) - } - else - removeActionMode?.finish() + 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) }) + } + } + actionHelper.finish() + }) + } } } diff --git a/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsUserEntryFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsUserEntryFragment.kt index 71cb5c1084..3cbebcee47 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsUserEntryFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsUserEntryFragment.kt @@ -13,20 +13,21 @@ import info.nightscout.androidaps.database.entities.UserEntry.Sources import info.nightscout.androidaps.databinding.TreatmentsUserEntryFragmentBinding import info.nightscout.androidaps.databinding.TreatmentsUserEntryItemBinding 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.ProfileFunction import info.nightscout.androidaps.logging.UserEntryLogger 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.FabricPrivacy import info.nightscout.androidaps.utils.T +import info.nightscout.androidaps.utils.ToastUtils 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.resources.ResourceHelper import info.nightscout.androidaps.utils.rx.AapsSchedulers +import info.nightscout.androidaps.utils.userEntry.UserEntryPresentationHelper import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign import java.util.concurrent.TimeUnit @@ -47,9 +48,9 @@ class TreatmentsUserEntryFragment : DaggerFragment() { @Inject lateinit var userEntryPresentationHelper: UserEntryPresentationHelper private val disposable = CompositeDisposable() - private val millsToThePastFiltered = T.days(30).msecs() private val millsToThePastUnFiltered = T.days(3).msecs() + private var menu: Menu? = null private var showLoop = false private var _binding: TreatmentsUserEntryFragmentBinding? = null @@ -66,7 +67,7 @@ class TreatmentsUserEntryFragment : DaggerFragment() { binding.recyclerview.layoutManager = LinearLayoutManager(view.context) } - fun exportUserEnteries() { + private fun exportUserEntries() { activity?.let { activity -> OKDialog.showConfirmation(activity, rh.gs(R.string.ue_export_to_csv) + "?") { uel.log(Action.EXPORT_CSV, Sources.Treatments) @@ -94,7 +95,6 @@ class TreatmentsUserEntryFragment : DaggerFragment() { override fun onResume() { super.onResume() swapAdapter() - disposable += rxBus .toObservable(EventPreferenceChange::class.java) .observeOn(aapsSchedulers.io) @@ -134,11 +134,10 @@ class TreatmentsUserEntryFragment : DaggerFragment() { holder.binding.time.text = dateUtil.timeStringWithSeconds(current.timestamp) holder.binding.action.text = userEntryPresentationHelper.actionToColoredString(current.action) 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.visibility = View.VISIBLE 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 holder.binding.delimiter.visibility = dateUtil.isSameDayGroup(current.timestamp, nextTimestamp).toVisibility() } @@ -152,14 +151,18 @@ class TreatmentsUserEntryFragment : DaggerFragment() { } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + this.menu = menu inflater.inflate(R.menu.menu_treatments_user_entry, menu) super.onCreateOptionsMenu(menu, inflater) } - override fun onPrepareOptionsMenu(menu: Menu) { - menu.findItem(R.id.nav_hide_loop)?.isVisible = showLoop - menu.findItem(R.id.nav_show_loop)?.isVisible = !showLoop + private fun updateMenuVisibility() { + menu?.findItem(R.id.nav_hide_loop)?.isVisible = showLoop + menu?.findItem(R.id.nav_show_loop)?.isVisible = !showLoop + } + override fun onPrepareOptionsMenu(menu: Menu) { + updateMenuVisibility() return super.onPrepareOptionsMenu(menu) } @@ -167,21 +170,26 @@ class TreatmentsUserEntryFragment : DaggerFragment() { when (item.itemId) { R.id.nav_show_loop -> { showLoop = true + updateMenuVisibility() + ToastUtils.showToastInUiThread(context, rh.gs(R.string.show_loop_records)) rxBus.send(EventTreatmentUpdateGui()) true } R.id.nav_hide_loop -> { showLoop = false + updateMenuVisibility() + ToastUtils.showToastInUiThread(context, rh.gs(R.string.show_hide_records)) rxBus.send(EventTreatmentUpdateGui()) true } R.id.nav_export -> { - exportUserEnteries() + exportUserEntries() true } else -> false } + } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/activities/QuickWizardListActivity.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/activities/QuickWizardListActivity.kt index c19899c75a..128f6c6613 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/activities/QuickWizardListActivity.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/activities/QuickWizardListActivity.kt @@ -7,11 +7,6 @@ import android.view.* import androidx.core.util.forEach import androidx.fragment.app.FragmentManager import androidx.recyclerview.widget.ItemTouchHelper -import androidx.recyclerview.widget.ItemTouchHelper.ACTION_STATE_DRAG -import androidx.recyclerview.widget.ItemTouchHelper.DOWN -import androidx.recyclerview.widget.ItemTouchHelper.END -import androidx.recyclerview.widget.ItemTouchHelper.START -import androidx.recyclerview.widget.ItemTouchHelper.UP import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import info.nightscout.androidaps.R @@ -20,8 +15,12 @@ import info.nightscout.androidaps.databinding.OverviewQuickwizardlistActivityBin import info.nightscout.androidaps.databinding.OverviewQuickwizardlistItemBinding import info.nightscout.androidaps.extensions.toVisibility import info.nightscout.androidaps.plugins.bus.RxBus +import info.nightscout.androidaps.utils.dragHelpers.ItemTouchHelperAdapter +import info.nightscout.androidaps.utils.dragHelpers.OnStartDragListener +import info.nightscout.androidaps.utils.dragHelpers.SimpleItemTouchHelperCallback import info.nightscout.androidaps.plugins.general.overview.dialogs.EditQuickWizardDialog import info.nightscout.androidaps.plugins.general.overview.events.EventQuickWizardChange +import info.nightscout.androidaps.utils.ActionModeHelper import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.alertDialogs.OKDialog @@ -33,7 +32,7 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign import javax.inject.Inject -class QuickWizardListActivity : DaggerAppCompatActivityWithResult() { +class QuickWizardListActivity : DaggerAppCompatActivityWithResult(), OnStartDragListener { @Inject lateinit var aapsSchedulers: AapsSchedulers @Inject lateinit var rxBus: RxBus @@ -43,49 +42,15 @@ class QuickWizardListActivity : DaggerAppCompatActivityWithResult() { @Inject lateinit var sp: SP private var disposable: CompositeDisposable = CompositeDisposable() - private var selectedItems: SparseArray = SparseArray() - private var removeActionMode: ActionMode? = null - private var sortActionMode: ActionMode? = null - + private lateinit var actionHelper: ActionModeHelper + private val itemTouchHelper = ItemTouchHelper(SimpleItemTouchHelperCallback()) private lateinit var binding: OverviewQuickwizardlistActivityBinding - private val itemTouchHelper by lazy { - val simpleItemTouchCallback = object : ItemTouchHelper.SimpleCallback(UP or DOWN or START or END, 0) { - - override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean { - val adapter = recyclerView.adapter as RecyclerViewAdapter - val from = viewHolder.layoutPosition - val to = target.layoutPosition - adapter.moveItem(from, to) - adapter.notifyItemMoved(from, to) - - return true - } - - override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {} - - override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) { - super.onSelectedChanged(viewHolder, actionState) - if (actionState == ACTION_STATE_DRAG) { - viewHolder?.itemView?.alpha = 0.5f - } - } - - override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { - super.clearView(recyclerView, viewHolder) - viewHolder.itemView.alpha = 1.0f - (recyclerView.adapter as RecyclerViewAdapter).onDrop() - } - } - - ItemTouchHelper(simpleItemTouchCallback) - } - - fun startDragging(viewHolder: RecyclerView.ViewHolder) { + override fun onStartDrag(viewHolder: RecyclerView.ViewHolder) { itemTouchHelper.startDrag(viewHolder) } - private inner class RecyclerViewAdapter(var fragmentManager: FragmentManager) : RecyclerView.Adapter() { + private inner class RecyclerViewAdapter(var fragmentManager: FragmentManager) : RecyclerView.Adapter(), ItemTouchHelperAdapter { private inner class QuickWizardEntryViewHolder(val binding: OverviewQuickwizardlistItemBinding, val fragmentManager: FragmentManager) : RecyclerView.ViewHolder(binding.root) @@ -112,61 +77,43 @@ class QuickWizardListActivity : DaggerAppCompatActivityWithResult() { } ) } - - if (sortActionMode != null && removeActionMode != null) { - holder.binding.cardview.setOnClickListener { + holder.binding.root.setOnClickListener { + if (actionHelper.isNoAction) { val manager = fragmentManager val editQuickWizardDialog = EditQuickWizardDialog() val bundle = Bundle() bundle.putInt("position", position) editQuickWizardDialog.arguments = bundle editQuickWizardDialog.show(manager, "EditQuickWizardDialog") - } - } - - fun updateSelection(selected: Boolean) { - if (selected) { - selectedItems.put(position, entry) - } else { - selectedItems.remove(position) - } - removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size()) - } - - holder.binding.cardview.setOnTouchListener { _, event -> - if (event.actionMasked == MotionEvent.ACTION_UP && sortActionMode == null && removeActionMode == null) { - val manager = fragmentManager - val editQuickWizardDialog = EditQuickWizardDialog() - val bundle = Bundle() - bundle.putInt("position", position) - editQuickWizardDialog.arguments = bundle - editQuickWizardDialog.show(manager, "EditQuickWizardDialog") - } - if (event.actionMasked == MotionEvent.ACTION_DOWN && sortActionMode != null) { - startDragging(holder) - } - if (event.actionMasked == MotionEvent.ACTION_UP && removeActionMode != null) { + } else if (actionHelper.isRemoving) { holder.binding.cbRemove.toggle() - updateSelection(holder.binding.cbRemove.isChecked) + actionHelper.updateSelection(position, entry, holder.binding.cbRemove.isChecked) } - return@setOnTouchListener true } - holder.binding.cbRemove.isChecked = selectedItems.get(position) != null - holder.binding.cbRemove.setOnCheckedChangeListener { _, value -> updateSelection(value) } - holder.binding.handleView.visibility = (sortActionMode != null).toVisibility() - holder.binding.cbRemove.visibility = (removeActionMode != null).toVisibility() - removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size()) + holder.binding.sortHandle.setOnTouchListener { _, event -> + if (event.actionMasked == MotionEvent.ACTION_DOWN) { + onStartDrag(holder) + return@setOnTouchListener true + } + 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) { - quickWizard.move(from, to) + override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean { + binding.recyclerview.adapter?.notifyItemMoved(fromPosition, toPosition) + quickWizard.move(fromPosition, toPosition) + return true } - fun onDrop() { - rxBus.send(EventQuickWizardChange()) - } + override fun onDrop() = rxBus.send(EventQuickWizardChange()) } override fun onCreate(savedInstanceState: Bundle?) { @@ -174,6 +121,11 @@ class QuickWizardListActivity : DaggerAppCompatActivityWithResult() { binding = OverviewQuickwizardlistActivityBinding.inflate(layoutInflater) 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) supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayShowHomeEnabled(true) @@ -184,6 +136,7 @@ class QuickWizardListActivity : DaggerAppCompatActivityWithResult() { itemTouchHelper.attachToRecyclerView(binding.recyclerview) binding.addButton.setOnClickListener { + actionHelper.finish() val manager = supportFragmentManager val editQuickWizardDialog = EditQuickWizardDialog() editQuickWizardDialog.show(manager, "EditQuickWizardDialog") @@ -203,105 +156,37 @@ class QuickWizardListActivity : DaggerAppCompatActivityWithResult() { override fun onPause() { disposable.clear() + actionHelper.finish() super.onPause() } - private fun removeSelected() { - if (selectedItems.size() > 0) - OKDialog.showConfirmation(this, rh.gs(R.string.removerecord), getConfirmationText(), Runnable { - selectedItems.forEach { _, item -> - quickWizard.remove(item.position) - rxBus.send(EventQuickWizardChange()) - } - removeActionMode?.finish() - }) - else - removeActionMode?.finish() + private fun removeSelected(selectedItems: SparseArray) { + OKDialog.showConfirmation(this, rh.gs(R.string.removerecord), getConfirmationText(selectedItems), Runnable { + selectedItems.forEach { _, item -> + quickWizard.remove(item.position) + rxBus.send(EventQuickWizardChange()) + } + actionHelper.finish() + }) } override fun onCreateOptionsMenu(menu: Menu?): Boolean { - val inflater = menuInflater - inflater.inflate(R.menu.menu_quickwizard, menu) + menuInflater.inflate(R.menu.menu_actions, menu) return super.onCreateOptionsMenu(menu) } override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) { - android.R.id.home -> { + android.R.id.home -> { finish() true } - R.id.nav_remove_items -> { - removeActionMode = startActionMode(RemoveActionModeCallback()) - true - } + else -> actionHelper.onOptionsItemSelected(item) - R.id.nav_sort_items -> { - sortActionMode = startActionMode(SortActionModeCallback()) - true - } - - else -> false } - inner class RemoveActionModeCallback : ActionMode.Callback { - - override fun onCreateActionMode(mode: ActionMode, menu: Menu?): Boolean { - mode.menuInflater.inflate(R.menu.menu_delete_selection, menu) - selectedItems.clear() - mode.title = rh.gs(R.string.count_selected, selectedItems.size()) - binding.recyclerview.adapter?.notifyDataSetChanged() - return true - } - - override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?) = false - - override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { - return when (item.itemId) { - R.id.remove_selected -> { - removeSelected() - true - } - - else -> false - } - } - - override fun onDestroyActionMode(mode: ActionMode?) { - removeActionMode = null - binding.recyclerview.adapter?.notifyDataSetChanged() - } - } - - inner class SortActionModeCallback : ActionMode.Callback { - - override fun onCreateActionMode(mode: ActionMode, menu: Menu?): Boolean { - mode.title = rh.gs(R.string.sort_label) - binding.recyclerview.adapter?.notifyDataSetChanged() - return true - } - - override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?) = false - - override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { - return when (item.itemId) { - R.id.remove_selected -> { - removeSelected() - true - } - - else -> false - } - } - - override fun onDestroyActionMode(mode: ActionMode?) { - sortActionMode = null - binding.recyclerview.adapter?.notifyDataSetChanged() - } - } - - private fun getConfirmationText(): String { + private fun getConfirmationText(selectedItems: SparseArray): String { if (selectedItems.size() == 1) { val entry = selectedItems.valueAt(0) return "${rh.gs(R.string.remove_button)} ${entry.buttonText()} ${rh.gs(R.string.format_carbs, entry.carbs())}\n" + @@ -309,4 +194,5 @@ class QuickWizardListActivity : DaggerAppCompatActivityWithResult() { } return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size()) } + } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/BGSourceFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/source/BGSourceFragment.kt index e175939d4c..c4249955ed 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/source/BGSourceFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/BGSourceFragment.kt @@ -3,7 +3,6 @@ package info.nightscout.androidaps.plugins.source import android.os.Bundle import android.util.SparseArray import android.view.* -import androidx.appcompat.widget.Toolbar import androidx.core.util.forEach import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -26,6 +25,7 @@ import info.nightscout.androidaps.interfaces.PluginBase import info.nightscout.androidaps.interfaces.ProfileFunction import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.plugins.bus.RxBus +import info.nightscout.androidaps.utils.ActionModeHelper import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.T @@ -54,11 +54,9 @@ class BGSourceFragment : DaggerFragment() { private val disposable = CompositeDisposable() private val millsToThePast = T.hours(36).msecs() - private var selectedItems: SparseArray = SparseArray() - private var toolbar: Toolbar? = null - private var removeActionMode: ActionMode? = null - + private lateinit var actionHelper: ActionModeHelper private var _binding: BgsourceFragmentBinding? = null + // This property is only valid between onCreateView and onDestroyView. private val binding get() = _binding!! @@ -67,8 +65,10 @@ class BGSourceFragment : DaggerFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - toolbar = activity?.findViewById(R.id.toolbar) - setHasOptionsMenu(true) + actionHelper = ActionModeHelper(rh, activity) + actionHelper.setUpdateListHandler { binding.recyclerview.adapter?.notifyDataSetChanged() } + actionHelper.setOnRemoveHandler { removeSelected(it) } + setHasOptionsMenu(actionHelper.inMenu) binding.recyclerview.setHasFixedSize(true) binding.recyclerview.layoutManager = LinearLayoutManager(view.context) @@ -97,44 +97,30 @@ class BGSourceFragment : DaggerFragment() { @Synchronized override fun onPause() { - removeActionMode?.finish() + actionHelper.finish() disposable.clear() super.onPause() } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { super.onCreateOptionsMenu(menu, inflater) - inflater.inflate(R.menu.menu_bgsource, menu) + actionHelper.onCreateOptionsMenu(menu, inflater) } override fun onPrepareOptionsMenu(menu: Menu) { - // Only show when tab bg source is shown - menu.findItem(R.id.nav_remove_items)?.isVisible = isResumed super.onPrepareOptionsMenu(menu) + actionHelper.onPrepareOptionsMenu(menu) } @Synchronized override fun onDestroyView() { super.onDestroyView() - removeActionMode?.finish() binding.recyclerview.adapter = null // avoid leaks _binding = null } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - return when (item.itemId) { - R.id.nav_remove_items -> { - if (toolbar != null) { - removeActionMode = toolbar?.startActionMode(RemoveActionModeCallback()) // in overview - } else { - removeActionMode = activity?.startActionMode(RemoveActionModeCallback()) // in Single FragmentActivity - } - true - } - - else -> false - } - } + override fun onOptionsItemSelected(item: MenuItem) = + actionHelper.onOptionsItemSelected(item) inner class RecyclerViewAdapter internal constructor(private var glucoseValues: List) : RecyclerView.Adapter() { @@ -159,34 +145,29 @@ class BGSourceFragment : DaggerFragment() { if (diff < T.secs(20).msecs()) holder.binding.root.setBackgroundColor(rh.gc(R.color.errorAlertBackground)) } - fun updateSelection(selected: Boolean) { - if (selected) { - selectedItems.put(position, glucoseValue) - } else { - selectedItems.remove(position) - } - removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size()) - } + holder.binding.root.setOnLongClickListener { - if (removeActionMode == null) { - removeActionMode = toolbar?.startActionMode(RemoveActionModeCallback()) + if (actionHelper.startRemove()) { + holder.binding.cbRemove.toggle() + actionHelper.updateSelection(position, glucoseValue, holder.binding.cbRemove.isChecked) + return@setOnLongClickListener true } - holder.binding.cbRemove.toggle() - updateSelection(holder.binding.cbRemove.isChecked) - true + false } holder.binding.root.setOnClickListener { - if (removeActionMode != null) { + if (actionHelper.isRemoving) { holder.binding.cbRemove.toggle() - updateSelection(holder.binding.cbRemove.isChecked) + actionHelper.updateSelection(position, glucoseValue, holder.binding.cbRemove.isChecked) } } - holder.binding.cbRemove.setOnCheckedChangeListener { _, value -> updateSelection(value) } - holder.binding.cbRemove.isChecked = selectedItems.get(position) != null - holder.binding.cbRemove.visibility = (removeActionMode != null).toVisibility() + holder.binding.cbRemove.setOnCheckedChangeListener { _, value -> + actionHelper.updateSelection(position, glucoseValue, value) + } + holder.binding.cbRemove.isChecked = actionHelper.isSelected(position) + holder.binding.cbRemove.visibility = actionHelper.isRemoving.toVisibility() } - override fun getItemCount(): Int = glucoseValues.size + override fun getItemCount() = glucoseValues.size inner class GlucoseValuesViewHolder(view: View) : RecyclerView.ViewHolder(view) { @@ -194,36 +175,7 @@ class BGSourceFragment : DaggerFragment() { } } - inner class RemoveActionModeCallback : ActionMode.Callback { - - override fun onCreateActionMode(mode: ActionMode, menu: Menu?): Boolean { - mode.menuInflater.inflate(R.menu.menu_delete_selection, menu) - selectedItems.clear() - mode.title = rh.gs(R.string.count_selected, selectedItems.size()) - binding.recyclerview.adapter?.notifyDataSetChanged() - return true - } - - override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?) = false - - override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { - return when (item.itemId) { - R.id.remove_selected -> { - removeSelected() - true - } - - else -> false - } - } - - override fun onDestroyActionMode(mode: ActionMode?) { - removeActionMode = null - binding.recyclerview.adapter?.notifyDataSetChanged() - } - } - - private fun getConfirmationText(): String { + private fun getConfirmationText(selectedItems: SparseArray): String { if (selectedItems.size() == 1) { val glucoseValue = selectedItems.valueAt(0) 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()) } - private fun removeSelected() { - if (selectedItems.size() > 0) - activity?.let { activity -> - OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(), Runnable { - selectedItems.forEach { _, glucoseValue -> - val source = when ((activePlugin.activeBgSource as PluginBase).pluginDescription.pluginName) { - R.string.dexcom_app_patched -> Sources.Dexcom - R.string.eversense -> Sources.Eversense - R.string.Glimp -> Sources.Glimp - R.string.MM640g -> Sources.MM640g - R.string.nsclientbg -> Sources.NSClientSource - R.string.poctech -> Sources.PocTech - R.string.tomato -> Sources.Tomato - R.string.glunovo -> Sources.Glunovo - R.string.xdrip -> Sources.Xdrip - 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") } } + private fun removeSelected(selectedItems: SparseArray) { + activity?.let { activity -> + OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(selectedItems), Runnable { + selectedItems.forEach { _, glucoseValue -> + val source = when ((activePlugin.activeBgSource as PluginBase).pluginDescription.pluginName) { + R.string.dexcom_app_patched -> Sources.Dexcom + R.string.eversense -> Sources.Eversense + R.string.Glimp -> Sources.Glimp + R.string.MM640g -> Sources.MM640g + R.string.nsclientbg -> Sources.NSClientSource + R.string.poctech -> Sources.PocTech + R.string.tomato -> Sources.Tomato + R.string.glunovo -> Sources.Glunovo + R.string.xdrip -> Sources.Xdrip + else -> Sources.Unknown } - removeActionMode?.finish() - }) - } - else - removeActionMode?.finish() + 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") } } + } + actionHelper.finish() + }) + } } } diff --git a/app/src/main/res/drawable/ic_settings.xml b/app/src/main/res/drawable/ic_settings.xml index 8b7d553260..9553b51a99 100644 --- a/app/src/main/res/drawable/ic_settings.xml +++ b/app/src/main/res/drawable/ic_settings.xml @@ -6,6 +6,6 @@ android:viewportHeight="24" android:viewportWidth="24"> diff --git a/app/src/main/res/drawable/ic_sort.xml b/app/src/main/res/drawable/ic_sort.xml new file mode 100644 index 0000000000..4b1317ac20 --- /dev/null +++ b/app/src/main/res/drawable/ic_sort.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_visibility.xml b/app/src/main/res/drawable/ic_visibility.xml index de67f3ee84..76c9b61954 100644 --- a/app/src/main/res/drawable/ic_visibility.xml +++ b/app/src/main/res/drawable/ic_visibility.xml @@ -5,8 +5,8 @@ android:viewportHeight="24"> + android:fillColor="?attr/colorControlNormal"/> + android:fillColor="?attr/colorControlNormal"/> diff --git a/app/src/main/res/drawable/ic_visibility_off.xml b/app/src/main/res/drawable/ic_visibility_off.xml new file mode 100644 index 0000000000..00ecc3cdb3 --- /dev/null +++ b/app/src/main/res/drawable/ic_visibility_off.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/overview_quickwizardlist_item.xml b/app/src/main/res/layout/overview_quickwizardlist_item.xml index c45ed72a08..2496e6e0bc 100644 --- a/app/src/main/res/layout/overview_quickwizardlist_item.xml +++ b/app/src/main/res/layout/overview_quickwizardlist_item.xml @@ -81,7 +81,7 @@ tools:ignore="RtlSymmetry" /> - - + android:id="@+id/cb_carbs_remove" + android:layout_width="wrap_content" + android:layout_height="19dp" + android:contentDescription="@string/select_for_removal" + android:minWidth="0dp" + android:visibility="gone" /> diff --git a/app/src/main/res/layout/treatments_extendedbolus_item.xml b/app/src/main/res/layout/treatments_extendedbolus_item.xml index ed771d1cca..ffc0d10fd5 100644 --- a/app/src/main/res/layout/treatments_extendedbolus_item.xml +++ b/app/src/main/res/layout/treatments_extendedbolus_item.xml @@ -156,8 +156,9 @@ diff --git a/app/src/main/res/layout/treatments_tempbasals_item.xml b/app/src/main/res/layout/treatments_tempbasals_item.xml index cbbb4d57ab..9788110bb7 100644 --- a/app/src/main/res/layout/treatments_tempbasals_item.xml +++ b/app/src/main/res/layout/treatments_tempbasals_item.xml @@ -175,8 +175,9 @@ diff --git a/app/src/main/res/menu/menu_bgsource.xml b/app/src/main/res/menu/menu_bgsource.xml deleted file mode 100644 index 87d741a9eb..0000000000 --- a/app/src/main/res/menu/menu_bgsource.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - diff --git a/app/src/main/res/menu/menu_delete_selection.xml b/app/src/main/res/menu/menu_delete_selection.xml index be03cd47cd..4fb8d9e96a 100644 --- a/app/src/main/res/menu/menu_delete_selection.xml +++ b/app/src/main/res/menu/menu_delete_selection.xml @@ -4,8 +4,8 @@ + app:showAsAction="ifRoom" /> diff --git a/app/src/main/res/menu/menu_single_fragment.xml b/app/src/main/res/menu/menu_single_fragment.xml index 0bc5ee19b8..2a1d4db202 100644 --- a/app/src/main/res/menu/menu_single_fragment.xml +++ b/app/src/main/res/menu/menu_single_fragment.xml @@ -2,9 +2,12 @@ xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" tools:context=".MainActivity"> + + diff --git a/app/src/main/res/menu/menu_treatments_carbs_bolus.xml b/app/src/main/res/menu/menu_treatments_carbs_bolus.xml index c45d9562d3..c31f9e5295 100644 --- a/app/src/main/res/menu/menu_treatments_carbs_bolus.xml +++ b/app/src/main/res/menu/menu_treatments_carbs_bolus.xml @@ -3,20 +3,27 @@ + app:showAsAction="ifRoom" /> + + app:showAsAction="ifRoom" /> + + app:showAsAction="ifRoom" /> + + + app:showAsAction="ifRoom" /> + + app:showAsAction="ifRoom" /> + + app:showAsAction="ifRoom" /> + + + app:showAsAction="ifRoom" /> + + app:showAsAction="ifRoom" /> + + app:showAsAction="ifRoom" /> diff --git a/app/src/main/res/menu/menu_treatments_profile_switch.xml b/app/src/main/res/menu/menu_treatments_profile_switch.xml index 2822a53819..8623848e54 100644 --- a/app/src/main/res/menu/menu_treatments_profile_switch.xml +++ b/app/src/main/res/menu/menu_treatments_profile_switch.xml @@ -3,15 +3,20 @@ + app:showAsAction="ifRoom" /> + + app:showAsAction="ifRoom" /> + + app:showAsAction="ifRoom" /> diff --git a/app/src/main/res/menu/menu_treatments_temp_basal.xml b/app/src/main/res/menu/menu_treatments_temp_basal.xml index 2822a53819..8623848e54 100644 --- a/app/src/main/res/menu/menu_treatments_temp_basal.xml +++ b/app/src/main/res/menu/menu_treatments_temp_basal.xml @@ -3,15 +3,20 @@ + app:showAsAction="ifRoom" /> + + app:showAsAction="ifRoom" /> + + app:showAsAction="ifRoom" /> diff --git a/app/src/main/res/menu/menu_treatments_temp_target.xml b/app/src/main/res/menu/menu_treatments_temp_target.xml index 751c678f12..b861d41178 100644 --- a/app/src/main/res/menu/menu_treatments_temp_target.xml +++ b/app/src/main/res/menu/menu_treatments_temp_target.xml @@ -3,16 +3,22 @@ + app:showAsAction="ifRoom" /> + + app:showAsAction="ifRoom" /> + + android:icon="@drawable/ic_visibility_off" + app:showAsAction="ifRoom" /> + + app:showAsAction="ifRoom" /> + + app:showAsAction="ifRoom" /> + Unknown action command: Percentage Application default + Show invalidated / removed records + Hide invalidated / removed records Select profile to edit Refresh from Nightscout Remove selected items @@ -1204,4 +1206,6 @@ Below In range Above + Show loop records + Hide loop records diff --git a/automation/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationFragment.kt b/automation/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationFragment.kt index 8543558515..15d72f6682 100644 --- a/automation/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationFragment.kt +++ b/automation/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationFragment.kt @@ -6,10 +6,10 @@ import android.os.Bundle import android.text.method.ScrollingMovementMethod import android.util.SparseArray import android.view.* -import androidx.core.util.forEach import android.widget.ImageView import android.widget.LinearLayout import androidx.annotation.DrawableRes +import androidx.core.util.forEach import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -20,24 +20,24 @@ import info.nightscout.androidaps.automation.databinding.AutomationEventItemBind import info.nightscout.androidaps.automation.databinding.AutomationFragmentBinding import info.nightscout.androidaps.database.entities.UserEntry.Action import info.nightscout.androidaps.database.entities.UserEntry.Sources +import info.nightscout.androidaps.extensions.toVisibility import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.plugins.bus.RxBus import info.nightscout.androidaps.plugins.general.automation.dialogs.EditEventDialog import info.nightscout.androidaps.plugins.general.automation.events.EventAutomationDataChanged import info.nightscout.androidaps.plugins.general.automation.events.EventAutomationUpdateGui import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnector +import info.nightscout.androidaps.utils.ActionModeHelper import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.HtmlHelper import info.nightscout.androidaps.utils.alertDialogs.OKDialog -import io.reactivex.rxjava3.kotlin.plusAssign -import info.nightscout.androidaps.extensions.toVisibility -import info.nightscout.androidaps.plugins.general.automation.dragHelpers.ItemTouchHelperAdapter -import info.nightscout.androidaps.plugins.general.automation.dragHelpers.OnStartDragListener -import info.nightscout.androidaps.plugins.general.automation.dragHelpers.SimpleItemTouchHelperCallback +import info.nightscout.androidaps.utils.dragHelpers.ItemTouchHelperAdapter +import info.nightscout.androidaps.utils.dragHelpers.OnStartDragListener +import info.nightscout.androidaps.utils.dragHelpers.SimpleItemTouchHelperCallback import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.rx.AapsSchedulers import io.reactivex.rxjava3.disposables.CompositeDisposable -import java.util.* +import io.reactivex.rxjava3.kotlin.plusAssign import javax.inject.Inject class AutomationFragment : DaggerFragment(), OnStartDragListener { @@ -52,11 +52,8 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener { private var disposable: CompositeDisposable = CompositeDisposable() private lateinit var eventListAdapter: EventListAdapter - private var selectedItems: SparseArray = SparseArray() - private var removeActionMode: ActionMode? = null - private var sortActionMode: ActionMode? = null + private lateinit var actionHelper: ActionModeHelper private val itemTouchHelper = ItemTouchHelper(SimpleItemTouchHelperCallback()) - private var _binding: AutomationFragmentBinding? = null // 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 { _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 } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setHasOptionsMenu(true) - eventListAdapter = EventListAdapter() binding.eventListView.layoutManager = LinearLayoutManager(context) binding.eventListView.adapter = eventListAdapter binding.logView.movementMethod = ScrollingMovementMethod() binding.fabAddEvent.setOnClickListener { - removeActionMode?.finish() - sortActionMode?.finish() + actionHelper.finish() val dialog = EditEventDialog() val args = Bundle() args.putString("event", AutomationEvent(injector).toJSON()) @@ -110,8 +109,7 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener { @Synchronized override fun onPause() { - removeActionMode?.finish() - sortActionMode?.finish() + actionHelper.finish() super.onPause() disposable.clear() } @@ -119,38 +117,16 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener { @Synchronized override fun onDestroyView() { super.onDestroyView() - removeActionMode?.finish() - sortActionMode?.finish() _binding = null } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { super.onCreateOptionsMenu(menu, inflater) - inflater.inflate(R.menu.menu_automation, menu) + actionHelper.onCreateOptionsMenu(menu, inflater) } - override fun onPrepareOptionsMenu(menu: Menu) { - // Only show when tab automation is shown - menu.findItem(R.id.nav_remove_automation_items)?.isVisible = isResumed - menu.findItem(R.id.nav_sort_automation_items)?.isVisible = isResumed - super.onPrepareOptionsMenu(menu) - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - return when (item.itemId) { - R.id.nav_remove_automation_items -> { - removeActionMode = activity?.startActionMode(RemoveActionModeCallback()) - true - } - - R.id.nav_sort_automation_items -> { - sortActionMode = activity?.startActionMode(SortActionModeCallback()) - true - } - - else -> false - } - } + override fun onOptionsItemSelected(item: MenuItem): Boolean = + actionHelper.onOptionsItemSelected(item) @Synchronized private fun updateGui() { @@ -228,59 +204,54 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener { for (res in actionIcons) { addImage(res, holder.context, holder.binding.iconLayout) } - // enabled event + holder.binding.aapsLogo.visibility = (automation.systemAction).toVisibility() + // Enabled events holder.binding.enabled.setOnClickListener { automation.isEnabled = holder.binding.enabled.isChecked rxBus.send(EventAutomationDataChanged()) } - holder.binding.aapsLogo.visibility = (automation.systemAction).toVisibility() - - fun updateSelection(selected: Boolean) { - if (selected) { - selectedItems.put(position, automation) - } else { - selectedItems.remove(position) - } - removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size()) - } - - holder.binding.rootLayout.setOnTouchListener { _, touchEvent -> - if (touchEvent.actionMasked == MotionEvent.ACTION_UP && sortActionMode == null && removeActionMode == null) { + holder.binding.rootLayout.setOnClickListener { + if (actionHelper.isNoAction) { val dialog = EditEventDialog() val args = Bundle() args.putString("event", automation.toJSON()) args.putInt("position", position) dialog.arguments = args dialog.show(childFragmentManager, "EditEventDialog") - } - if (touchEvent.actionMasked == MotionEvent.ACTION_DOWN && sortActionMode != null) { - onStartDrag(holder) - } - if (touchEvent.actionMasked == MotionEvent.ACTION_UP && removeActionMode != null) { + } else if (actionHelper.isRemoving) { holder.binding.cbRemove.toggle() - updateSelection(holder.binding.cbRemove.isChecked) - removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size()) + actionHelper.updateSelection(position, automation, holder.binding.cbRemove.isChecked) } - return@setOnTouchListener true } - holder.binding.cbRemove.isChecked = selectedItems.get(position) != null - holder.binding.cbRemove.setOnCheckedChangeListener { _, value -> updateSelection(value) } - holder.binding.iconSort.visibility = (sortActionMode != null).toVisibility() - holder.binding.cbRemove.visibility = (removeActionMode != null).toVisibility() - holder.binding.cbRemove.isEnabled = !automation.readOnly - holder.binding.enabled.visibility = if (removeActionMode == null) View.VISIBLE else View.INVISIBLE + holder.binding.rootLayout.setOnLongClickListener { + actionHelper.startAction() + } + holder.binding.sortHandle.setOnTouchListener { _, touchEvent -> + if (touchEvent.actionMasked == MotionEvent.ACTION_DOWN) { + 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 { + binding.eventListView.adapter?.notifyItemMoved(fromPosition, toPosition) automationPlugin.swap(fromPosition, toPosition) return true } - override fun onDrop() { - rxBus.send(EventAutomationDataChanged()) - } + override fun onDrop() = rxBus.send(EventAutomationDataChanged()) inner class ViewHolder(view: View, val context: Context) : RecyclerView.ViewHolder(view) { @@ -288,54 +259,7 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener { } } - inner class SortActionModeCallback : ActionMode.Callback { - - override fun onCreateActionMode(mode: ActionMode, menu: Menu?): Boolean { - mode.title = rh.gs(R.string.sort_label) - binding.eventListView.adapter?.notifyDataSetChanged() - return true - } - - override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?) = false - - override fun onActionItemClicked(mode: ActionMode, item: MenuItem) = false - - override fun onDestroyActionMode(mode: ActionMode?) { - sortActionMode = null - binding.eventListView.adapter?.notifyDataSetChanged() - } - } - - inner class RemoveActionModeCallback : ActionMode.Callback { - - override fun onCreateActionMode(mode: ActionMode, menu: Menu?): Boolean { - mode.menuInflater.inflate(R.menu.menu_delete_selection, menu) - selectedItems.clear() - mode.title = rh.gs(R.string.count_selected, selectedItems.size()) - binding.eventListView.adapter?.notifyDataSetChanged() - return true - } - - override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?) = false - - override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { - return when (item.itemId) { - R.id.remove_selected -> { - removeSelected() - true - } - - else -> false - } - } - - override fun onDestroyActionMode(mode: ActionMode?) { - removeActionMode = null - binding.eventListView.adapter?.notifyDataSetChanged() - } - } - - private fun getConfirmationText(): String { + private fun getConfirmationText(selectedItems: SparseArray): String { if (selectedItems.size() == 1) { val event = selectedItems.valueAt(0) 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()) } - private fun removeSelected() { - if (selectedItems.size() > 0) { - activity?.let { activity -> - OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(), Runnable { - selectedItems.forEach { _, event -> - uel.log(Action.AUTOMATION_REMOVED, Sources.Automation, event.title) - automationPlugin.removeAt(event.position) - rxBus.send(EventAutomationDataChanged()) - } - removeActionMode?.finish() - }) - } - } else { - removeActionMode?.finish() + private fun removeSelected(selectedItems: SparseArray) { + activity?.let { activity -> + OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(selectedItems), Runnable { + selectedItems.forEach { _, event -> + uel.log(Action.AUTOMATION_REMOVED, Sources.Automation, event.title) + automationPlugin.removeAt(event.position) + rxBus.send(EventAutomationDataChanged()) + } + actionHelper.finish() + }) } } + } diff --git a/automation/src/main/res/layout/automation_event_item.xml b/automation/src/main/res/layout/automation_event_item.xml index eedaaa5535..3190d30083 100644 --- a/automation/src/main/res/layout/automation_event_item.xml +++ b/automation/src/main/res/layout/automation_event_item.xml @@ -55,12 +55,12 @@ android:contentDescription="@string/remove_label" android:orientation="horizontal" 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_constraintTop_toTopOf="parent" /> - - - - - - diff --git a/core/src/main/java/info/nightscout/androidaps/utils/ActionModeHelper.kt b/core/src/main/java/info/nightscout/androidaps/utils/ActionModeHelper.kt new file mode 100644 index 0000000000..5a928bc8eb --- /dev/null +++ b/core/src/main/java/info/nightscout/androidaps/utils/ActionModeHelper.kt @@ -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(val rh: ResourceHelper, val activity: FragmentActivity?) { + + var enableSort = false + private var selectedItems: SparseArray = SparseArray() + private var actionMode: ActionMode? = null + private var removeActionMode: ActionMode? = null + private var sortActionMode: ActionMode? = null + private var onRemove: ((selectedItems: SparseArray) -> 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) -> 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() } + } + } + +} diff --git a/automation/src/main/java/info/nightscout/androidaps/plugins/general/automation/dragHelpers/ItemTouchHelperAdapter.kt b/core/src/main/java/info/nightscout/androidaps/utils/dragHelpers/ItemTouchHelperAdapter.kt similarity index 91% rename from automation/src/main/java/info/nightscout/androidaps/plugins/general/automation/dragHelpers/ItemTouchHelperAdapter.kt rename to core/src/main/java/info/nightscout/androidaps/utils/dragHelpers/ItemTouchHelperAdapter.kt index e873e73173..d76f12c4a2 100644 --- a/automation/src/main/java/info/nightscout/androidaps/plugins/general/automation/dragHelpers/ItemTouchHelperAdapter.kt +++ b/core/src/main/java/info/nightscout/androidaps/utils/dragHelpers/ItemTouchHelperAdapter.kt @@ -1,4 +1,4 @@ -package info.nightscout.androidaps.plugins.general.automation.dragHelpers +package info.nightscout.androidaps.utils.dragHelpers interface ItemTouchHelperAdapter { diff --git a/automation/src/main/java/info/nightscout/androidaps/plugins/general/automation/dragHelpers/OnStartDragListener.kt b/core/src/main/java/info/nightscout/androidaps/utils/dragHelpers/OnStartDragListener.kt similarity index 78% rename from automation/src/main/java/info/nightscout/androidaps/plugins/general/automation/dragHelpers/OnStartDragListener.kt rename to core/src/main/java/info/nightscout/androidaps/utils/dragHelpers/OnStartDragListener.kt index 9a90a360de..0924f37b2a 100644 --- a/automation/src/main/java/info/nightscout/androidaps/plugins/general/automation/dragHelpers/OnStartDragListener.kt +++ b/core/src/main/java/info/nightscout/androidaps/utils/dragHelpers/OnStartDragListener.kt @@ -1,4 +1,4 @@ -package info.nightscout.androidaps.plugins.general.automation.dragHelpers +package info.nightscout.androidaps.utils.dragHelpers import androidx.recyclerview.widget.RecyclerView @@ -9,4 +9,4 @@ interface OnStartDragListener { * @param viewHolder The holder of the view to drag. */ fun onStartDrag(viewHolder: RecyclerView.ViewHolder) -} \ No newline at end of file +} diff --git a/automation/src/main/java/info/nightscout/androidaps/plugins/general/automation/dragHelpers/SimpleItemTouchHelperCallback.kt b/core/src/main/java/info/nightscout/androidaps/utils/dragHelpers/SimpleItemTouchHelperCallback.kt similarity index 52% rename from automation/src/main/java/info/nightscout/androidaps/plugins/general/automation/dragHelpers/SimpleItemTouchHelperCallback.kt rename to core/src/main/java/info/nightscout/androidaps/utils/dragHelpers/SimpleItemTouchHelperCallback.kt index 76d15f5477..2dc27917bc 100644 --- a/automation/src/main/java/info/nightscout/androidaps/plugins/general/automation/dragHelpers/SimpleItemTouchHelperCallback.kt +++ b/core/src/main/java/info/nightscout/androidaps/utils/dragHelpers/SimpleItemTouchHelperCallback.kt @@ -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.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 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 { - return false - } +class SimpleItemTouchHelperCallback : ItemTouchHelper.SimpleCallback(UP or DOWN or START or END, 0) { + + override fun isLongPressDragEnabled() = false 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 to = target.layoutPosition adapter.onItemMove(from, to) - adapter.notifyItemMoved(from, to) return true } @@ -24,14 +28,14 @@ class SimpleItemTouchHelperCallback : ItemTouchHelper.SimpleCallback(ItemTouchHe override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) { super.onSelectedChanged(viewHolder, actionState) - if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) { - viewHolder?.itemView?.alpha = 0.5f + if (actionState == ACTION_STATE_DRAG) { + viewHolder?.itemView?.alpha = ALPHA_DRAGGING } } override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { super.clearView(recyclerView, viewHolder) - viewHolder.itemView.alpha = 1.0f - (recyclerView.adapter as AutomationFragment.EventListAdapter).onDrop() + viewHolder.itemView.alpha = ALPHA_FULL + (recyclerView.adapter as ItemTouchHelperAdapter).onDrop() } } diff --git a/core/src/main/res/drawable/ic_loop_closed_off.xml b/core/src/main/res/drawable/ic_loop_closed_off.xml new file mode 100644 index 0000000000..6270159c4b --- /dev/null +++ b/core/src/main/res/drawable/ic_loop_closed_off.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/core/src/main/res/drawable/ic_sort.xml b/core/src/main/res/drawable/ic_sort.xml new file mode 100644 index 0000000000..4b1317ac20 --- /dev/null +++ b/core/src/main/res/drawable/ic_sort.xml @@ -0,0 +1,11 @@ + + + diff --git a/core/src/main/res/drawable/ic_trash.xml b/core/src/main/res/drawable/ic_trash.xml new file mode 100644 index 0000000000..770ca733c8 --- /dev/null +++ b/core/src/main/res/drawable/ic_trash.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/menu/menu_quickwizard.xml b/core/src/main/res/menu/menu_actions.xml similarity index 52% rename from app/src/main/res/menu/menu_quickwizard.xml rename to core/src/main/res/menu/menu_actions.xml index 7a3e86905d..798c6a5da2 100644 --- a/app/src/main/res/menu/menu_quickwizard.xml +++ b/core/src/main/res/menu/menu_actions.xml @@ -1,14 +1,19 @@ - - - - - - - + + + + + + + diff --git a/automation/src/main/res/menu/menu_delete_selection.xml b/core/src/main/res/menu/menu_delete_selection.xml similarity index 75% rename from automation/src/main/res/menu/menu_delete_selection.xml rename to core/src/main/res/menu/menu_delete_selection.xml index 1090412c5a..4fb8d9e96a 100644 --- a/automation/src/main/res/menu/menu_delete_selection.xml +++ b/core/src/main/res/menu/menu_delete_selection.xml @@ -4,8 +4,8 @@ + app:showAsAction="ifRoom" /> diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index 1e2c53c19e..74e30a005c 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -556,6 +556,11 @@ %1$.0f%% Basal Basal % + %1$d selected + Sort + Remove Items + Sort Items + Remove Selected Items %1$d day diff --git a/icons/loop_off.svg b/icons/loop_off.svg new file mode 100644 index 0000000000..5007475930 --- /dev/null +++ b/icons/loop_off.svg @@ -0,0 +1,19 @@ + + + + + + + + + + +