diff --git a/app/src/main/java/info/nightscout/androidaps/activities/TreatmentsActivity.kt b/app/src/main/java/info/nightscout/androidaps/activities/TreatmentsActivity.kt index 9a68aeaf5f..5290b48711 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/TreatmentsActivity.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/TreatmentsActivity.kt @@ -1,13 +1,13 @@ package info.nightscout.androidaps.activities import android.os.Bundle +import android.view.MenuItem import android.view.View import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentTransaction import info.nightscout.androidaps.R import info.nightscout.androidaps.activities.fragments.* import info.nightscout.androidaps.databinding.TreatmentsFragmentBinding -import info.nightscout.androidaps.extensions.toVisibility import info.nightscout.androidaps.interfaces.ActivePlugin import info.nightscout.androidaps.utils.buildHelper.BuildHelper import javax.inject.Inject @@ -23,40 +23,60 @@ class TreatmentsActivity : NoSplashAppCompatActivity() { super.onCreate(savedInstanceState) binding = TreatmentsFragmentBinding.inflate(layoutInflater) setContentView(binding.root) - //binding.tempBasals.visibility = buildHelper.isEngineeringMode().toVisibility() //binding.extendedBoluses.visibility = (buildHelper.isEngineeringMode() && !activePlugin.activePump.isFakingTempsByExtendedBoluses).toVisibility() binding.treatments.setOnClickListener { setFragment(TreatmentsBolusCarbsFragment()) setBackgroundColorOnSelected(it) + supportActionBar?.title = rh.gs(R.string.carbs_and_bolus) } binding.extendedBoluses.setOnClickListener { setFragment(TreatmentsExtendedBolusesFragment()) setBackgroundColorOnSelected(it) + supportActionBar?.title = rh.gs(R.string.extended_bolus) } binding.tempBasals.setOnClickListener { setFragment(TreatmentsTemporaryBasalsFragment()) setBackgroundColorOnSelected(it) + supportActionBar?.title = rh.gs(R.string.tempbasal_label) } binding.tempTargets.setOnClickListener { setFragment(TreatmentsTempTargetFragment()) setBackgroundColorOnSelected(it) + supportActionBar?.title = rh.gs(R.string.tempt_targets) } binding.profileSwitches.setOnClickListener { setFragment(TreatmentsProfileSwitchFragment()) setBackgroundColorOnSelected(it) + supportActionBar?.title = rh.gs(R.string.profile_changes) } binding.careportal.setOnClickListener { setFragment(TreatmentsCareportalFragment()) setBackgroundColorOnSelected(it) + supportActionBar?.title = rh.gs(R.string.careportal) } binding.userentry.setOnClickListener { setFragment(TreatmentsUserEntryFragment()) setBackgroundColorOnSelected(it) + supportActionBar?.title = rh.gs(R.string.user_action) } setFragment(TreatmentsBolusCarbsFragment()) setBackgroundColorOnSelected(binding.treatments) + setSupportActionBar(binding.toolbar) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + supportActionBar?.title = rh.gs(R.string.carbs_and_bolus) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + android.R.id.home -> { + finish() + true + } + + else -> false + } } private fun setFragment(selectedFragment: Fragment) { 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 3ed805966e..1323b23113 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 @@ -3,9 +3,13 @@ package info.nightscout.androidaps.activities.fragments import android.annotation.SuppressLint import android.graphics.Paint import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup +import android.util.Log +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 import dagger.android.support.DaggerFragment @@ -67,6 +71,10 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() { @Inject lateinit var repository: AppRepository @Inject lateinit var activePlugin: ActivePlugin + private var _binding: TreatmentsBolusCarbsFragmentBinding? = null + // This property is only valid between onCreateView and onDestroyView. + private val binding get() = _binding!! + class MealLink( val bolus: Bolus? = null, val carbs: Carbs? = null, @@ -74,14 +82,12 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() { ) private val disposable = CompositeDisposable() - private val millsToThePast = T.days(30).msecs() - private var _binding: TreatmentsBolusCarbsFragmentBinding? = null - - // This property is only valid between onCreateView and - // onDestroyView. - private val binding get() = _binding!! + 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 @@ -89,92 +95,10 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() { @SuppressLint("CheckResult") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + setHasOptionsMenu(true) + toolbar = activity?.findViewById(R.id.toolbar) binding.recyclerview.setHasFixedSize(true) binding.recyclerview.layoutManager = LinearLayoutManager(view.context) - - binding.refreshFromNightscout.setOnClickListener { - activity?.let { activity -> - OKDialog.showConfirmation(activity, rh.gs(R.string.refresheventsfromnightscout) + "?") { - uel.log(Action.TREATMENTS_NS_REFRESH, Sources.Treatments) - disposable += - Completable.fromAction { - repository.deleteAllBolusCalculatorResults() - repository.deleteAllBoluses() - repository.deleteAllCarbs() - } - .subscribeOn(aapsSchedulers.io) - .observeOn(aapsSchedulers.main) - .subscribeBy( - onError = { aapsLogger.error("Error removing entries", it) }, - onComplete = { - rxBus.send(EventTreatmentChange()) - rxBus.send(EventNewHistoryData(0, false)) - } - ) - rxBus.send(EventNSClientRestart()) - } - } - } - binding.deleteFutureTreatments.setOnClickListener { - activity?.let { activity -> - OKDialog.showConfirmation(activity, rh.gs(R.string.overview_treatment_label), rh.gs(R.string.deletefuturetreatments) + "?", Runnable { - uel.log(Action.DELETE_FUTURE_TREATMENTS, Sources.Treatments) - repository - .getBolusesDataFromTime(dateUtil.now(), false) - .observeOn(aapsSchedulers.main) - .subscribe { list -> - list.forEach { bolus -> - 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) } - ) - } - } - repository - .getCarbsDataFromTimeNotExpanded(dateUtil.now(), false) - .observeOn(aapsSchedulers.main) - .subscribe { list -> - list.forEach { carb -> - if (carb.duration == 0L) - 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) } - ) - else { - disposable += repository.runTransactionForResult(CutCarbsTransaction(carb.id, dateUtil.now())) - .subscribe( - { result -> - result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated carbs $it") } - result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated (cut end) carbs $it") } - }, - { aapsLogger.error(LTag.DATABASE, "Error while invalidating carbs", it) } - ) - } - } - } - repository - .getBolusCalculatorResultsDataFromTime(dateUtil.now(), false) - .observeOn(aapsSchedulers.main) - .subscribe { list -> - list.forEach { bolusCalc -> - disposable += repository.runTransactionForResult(InvalidateBolusCalculatorResultTransaction(bolusCalc.id)) - .subscribe( - { result -> result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated bolusCalculatorResult $it") } }, - { aapsLogger.error(LTag.DATABASE, "Error while invalidating bolusCalculatorResult", it) } - ) - } - } - binding.deleteFutureTreatments.visibility = View.GONE - }) - } - } - val nsUploadOnly = !sp.getBoolean(R.string.key_ns_receive_insulin, false) || !sp.getBoolean(R.string.key_ns_receive_carbs, false) || !buildHelper.isEngineeringMode() - if (nsUploadOnly) binding.refreshFromNightscout.visibility = View.GONE - binding.showInvalidated.setOnCheckedChangeListener { _, _ -> - rxBus.send(EventTreatmentUpdateGui()) - } } private fun bolusMealLinksWithInvalid(now: Long) = repository @@ -204,36 +128,35 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() { fun swapAdapter() { val now = System.currentTimeMillis() - if (binding.showInvalidated.isChecked) - disposable += carbsMealLinksWithInvalid(now) - .zipWith(bolusMealLinksWithInvalid(now)) { first, second -> first + second } - .zipWith(calcResultMealLinksWithInvalid(now)) { first, second -> first + second } - .map { ml -> - ml.sortedByDescending { - it.carbs?.timestamp ?: it.bolus?.timestamp - ?: it.bolusCalculatorResult?.timestamp + disposable += + if (showInvalidated) + carbsMealLinksWithInvalid(now) + .zipWith(bolusMealLinksWithInvalid(now)) { first, second -> first + second } + .zipWith(calcResultMealLinksWithInvalid(now)) { first, second -> first + second } + .map { ml -> + ml.sortedByDescending { + it.carbs?.timestamp ?: it.bolus?.timestamp + ?: it.bolusCalculatorResult?.timestamp + } } - } - .observeOn(aapsSchedulers.main) - .subscribe { list -> - binding.recyclerview.swapAdapter(RecyclerViewAdapter(list), true) - binding.deleteFutureTreatments.visibility = list.isNotEmpty().toVisibility() - } - else - disposable += carbsMealLinks(now) - .zipWith(bolusMealLinks(now)) { first, second -> first + second } - .zipWith(calcResultMealLinks(now)) { first, second -> first + second } - .map { ml -> - ml.sortedByDescending { - it.carbs?.timestamp ?: it.bolus?.timestamp - ?: it.bolusCalculatorResult?.timestamp + .observeOn(aapsSchedulers.main) + .subscribe { list -> + binding.recyclerview.swapAdapter(RecyclerViewAdapter(list), true) + } + else + carbsMealLinks(now) + .zipWith(bolusMealLinks(now)) { first, second -> first + second } + .zipWith(calcResultMealLinks(now)) { first, second -> first + second } + .map { ml -> + ml.sortedByDescending { + it.carbs?.timestamp ?: it.bolus?.timestamp + ?: it.bolusCalculatorResult?.timestamp + } + } + .observeOn(aapsSchedulers.main) + .subscribe { list -> + binding.recyclerview.swapAdapter(RecyclerViewAdapter(list), true) } - } - .observeOn(aapsSchedulers.main) - .subscribe { list -> - binding.recyclerview.swapAdapter(RecyclerViewAdapter(list), true) - binding.deleteFutureTreatments.visibility = list.isNotEmpty().toVisibility() - } } @@ -267,13 +190,14 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() { @Synchronized override fun onDestroyView() { super.onDestroyView() + removeActionMode?.let { it.finish() } binding.recyclerview.adapter = null // avoid leaks _binding = null } - private fun timestamp(ml: MealLink): Long = ml.bolusCalculatorResult?.let { it.timestamp } ?: ml.bolus?.let { it.timestamp } ?: ml.carbs?.let { it.timestamp } ?: 0L + private fun timestamp(ml: MealLink): Long = ml.bolusCalculatorResult?.timestamp ?: ml.bolus?.timestamp ?: ml.carbs?.timestamp ?: 0L - inner class RecyclerViewAdapter internal constructor(var mealLinks: List) : RecyclerView.Adapter() { + inner class RecyclerViewAdapter internal constructor(private var mealLinks: List) : RecyclerView.Adapter() { override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): MealLinkLoadedViewHolder = MealLinkLoadedViewHolder(LayoutInflater.from(viewGroup.context).inflate(R.layout.treatments_bolus_carbs_item, viewGroup, false)) @@ -287,13 +211,13 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() { holder.binding.date.text = dateUtil.dateString(timestamp(ml)) // Metadata - holder.binding.metadataLayout.visibility = (ml.bolusCalculatorResult != null && (ml.bolusCalculatorResult.isValid || binding.showInvalidated.isChecked)).toVisibility() + holder.binding.metadataLayout.visibility = (ml.bolusCalculatorResult != null && (ml.bolusCalculatorResult.isValid || showInvalidated)).toVisibility() ml.bolusCalculatorResult?.let { bolusCalculatorResult -> holder.binding.calcTime.text = dateUtil.timeString(bolusCalculatorResult.timestamp) } // Bolus - holder.binding.bolusLayout.visibility = (ml.bolus != null && (ml.bolus.isValid || binding.showInvalidated.isChecked)).toVisibility() + holder.binding.bolusLayout.visibility = (ml.bolus != null && (ml.bolus.isValid || showInvalidated)).toVisibility() ml.bolus?.let { bolus -> holder.binding.bolusTime.text = dateUtil.timeString(bolus.timestamp) holder.binding.insulin.text = rh.gs(R.string.formatinsulinunits, bolus.amount) @@ -321,7 +245,7 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() { } } // Carbs - holder.binding.carbsLayout.visibility = (ml.carbs != null && (ml.carbs.isValid || binding.showInvalidated.isChecked)).toVisibility() + holder.binding.carbsLayout.visibility = (ml.carbs != null && (ml.carbs.isValid || showInvalidated)).toVisibility() ml.carbs?.let { carbs -> holder.binding.carbsTime.text = dateUtil.timeString(carbs.timestamp) holder.binding.carbs.text = rh.gs(R.string.format_carbs, carbs.amount.toInt()) @@ -330,19 +254,28 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() { holder.binding.carbsPump.visibility = (carbs.interfaceIDs.pumpId != null).toVisibility() holder.binding.carbsInvalid.visibility = carbs.isValid.not().toVisibility() } - - holder.binding.bolusRemove.visibility = (ml.bolus?.isValid == true).toVisibility() - holder.binding.carbsRemove.visibility = (ml.carbs?.isValid == true).toVisibility() - holder.binding.bolusRemove.tag = ml - holder.binding.carbsRemove.tag = ml + 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) + } + removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size()) + } + 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.isSameDay(timestamp(ml), nextTimestamp).toVisibility() } - override fun getItemCount(): Int { - return mealLinks.size - } + override fun getItemCount() = mealLinks.size inner class MealLinkLoadedViewHolder internal constructor(view: View) : RecyclerView.ViewHolder(view) { @@ -359,35 +292,199 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() { } } binding.calculation.paintFlags = binding.calculation.paintFlags or Paint.UNDERLINE_TEXT_FLAG - binding.bolusRemove.setOnClickListener { ml -> - val bolus = (ml.tag as MealLink?)?.bolus ?: return@setOnClickListener - activity?.let { activity -> - val text = rh.gs(R.string.configbuilder_insulin) + ": " + - rh.gs(R.string.formatinsulinunits, bolus.amount) + "\n" + - rh.gs(R.string.date) + ": " + dateUtil.dateAndTimeString(bolus.timestamp) - OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), text, Runnable { + } + } + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.menu_treatments_carbs_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 + 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 + menu.findItem(R.id.nav_delete_future)?.isVisible = hasItems + + 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_show_invalidated -> { + showInvalidated = true + rxBus.send(EventTreatmentUpdateGui()) + true + } + + R.id.nav_hide_invalidated -> { + showInvalidated = false + rxBus.send(EventTreatmentUpdateGui()) + true + } + + R.id.nav_delete_future -> { + deleteFutureTreatments() + true + } + + R.id.nav_refresh_ns -> { + refreshFromNightscout() + true + } + + else -> false + } + + private fun refreshFromNightscout() { + activity?.let { activity -> + OKDialog.showConfirmation(activity, rh.gs(R.string.refresheventsfromnightscout) + "?") { + uel.log(Action.TREATMENTS_NS_REFRESH, Sources.Treatments) + disposable += + Completable.fromAction { + repository.deleteAllBolusCalculatorResults() + repository.deleteAllBoluses() + repository.deleteAllCarbs() + } + .subscribeOn(aapsSchedulers.io) + .observeOn(aapsSchedulers.main) + .subscribeBy( + onError = { aapsLogger.error("Error removing entries", it) }, + onComplete = { + rxBus.send(EventTreatmentChange()) + rxBus.send(EventNewHistoryData(0, false)) + } + ) + rxBus.send(EventNSClientRestart()) + } + } + } + + fun deleteFutureTreatments() { + activity?.let { activity -> + OKDialog.showConfirmation(activity, rh.gs(R.string.overview_treatment_label), rh.gs(R.string.deletefuturetreatments) + "?", Runnable { + uel.log(Action.DELETE_FUTURE_TREATMENTS, Sources.Treatments) + disposable += repository + .getBolusesDataFromTime(dateUtil.now(), false) + .observeOn(aapsSchedulers.main) + .subscribe { list -> + list.forEach { bolus -> + 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 + .getCarbsDataFromTimeNotExpanded(dateUtil.now(), false) + .observeOn(aapsSchedulers.main) + .subscribe { list -> + list.forEach { carb -> + if (carb.duration == 0L) + 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) } + ) + else { + disposable += repository.runTransactionForResult(CutCarbsTransaction(carb.id, dateUtil.now())) + .subscribe( + { result -> + result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated carbs $it") } + result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated (cut end) carbs $it") } + }, + { aapsLogger.error(LTag.DATABASE, "Error while invalidating carbs", it) } + ) + } + } + } + disposable += repository + .getBolusCalculatorResultsDataFromTime(dateUtil.now(), false) + .observeOn(aapsSchedulers.main) + .subscribe { list -> + list.forEach { bolusCalc -> + disposable += repository.runTransactionForResult(InvalidateBolusCalculatorResultTransaction(bolusCalc.id)) + .subscribe( + { result -> result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated bolusCalculatorResult $it") } }, + { aapsLogger.error(LTag.DATABASE, "Error while invalidating bolusCalculatorResult", it) } + ) + } + } + }) + } + } + + 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 { + if (selectedItems.size() == 1) { + val mealLink = selectedItems.valueAt(0) + val bolus = mealLink.bolus + if (bolus != null) + return rh.gs(R.string.configbuilder_insulin) + ": " + rh.gs(R.string.formatinsulinunits, bolus.amount) + "\n" + + rh.gs(R.string.date) + ": " + dateUtil.dateAndTimeString(bolus.timestamp) + val carbs = mealLink.carbs + if (carbs != null) + return rh.gs(R.string.carbs) + ": " + rh.gs(R.string.format_carbs, carbs.amount.toInt()) + "\n" + + rh.gs(R.string.date) + ": " + dateUtil.dateAndTimeString(carbs.timestamp) + } + 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 {key, ml -> + ml.bolus?.let { bolus -> uel.log( Action.BOLUS_REMOVED, Sources.Treatments, ValueWithUnit.Timestamp(bolus.timestamp), ValueWithUnit.Insulin(bolus.amount) - //ValueWithUnit.Gram(mealLinkLoaded.carbs.toInt()) ) 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) } ) - }) - } - } - binding.bolusRemove.paintFlags = binding.bolusRemove.paintFlags or Paint.UNDERLINE_TEXT_FLAG - binding.carbsRemove.setOnClickListener { ml -> - val carb = (ml.tag as MealLink?)?.carbs ?: return@setOnClickListener - activity?.let { activity -> - val text = rh.gs(R.string.carbs) + ": " + - rh.gs(R.string.carbs) + ": " + rh.gs(R.string.format_carbs, carb.amount.toInt()) + "\n" + - rh.gs(R.string.date) + ": " + dateUtil.dateAndTimeString(carb.timestamp) - OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), text, Runnable { + } + ml.carbs?.let { carb -> uel.log( Action.CARBS_REMOVED, Sources.Treatments, ValueWithUnit.Timestamp(carb.timestamp), @@ -398,11 +495,12 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() { { result -> result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated carbs $it") } }, { aapsLogger.error(LTag.DATABASE, "Error while invalidating carbs", it) } ) - }) + } } - } - binding.carbsRemove.paintFlags = binding.carbsRemove.paintFlags or Paint.UNDERLINE_TEXT_FLAG + removeActionMode?.finish() + }) } - } + else + removeActionMode?.finish() } -} \ No newline at end of file +} 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 0dca0e416f..d2069a504b 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 @@ -1,10 +1,11 @@ package info.nightscout.androidaps.activities.fragments -import android.graphics.Paint import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup +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 @@ -57,61 +58,61 @@ class TreatmentsCareportalFragment : DaggerFragment() { @Inject lateinit var repository: AppRepository @Inject lateinit var uel: UserEntryLogger - private val disposable = CompositeDisposable() - - private val millsToThePast = T.days(30).msecs() - private var _binding: TreatmentsCareportalFragmentBinding? = null - - // This property is only valid between onCreateView and - // onDestroyView. + // This property is only valid between onCreateView and onDestroyView. private val binding get() = _binding!! + private val 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 = 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) + setHasOptionsMenu(true) binding.recyclerview.setHasFixedSize(true) binding.recyclerview.layoutManager = LinearLayoutManager(view.context) - binding.refreshFromNightscout.setOnClickListener { - activity?.let { activity -> - OKDialog.showConfirmation(activity, rh.gs(R.string.careportal), rh.gs(R.string.refresheventsfromnightscout) + " ?", Runnable { - uel.log(Action.CAREPORTAL_NS_REFRESH, Sources.Treatments) - disposable += Completable.fromAction { repository.deleteAllTherapyEventsEntries() } - .subscribeOn(aapsSchedulers.io) - .subscribeBy( - onError = { aapsLogger.error("Error removing entries", it) }, - onComplete = { rxBus.send(EventTherapyEventChange()) } - ) - rxBus.send(EventNSClientRestart()) - }) - } - } - binding.removeAndroidapsStartedEvents.setOnClickListener { - activity?.let { activity -> - OKDialog.showConfirmation(activity, rh.gs(R.string.careportal), rh.gs(R.string.careportal_removestartedevents), Runnable { - uel.log(Action.RESTART_EVENTS_REMOVED, Sources.Treatments) - repository.runTransactionForResult(InvalidateAAPSStartedTherapyEventTransaction(rh.gs(R.string.androidaps_start))) - .subscribe( - { result -> result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated therapy event $it") } }, - { aapsLogger.error(LTag.DATABASE, "Error while invalidating therapy event", it) } - ) - }, null) - } - } + } - val nsUploadOnly = !sp.getBoolean(R.string.key_ns_receive_therapy_events, false) || !buildHelper.isEngineeringMode() - if (nsUploadOnly) binding.refreshFromNightscout.visibility = View.GONE - binding.showInvalidated.setOnCheckedChangeListener { _, _ -> - rxBus.send(EventTreatmentUpdateGui()) + private fun refreshFromNightscout() { + activity?.let { activity -> + OKDialog.showConfirmation(activity, rh.gs(R.string.careportal), rh.gs(R.string.refresheventsfromnightscout) + " ?", Runnable { + uel.log(Action.CAREPORTAL_NS_REFRESH, Sources.Treatments) + disposable += Completable.fromAction { repository.deleteAllTherapyEventsEntries() } + .subscribeOn(aapsSchedulers.io) + .subscribeBy( + onError = { aapsLogger.error("Error removing entries", it) }, + onComplete = { rxBus.send(EventTherapyEventChange()) } + ) + rxBus.send(EventNSClientRestart()) + }) + } + } + + private fun removeStartedEvents() { + activity?.let { activity -> + OKDialog.showConfirmation(activity, rh.gs(R.string.careportal), rh.gs(R.string.careportal_removestartedevents), Runnable { + uel.log(Action.RESTART_EVENTS_REMOVED, Sources.Treatments) + disposable += repository.runTransactionForResult(InvalidateAAPSStartedTherapyEventTransaction(rh.gs(R.string.androidaps_start))) + .subscribe( + { result -> result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated therapy event $it") } }, + { aapsLogger.error(LTag.DATABASE, "Error while invalidating therapy event", it) } + ) + }) } } fun swapAdapter() { val now = System.currentTimeMillis() disposable += - if (binding.showInvalidated.isChecked) + if (showInvalidated) repository .getTherapyEventDataIncludingInvalidFromTime(now - millsToThePast, false) .observeOn(aapsSchedulers.main) @@ -148,6 +149,7 @@ class TreatmentsCareportalFragment : DaggerFragment() { @Synchronized override fun onDestroyView() { super.onDestroyView() + removeActionMode?.let { it.finish() } binding.recyclerview.adapter = null // avoid leaks _binding = null } @@ -170,42 +172,137 @@ 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.remove.tag = therapyEvent + 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 + } val nextTimestamp = if (therapyList.size != position + 1) therapyList[position + 1].timestamp else 0L holder.binding.delimiter.visibility = dateUtil.isSameDay(therapyEvent.timestamp, nextTimestamp).toVisibility() } - override fun getItemCount(): Int { - return therapyList.size - } + override fun getItemCount() = therapyList.size inner class TherapyEventsViewHolder(view: View) : RecyclerView.ViewHolder(view) { val binding = TreatmentsCareportalItemBinding.bind(view) - - init { - binding.remove.setOnClickListener { v: View -> - val therapyEvent = v.tag as TherapyEvent - activity?.let { activity -> - val text = rh.gs(R.string.eventtype) + ": " + translator.translate(therapyEvent.type) + "\n" + - rh.gs(R.string.notes_label) + ": " + (therapyEvent.note - ?: "") + "\n" + - rh.gs(R.string.date) + ": " + dateUtil.dateAndTimeString(therapyEvent.timestamp) - OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), text, Runnable { - 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) } - ) - }, null) - } - } - binding.remove.paintFlags = binding.remove.paintFlags or Paint.UNDERLINE_TEXT_FLAG - } } } -} \ No newline at end of file + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + 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 + 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) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean = + when (item.itemId) { + R.id.nav_remove_items -> { + removeActionMode = toolbar?.startActionMode(RemoveActionModeCallback()) + true + } + + R.id.nav_show_invalidated -> { + showInvalidated = true + rxBus.send(EventTreatmentUpdateGui()) + true + } + + R.id.nav_hide_invalidated -> { + showInvalidated = false + rxBus.send(EventTreatmentUpdateGui()) + true + } + + R.id.nav_remove_started_events -> { + removeStartedEvents() + true + } + + R.id.nav_refresh_ns -> { + refreshFromNightscout() + 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() + } + } + + private fun getConfirmationText(): String { + if (selectedItems.size() == 1) { + val therapyEvent = selectedItems.valueAt(0) + return rh.gs(R.string.eventtype) + ": " + translator.translate(therapyEvent.type) + "\n" + + rh.gs(R.string.notes_label) + ": " + (therapyEvent.note ?: "") + "\n" + + rh.gs(R.string.date) + ": " + dateUtil.dateAndTimeString(therapyEvent.timestamp) + } + 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) + ) + 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() + } + +} 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 bdd0e4f424..57e766dc54 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,12 +1,12 @@ package info.nightscout.androidaps.activities.fragments import android.annotation.SuppressLint -import android.content.DialogInterface -import android.graphics.Paint import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup +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 @@ -27,16 +27,17 @@ 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.TreatmentsExtendedBolusesFragment.RecyclerViewAdapter.ExtendedBolusesViewHolder +import info.nightscout.androidaps.events.EventTreatmentUpdateGui import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.T 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.LTag import io.reactivex.disposables.CompositeDisposable import io.reactivex.rxkotlin.plusAssign import java.util.concurrent.TimeUnit @@ -60,23 +61,27 @@ class TreatmentsExtendedBolusesFragment : DaggerFragment() { @Inject lateinit var repository: AppRepository private var _binding: TreatmentsExtendedbolusFragmentBinding? = null - - // This property is only valid between onCreateView and - // onDestroyView. + // This property is only valid between onCreateView and onDestroyView. private val binding get() = _binding!! - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?): View = + 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 = TreatmentsExtendedbolusFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + setHasOptionsMenu(true) + toolbar = activity?.findViewById(R.id.toolbar) binding.recyclerview.setHasFixedSize(true) binding.recyclerview.layoutManager = LinearLayoutManager(view.context) } fun swapAdapter() { val now = System.currentTimeMillis() - if (binding.showInvalidated.isChecked) + disposable += if (showInvalidated) repository .getExtendedBolusDataIncludingInvalidFromTime(now - millsToThePast, false) .observeOn(aapsSchedulers.main) @@ -109,6 +114,7 @@ class TreatmentsExtendedBolusesFragment : DaggerFragment() { @Synchronized override fun onDestroyView() { super.onDestroyView() + removeActionMode?.let { it.finish() } binding.recyclerview.adapter = null // avoid leaks _binding = null } @@ -125,7 +131,7 @@ class TreatmentsExtendedBolusesFragment : DaggerFragment() { holder.binding.ns.visibility = (extendedBolus.interfaceIDs.nightscoutId != null).toVisibility() holder.binding.ph.visibility = (extendedBolus.interfaceIDs.pumpId != null).toVisibility() holder.binding.invalid.visibility = extendedBolus.isValid.not().toVisibility() - val sameDayPrevious = position > 0 && dateUtil.isSameDay(extendedBolus.timestamp, extendedBolusList[position-1].timestamp) + val sameDayPrevious = position > 0 && dateUtil.isSameDay(extendedBolus.timestamp, extendedBolusList[position - 1].timestamp) holder.binding.date.visibility = sameDayPrevious.not().toVisibility() holder.binding.date.text = dateUtil.dateString(extendedBolus.timestamp) @SuppressLint("SetTextI18n") @@ -143,41 +149,125 @@ 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.gc(R.color.colorActive)) else holder.binding.iob.setTextColor(holder.binding.insulin.currentTextColor) - holder.binding.remove.tag = extendedBolus + holder.binding.cbRemove.visibility = (extendedBolus.isValid && removeActionMode != null).toVisibility() + if (removeActionMode != null) { + 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()) + } + holder.binding.cbRemove.isChecked = selectedItems.get(position) != null + } val nextTimestamp = if (extendedBolusList.size != position + 1) extendedBolusList[position + 1].timestamp else 0L holder.binding.delimiter.visibility = dateUtil.isSameDay(extendedBolus.timestamp, nextTimestamp).toVisibility() } - override fun getItemCount(): Int = extendedBolusList.size + override fun getItemCount() = extendedBolusList.size inner class ExtendedBolusesViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val binding = TreatmentsExtendedbolusItemBinding.bind(itemView) - - init { - binding.remove.setOnClickListener { v: View -> - val extendedBolus = v.tag as ExtendedBolus - context?.let { context -> - OKDialog.showConfirmation(context, rh.gs(R.string.removerecord), - """ - ${rh.gs(R.string.extended_bolus)} - ${rh.gs(R.string.date)}: ${dateUtil.dateAndTimeString(extendedBolus.timestamp)} - """.trimIndent(), { _: DialogInterface, _: Int -> - 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) }) - }, null) - } - } - binding.remove.paintFlags = binding.remove.paintFlags or Paint.UNDERLINE_TEXT_FLAG - } } } -} \ No newline at end of file + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + 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 + + 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_show_invalidated -> { + showInvalidated = true + rxBus.send(EventTreatmentUpdateGui()) + true + } + + R.id.nav_hide_invalidated -> { + showInvalidated = false + rxBus.send(EventTreatmentUpdateGui()) + 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() + } + } + + private fun getConfirmationText(): String { + if (selectedItems.size() == 1) { + val bolus = selectedItems.valueAt(0) + return rh.gs(R.string.extended_bolus) + "\n" + + "${rh.gs(R.string.date)}: ${dateUtil.dateAndTimeString(bolus.timestamp)}" + } + 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() + } +} 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 91b46228be..2922ead097 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 @@ -2,9 +2,11 @@ package info.nightscout.androidaps.activities.fragments import android.graphics.Paint import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup +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 @@ -61,50 +63,50 @@ 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 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 - // This property is only valid between onCreateView and - // onDestroyView. - private val binding get() = _binding!! override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = TreatmentsProfileswitchFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + setHasOptionsMenu(true) + toolbar = activity?.findViewById(R.id.toolbar) binding.recyclerview.setHasFixedSize(true) binding.recyclerview.layoutManager = LinearLayoutManager(view.context) + } - binding.refreshFromNightscout.setOnClickListener { - activity?.let { activity -> - OKDialog.showConfirmation(activity, rh.gs(R.string.refresheventsfromnightscout) + "?") { - uel.log(Action.TREATMENTS_NS_REFRESH, Sources.Treatments) - disposable += - Completable.fromAction { - repository.deleteAllEffectiveProfileSwitches() - repository.deleteAllProfileSwitches() - } - .subscribeOn(aapsSchedulers.io) - .observeOn(aapsSchedulers.main) - .subscribeBy( - onError = { aapsLogger.error("Error removing entries", it) }, - onComplete = { - rxBus.send(EventProfileSwitchChanged()) - rxBus.send(EventEffectiveProfileSwitchChanged(0L)) - rxBus.send(EventNewHistoryData(0, false)) - } - ) - rxBus.send(EventNSClientRestart()) - } + private fun refreshFromNightscout() { + activity?.let { activity -> + OKDialog.showConfirmation(activity, rh.gs(R.string.refresheventsfromnightscout) + "?") { + uel.log(Action.TREATMENTS_NS_REFRESH, Sources.Treatments) + disposable += + Completable.fromAction { + repository.deleteAllEffectiveProfileSwitches() + repository.deleteAllProfileSwitches() + } + .subscribeOn(aapsSchedulers.io) + .observeOn(aapsSchedulers.main) + .subscribeBy( + onError = { aapsLogger.error("Error removing entries", it) }, + onComplete = { + rxBus.send(EventProfileSwitchChanged()) + rxBus.send(EventEffectiveProfileSwitchChanged(0L)) + rxBus.send(EventNewHistoryData(0, false)) + } + ) + rxBus.send(EventNSClientRestart()) } } - if (!sp.getBoolean(R.string.key_ns_receive_profile_switch, false) || !buildHelper.isEngineeringMode()) binding.refreshFromNightscout.visibility = View.GONE - binding.showInvalidated.setOnCheckedChangeListener { _, _ -> - rxBus.send(EventTreatmentUpdateGui()) - } } private fun profileSwitchWithInvalid(now: Long) = repository @@ -126,23 +128,23 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() { fun swapAdapter() { val now = System.currentTimeMillis() - if (binding.showInvalidated.isChecked) - disposable += profileSwitchWithInvalid(now) - .zipWith(effectiveProfileSwitchWithInvalid(now)) { first, second -> first + second } - .map { ml -> ml.sortedByDescending { it.timestamp } } - .observeOn(aapsSchedulers.main) - .subscribe { list -> - binding.recyclerview.swapAdapter(RecyclerProfileViewAdapter(list), true) - } - else - disposable += profileSwitches(now) - .zipWith(effectiveProfileSwitches(now)) { first, second -> first + second } - .map { ml -> ml.sortedByDescending { it.timestamp } } - .observeOn(aapsSchedulers.main) - .subscribe { list -> - binding.recyclerview.swapAdapter(RecyclerProfileViewAdapter(list), true) - } - + disposable += + if (showInvalidated) + profileSwitchWithInvalid(now) + .zipWith(effectiveProfileSwitchWithInvalid(now)) { first, second -> first + second } + .map { ml -> ml.sortedByDescending { it.timestamp } } + .observeOn(aapsSchedulers.main) + .subscribe { list -> + binding.recyclerview.swapAdapter(RecyclerProfileViewAdapter(list), true) + } + else + profileSwitches(now) + .zipWith(effectiveProfileSwitches(now)) { first, second -> first + second } + .map { ml -> ml.sortedByDescending { it.timestamp } } + .observeOn(aapsSchedulers.main) + .subscribe { list -> + binding.recyclerview.swapAdapter(RecyclerProfileViewAdapter(list), true) + } } @Synchronized @@ -168,6 +170,7 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() { @Synchronized override fun onDestroyView() { super.onDestroyView() + removeActionMode?.let { it.finish() } binding.recyclerview.adapter = null // avoid leaks _binding = null } @@ -186,16 +189,27 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() { holder.binding.date.text = dateUtil.dateString(profileSwitch.timestamp) holder.binding.time.text = dateUtil.timeString(profileSwitch.timestamp) holder.binding.duration.text = rh.gs(R.string.format_mins, T.msecs(profileSwitch.duration ?: 0L).mins()) - holder.binding.name.text = if (profileSwitch is ProfileSealed.PS) profileSwitch.value.getCustomizedName() else if (profileSwitch is ProfileSealed.EPS) profileSwitch.value.originalCustomizedName else "" + holder.binding.name.text = if (profileSwitch is ProfileSealed.PS) profileSwitch.value.getCustomizedName() + else if (profileSwitch is ProfileSealed.EPS) profileSwitch.value.originalCustomizedName else "" if (profileSwitch.isInProgress(dateUtil)) holder.binding.date.setTextColor(rh.gc(R.color.colorActive)) else holder.binding.date.setTextColor(holder.binding.duration.currentTextColor) - holder.binding.remove.tag = profileSwitch holder.binding.clone.tag = profileSwitch holder.binding.name.tag = profileSwitch 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.remove.visibility = (profileSwitch is ProfileSealed.PS).toVisibility() + holder.binding.cbRemove.visibility = (removeActionMode != null && profileSwitch is ProfileSealed.PS).toVisibility() + if (removeActionMode != null) { + 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()) + } + holder.binding.cbRemove.isChecked = selectedItems.get(position) != null + } holder.binding.clone.visibility = (profileSwitch is ProfileSealed.PS).toVisibility() holder.binding.spacer.visibility = (profileSwitch is ProfileSealed.PS).toVisibility() holder.binding.root.setBackgroundColor(rh.gc(if (profileSwitch is ProfileSealed.PS) R.color.defaultbackground else R.color.list_delimiter)) @@ -203,47 +217,39 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() { holder.binding.delimiter.visibility = dateUtil.isSameDay(profileSwitch.timestamp, nextTimestamp).toVisibility() } - override fun getItemCount(): Int { - return profileSwitchList.size - } + override fun getItemCount() = profileSwitchList.size inner class ProfileSwitchViewHolder internal constructor(itemView: View) : RecyclerView.ViewHolder(itemView) { val binding = TreatmentsProfileswitchItemBinding.bind(itemView) init { - binding.remove.setOnClickListener { view -> - val profileSwitch = view.tag as ProfileSealed.PS - activity?.let { activity -> - OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), - rh.gs(R.string.careportal_profileswitch) + ": " + profileSwitch.profileName + - "\n" + rh.gs(R.string.date) + ": " + dateUtil.dateAndTimeString(profileSwitch.timestamp), Runnable { - 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) } - ) - }) - } - } binding.clone.setOnClickListener { activity?.let { activity -> val profileSwitch = (it.tag as ProfileSealed.PS).value val profileSealed = it.tag as ProfileSealed - OKDialog.showConfirmation(activity, rh.gs(R.string.careportal_profileswitch), rh.gs(R.string.copytolocalprofile) + "\n" + profileSwitch.getCustomizedName() + "\n" + dateUtil.dateAndTimeString(profileSwitch.timestamp), Runnable { - uel.log(Action.PROFILE_SWITCH_CLONED, Sources.Treatments, - profileSwitch.getCustomizedName() + " " + dateUtil.dateAndTimeString(profileSwitch.timestamp).replace(".", "_"), - ValueWithUnit.Timestamp(profileSwitch.timestamp), - ValueWithUnit.SimpleString(profileSwitch.profileName)) - val nonCustomized = profileSealed.convertToNonCustomizedProfile(dateUtil) - localProfilePlugin.addProfile(localProfilePlugin.copyFrom(nonCustomized, profileSwitch.getCustomizedName() + " " + dateUtil.dateAndTimeString(profileSwitch.timestamp).replace(".", "_"))) - rxBus.send(EventLocalProfileChanged()) - }) + OKDialog.showConfirmation( + activity, + rh.gs(R.string.careportal_profileswitch), + rh.gs(R.string.copytolocalprofile) + "\n" + profileSwitch.getCustomizedName() + "\n" + dateUtil.dateAndTimeString(profileSwitch.timestamp), + Runnable { + uel.log( + Action.PROFILE_SWITCH_CLONED, Sources.Treatments, + profileSwitch.getCustomizedName() + " " + dateUtil.dateAndTimeString(profileSwitch.timestamp).replace(".", "_"), + ValueWithUnit.Timestamp(profileSwitch.timestamp), + ValueWithUnit.SimpleString(profileSwitch.profileName) + ) + val nonCustomized = profileSealed.convertToNonCustomizedProfile(dateUtil) + localProfilePlugin.addProfile( + localProfilePlugin.copyFrom( + nonCustomized, + profileSwitch.getCustomizedName() + " " + dateUtil.dateAndTimeString(profileSwitch.timestamp).replace(".", "_") + ) + ) + rxBus.send(EventLocalProfileChanged()) + }) } } - binding.remove.paintFlags = binding.remove.paintFlags or Paint.UNDERLINE_TEXT_FLAG binding.clone.paintFlags = binding.clone.paintFlags or Paint.UNDERLINE_TEXT_FLAG binding.name.setOnClickListener { ProfileViewerDialog().also { pvd -> @@ -266,4 +272,104 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() { } } } -} \ No newline at end of file + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.menu_treatments_profile_switch, 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 + val nsUploadOnly = !sp.getBoolean(R.string.key_ns_receive_profile_switch, false) || !buildHelper.isEngineeringMode() + menu.findItem(R.id.nav_refresh_ns)?.isVisible = !nsUploadOnly + + 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_show_invalidated -> { + showInvalidated = true + rxBus.send(EventTreatmentUpdateGui()) + true + } + + R.id.nav_hide_invalidated -> { + showInvalidated = false + rxBus.send(EventTreatmentUpdateGui()) + true + } + + R.id.nav_refresh_ns -> { + refreshFromNightscout() + 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() + } + } + + private fun getConfirmationText(): 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) + } + 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) + ) + 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() + } +} 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 c408fd4299..2144feb13e 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 @@ -1,12 +1,11 @@ package info.nightscout.androidaps.activities.fragments import android.annotation.SuppressLint -import android.content.DialogInterface -import android.graphics.Paint import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup +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 @@ -30,6 +29,8 @@ 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 @@ -40,6 +41,7 @@ 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.plugins.iob.iobCobCalculator.events.EventNewHistoryData import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.rx.AapsSchedulers import info.nightscout.shared.sharedPreferences.SP @@ -65,57 +67,64 @@ class TreatmentsTempTargetFragment : DaggerFragment() { @Inject lateinit var uel: UserEntryLogger @Inject lateinit var repository: AppRepository - private val disposable = CompositeDisposable() - - private val millsToThePast = T.days(30).msecs() - private var _binding: TreatmentsTemptargetFragmentBinding? = null - - // This property is only valid between onCreateView and - // onDestroyView. + // This property is only valid between onCreateView and onDestroyView. private val binding get() = _binding!! + private val 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 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { binding.recyclerview.setHasFixedSize(true) + toolbar = activity?.findViewById(R.id.toolbar) + setHasOptionsMenu(true) binding.recyclerview.layoutManager = LinearLayoutManager(view.context) - binding.refreshFromNightscout.setOnClickListener { - context?.let { context -> - OKDialog.showConfirmation(context, rh.gs(R.string.refresheventsfromnightscout) + " ?", { - uel.log(Action.TT_NS_REFRESH, Sources.Treatments) - disposable += Completable.fromAction { repository.deleteAllTempTargetEntries() } + } + + private fun refreshFromNightscout() { + activity?.let { activity -> + OKDialog.showConfirmation(activity, rh.gs(R.string.refresheventsfromnightscout) + "?") { + uel.log(Action.TREATMENTS_NS_REFRESH, Sources.Treatments) + disposable += + Completable.fromAction { + repository.deleteAllEffectiveProfileSwitches() + repository.deleteAllProfileSwitches() + } .subscribeOn(aapsSchedulers.io) .observeOn(aapsSchedulers.main) .subscribeBy( onError = { aapsLogger.error("Error removing entries", it) }, - onComplete = { rxBus.send(EventTempTargetChange()) } + onComplete = { + rxBus.send(EventProfileSwitchChanged()) + rxBus.send(EventEffectiveProfileSwitchChanged(0L)) + rxBus.send(EventNewHistoryData(0, false)) + } ) - - rxBus.send(EventNSClientRestart()) - }) + rxBus.send(EventNSClientRestart()) } } - val nsUploadOnly = !sp.getBoolean(R.string.key_ns_receive_temp_target, false) || !buildHelper.isEngineeringMode() - if (nsUploadOnly) binding.refreshFromNightscout.visibility = View.INVISIBLE - binding.showInvalidated.setOnCheckedChangeListener { _, _ -> - rxBus.send(EventTreatmentUpdateGui()) - } } fun swapAdapter() { val now = System.currentTimeMillis() - if (binding.showInvalidated.isChecked) - repository - .getTemporaryTargetDataIncludingInvalidFromTime(now - millsToThePast, false) - .observeOn(aapsSchedulers.main) - .subscribe { list -> binding.recyclerview.swapAdapter(RecyclerViewAdapter(list), true) } - else - repository - .getTemporaryTargetDataFromTime(now - millsToThePast, false) - .observeOn(aapsSchedulers.main) - .subscribe { list -> binding.recyclerview.swapAdapter(RecyclerViewAdapter(list), true) } + disposable += + if (showInvalidated) + repository + .getTemporaryTargetDataIncludingInvalidFromTime(now - millsToThePast, false) + .observeOn(aapsSchedulers.main) + .subscribe { list -> binding.recyclerview.swapAdapter(RecyclerViewAdapter(list), true) } + else + repository + .getTemporaryTargetDataFromTime(now - millsToThePast, false) + .observeOn(aapsSchedulers.main) + .subscribe { list -> binding.recyclerview.swapAdapter(RecyclerViewAdapter(list), true) } } @Synchronized @@ -145,6 +154,7 @@ class TreatmentsTempTargetFragment : DaggerFragment() { @Synchronized override fun onDestroyView() { super.onDestroyView() + removeActionMode?.let { it.finish() } binding.recyclerview.adapter = null // avoid leaks _binding = null } @@ -163,8 +173,19 @@ 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.remove.visibility = tempTarget.isValid.toVisibility() - val sameDayPrevious = position > 0 && dateUtil.isSameDay(tempTarget.timestamp, tempTargetList[position-1].timestamp) + holder.binding.cbRemove.visibility = (tempTarget.isValid && removeActionMode != null).toVisibility() + if (removeActionMode != null) { + 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()) + } + holder.binding.cbRemove.isChecked = selectedItems.get(position) != null + } + val sameDayPrevious = position > 0 && dateUtil.isSameDay(tempTarget.timestamp, tempTargetList[position - 1].timestamp) holder.binding.date.visibility = sameDayPrevious.not().toVisibility() holder.binding.date.text = dateUtil.dateString(tempTarget.timestamp) holder.binding.time.text = dateUtil.timeRangeString(tempTarget.timestamp, tempTarget.end) @@ -177,43 +198,123 @@ class TreatmentsTempTargetFragment : DaggerFragment() { tempTarget.id == currentlyActiveTarget?.id -> rh.gc(R.color.colorActive) tempTarget.timestamp > dateUtil.now() -> rh.gc(R.color.colorScheduled) else -> holder.binding.reasonColon.currentTextColor - }) - holder.binding.remove.tag = tempTarget + } + ) val nextTimestamp = if (tempTargetList.size != position + 1) tempTargetList[position + 1].timestamp else 0L holder.binding.delimiter.visibility = dateUtil.isSameDay(tempTarget.timestamp, nextTimestamp).toVisibility() } - override fun getItemCount(): Int = tempTargetList.size + override fun getItemCount() = tempTargetList.size inner class TempTargetsViewHolder(view: View) : RecyclerView.ViewHolder(view) { val binding = TreatmentsTemptargetItemBinding.bind(view) - init { - binding.remove.setOnClickListener { v: View -> - val tempTarget = v.tag as TemporaryTarget - context?.let { context -> - OKDialog.showConfirmation(context, rh.gs(R.string.removerecord), - """ - ${rh.gs(R.string.careportal_temporarytarget)}: ${tempTarget.friendlyDescription(profileFunction.getUnits(), rh)} - ${dateUtil.dateAndTimeString(tempTarget.timestamp)} - """.trimIndent(), - { _: DialogInterface?, _: Int -> - 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) }) - }, null) - } - } - binding.remove.paintFlags = binding.remove.paintFlags or Paint.UNDERLINE_TEXT_FLAG - } } } -} \ No newline at end of file + + 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) { + inflater.inflate(R.menu.menu_treatments_temp_target, 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 + val nsUploadOnly = !sp.getBoolean(R.string.key_ns_receive_temp_target, false) || !buildHelper.isEngineeringMode() + menu.findItem(R.id.nav_refresh_ns)?.isVisible = !nsUploadOnly + + 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_show_invalidated -> { + showInvalidated = true + rxBus.send(EventTreatmentUpdateGui()) + true + } + + R.id.nav_hide_invalidated -> { + showInvalidated = false + rxBus.send(EventTreatmentUpdateGui()) + true + } + + R.id.nav_refresh_ns -> { + refreshFromNightscout() + 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() + } + } + + private fun getConfirmationText(): String { + if (selectedItems.size() == 1) { + val tempTarget = selectedItems.valueAt(0) + return "${rh.gs(R.string.careportal_temporarytarget)}: ${tempTarget.friendlyDescription(profileFunction.getUnits(), rh)}\n" + + dateUtil.dateAndTimeString(tempTarget.timestamp) + } + return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size()) + } + +} 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 6d4c536236..babd7f64c4 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,11 +1,11 @@ package info.nightscout.androidaps.activities.fragments -import android.content.DialogInterface -import android.graphics.Paint import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup +import android.util.Log +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 @@ -36,6 +36,7 @@ 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.DateUtil import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.T @@ -64,18 +65,22 @@ 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 val millsToThePast = T.days(30).msecs() - - // This property is only valid between onCreateView and - // onDestroyView. - private val binding get() = _binding!! + 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 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + toolbar = activity?.findViewById(R.id.toolbar) + setHasOptionsMenu(true) binding.recyclerview.setHasFixedSize(true) binding.recyclerview.layoutManager = LinearLayoutManager(view.context) } @@ -98,7 +103,7 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() { val now = System.currentTimeMillis() disposable += if (activePlugin.activePump.isFakingTempsByExtendedBoluses) { - if (binding.showInvalidated.isChecked) + if (showInvalidated) tempBasalsWithInvalid(now) .zipWith(extendedBolusesWithInvalid(now)) { first, second -> first + second } .map { list -> list.filterNotNull() } @@ -113,7 +118,7 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() { .observeOn(aapsSchedulers.main) .subscribe { list -> binding.recyclerview.swapAdapter(RecyclerViewAdapter(list), true) } } else { - if (binding.showInvalidated.isChecked) + if (showInvalidated) tempBasalsWithInvalid(now) .observeOn(aapsSchedulers.main) .subscribe { list -> binding.recyclerview.swapAdapter(RecyclerViewAdapter(list), true) } @@ -150,6 +155,7 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() { @Synchronized override fun onDestroyView() { super.onDestroyView() + removeActionMode?.let { it.finish() } binding.recyclerview.adapter = null // avoid leaks _binding = null } @@ -164,7 +170,7 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() { holder.binding.ns.visibility = (tempBasal.interfaceIDs.nightscoutId != null).toVisibility() holder.binding.invalid.visibility = tempBasal.isValid.not().toVisibility() holder.binding.ph.visibility = (tempBasal.interfaceIDs.pumpId != null).toVisibility() - val sameDayPrevious = position > 0 && dateUtil.isSameDay(tempBasal.timestamp, tempBasalList[position-1].timestamp) + val sameDayPrevious = position > 0 && dateUtil.isSameDay(tempBasal.timestamp, tempBasalList[position - 1].timestamp) holder.binding.date.visibility = sameDayPrevious.not().toVisibility() holder.binding.date.text = dateUtil.dateString(tempBasal.timestamp) if (tempBasal.isInProgress) { @@ -187,62 +193,147 @@ 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.gc(R.color.colorActive)) else holder.binding.iob.setTextColor(holder.binding.duration.currentTextColor) - holder.binding.remove.tag = tempBasal - + holder.binding.cbRemove.visibility = (tempBasal.isValid && removeActionMode != null).toVisibility() + if (removeActionMode != null) { + 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()) + } + holder.binding.cbRemove.isChecked = selectedItems.get(position) != null + } val nextTimestamp = if (tempBasalList.size != position + 1) tempBasalList[position + 1].timestamp else 0L holder.binding.delimiter.visibility = dateUtil.isSameDay(tempBasal.timestamp, nextTimestamp).toVisibility() } - override fun getItemCount(): Int = tempBasalList.size + override fun getItemCount() = tempBasalList.size inner class TempBasalsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val binding = TreatmentsTempbasalsItemBinding.bind(itemView) - init { - binding.remove.setOnClickListener { v: View -> - val tempBasal = v.tag as TemporaryBasal - 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 - } - val profile = profileFunction.getProfile(dateUtil.now()) - ?: return@setOnClickListener - context?.let { - OKDialog.showConfirmation(it, rh.gs(R.string.removerecord), - """ - ${if (isFakeExtended) rh.gs(R.string.extended_bolus) else rh.gs(R.string.tempbasal_label)}: ${tempBasal.toStringFull(profile, dateUtil)} - ${rh.gs(R.string.date)}: ${dateUtil.dateAndTimeString(tempBasal.timestamp)} - """.trimIndent(), - { _: DialogInterface?, _: Int -> - 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) }) - } - }, null) - } - } - binding.remove.paintFlags = binding.remove.paintFlags or Paint.UNDERLINE_TEXT_FLAG - } } } -} \ No newline at end of file + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.menu_treatments_temp_basal, 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 + + 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_show_invalidated -> { + showInvalidated = true + rxBus.send(EventTreatmentUpdateGui()) + true + } + + R.id.nav_hide_invalidated -> { + showInvalidated = false + rxBus.send(EventTreatmentUpdateGui()) + 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() + } + } + + private fun getConfirmationText(): String { + if (selectedItems.size() == 1) { + val tempBasal = selectedItems.valueAt(0) + val isFakeExtended = tempBasal.type == TemporaryBasal.Type.FAKE_EXTENDED + val profile = profileFunction.getProfile(dateUtil.now()) + if (profile != null) + return "${if (isFakeExtended) rh.gs(R.string.extended_bolus) else rh.gs(R.string.tempbasal_label)}: ${tempBasal.toStringFull(profile, dateUtil)}\n" + + "${rh.gs(R.string.date)}: ${dateUtil.dateAndTimeString(tempBasal.timestamp)}" + } + 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 {_, 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) }) + } + } + removeActionMode?.finish() + }) + } + else + removeActionMode?.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 4d3620052b..071054d79c 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 @@ -1,9 +1,7 @@ package info.nightscout.androidaps.activities.fragments import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup +import android.view.* import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import dagger.android.support.DaggerFragment @@ -30,6 +28,7 @@ import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.rx.AapsSchedulers import io.reactivex.disposables.CompositeDisposable +import io.reactivex.rxkotlin.plusAssign import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -51,11 +50,10 @@ class TreatmentsUserEntryFragment : DaggerFragment() { private val millsToThePastFiltered = T.days(30).msecs() private val millsToThePastUnFiltered = T.days(3).msecs() - + private var showLoop = false private var _binding: TreatmentsUserEntryFragmentBinding? = null - // This property is only valid between onCreateView and - // onDestroyView. + // This property is only valid between onCreateView and onDestroyView. private val binding get() = _binding!! override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = @@ -63,35 +61,33 @@ class TreatmentsUserEntryFragment : DaggerFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + setHasOptionsMenu(true) binding.recyclerview.setHasFixedSize(true) binding.recyclerview.layoutManager = LinearLayoutManager(view.context) - binding.ueExportToXml.setOnClickListener { - activity?.let { activity -> - OKDialog.showConfirmation(activity, rh.gs(R.string.ue_export_to_csv) + "?") { - uel.log(Action.EXPORT_CSV, Sources.Treatments) - importExportPrefs.exportUserEntriesCsv(activity) - } + } + + fun exportUserEnteries() { + activity?.let { activity -> + OKDialog.showConfirmation(activity, rh.gs(R.string.ue_export_to_csv) + "?") { + uel.log(Action.EXPORT_CSV, Sources.Treatments) + importExportPrefs.exportUserEntriesCsv(activity) } } - binding.showLoop.setOnCheckedChangeListener { _, _ -> - rxBus.send(EventTreatmentUpdateGui()) - } } fun swapAdapter() { val now = System.currentTimeMillis() - if (binding.showLoop.isChecked) - disposable.add( repository - .getUserEntryDataFromTime(now - millsToThePastUnFiltered) - .observeOn(aapsSchedulers.main) - .subscribe { list -> binding.recyclerview.swapAdapter(UserEntryAdapter(list), true) } - ) - else - disposable.add( repository - .getUserEntryFilteredDataFromTime(now - millsToThePastFiltered) - .observeOn(aapsSchedulers.main) - .subscribe { list -> binding.recyclerview.swapAdapter(UserEntryAdapter(list), true) } - ) + disposable += + if (showLoop) + repository + .getUserEntryDataFromTime(now - millsToThePastUnFiltered) + .observeOn(aapsSchedulers.main) + .subscribe { list -> binding.recyclerview.swapAdapter(UserEntryAdapter(list), true) } + else + repository + .getUserEntryFilteredDataFromTime(now - millsToThePastFiltered) + .observeOn(aapsSchedulers.main) + .subscribe { list -> binding.recyclerview.swapAdapter(UserEntryAdapter(list), true) } } @Synchronized @@ -99,15 +95,15 @@ class TreatmentsUserEntryFragment : DaggerFragment() { super.onResume() swapAdapter() - disposable.add(rxBus + disposable += rxBus .toObservable(EventPreferenceChange::class.java) .observeOn(aapsSchedulers.io) - .subscribe({ swapAdapter() }, fabricPrivacy::logException)) - disposable.add(rxBus + .subscribe({ swapAdapter() }, fabricPrivacy::logException) + disposable += rxBus .toObservable(EventTreatmentUpdateGui::class.java) .observeOn(aapsSchedulers.io) .debounce(1L, TimeUnit.SECONDS) - .subscribe({ swapAdapter() }, fabricPrivacy::logException)) + .subscribe({ swapAdapter() }, fabricPrivacy::logException) } @Synchronized @@ -132,7 +128,7 @@ class TreatmentsUserEntryFragment : DaggerFragment() { override fun onBindViewHolder(holder: UserEntryViewHolder, position: Int) { val current = entries[position] - val sameDayPrevious = position > 0 && dateUtil.isSameDay(current.timestamp, entries[position-1].timestamp) + val sameDayPrevious = position > 0 && dateUtil.isSameDay(current.timestamp, entries[position - 1].timestamp) holder.binding.date.visibility = sameDayPrevious.not().toVisibility() holder.binding.date.text = dateUtil.dateString(current.timestamp) holder.binding.time.text = dateUtil.timeStringWithSeconds(current.timestamp) @@ -152,7 +148,40 @@ class TreatmentsUserEntryFragment : DaggerFragment() { val binding = TreatmentsUserEntryItemBinding.bind(itemView) } - override fun getItemCount(): Int = entries.size + override fun getItemCount() = entries.size } -} \ No newline at end of file + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + 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 + + return super.onPrepareOptionsMenu(menu) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean = + when (item.itemId) { + R.id.nav_show_loop -> { + showLoop = true + rxBus.send(EventTreatmentUpdateGui()) + true + } + + R.id.nav_hide_loop -> { + showLoop = false + rxBus.send(EventTreatmentUpdateGui()) + true + } + + R.id.nav_export -> { + exportUserEnteries() + true + } + + else -> false + } +} diff --git a/app/src/main/res/layout/treatments_bolus_carbs_fragment.xml b/app/src/main/res/layout/treatments_bolus_carbs_fragment.xml index 1012d218e3..388a6a9976 100644 --- a/app/src/main/res/layout/treatments_bolus_carbs_fragment.xml +++ b/app/src/main/res/layout/treatments_bolus_carbs_fragment.xml @@ -1,54 +1,10 @@ - - - - - - - - - - - - - + android:contentDescription="@string/select_for_removal" + android:visibility="gone" /> + + - - - - - - - - - - - - - + android:contentDescription="@string/select_for_removal" + android:visibility="gone" /> diff --git a/app/src/main/res/layout/treatments_extendedbolus_fragment.xml b/app/src/main/res/layout/treatments_extendedbolus_fragment.xml index 8434b39e86..945a861bb1 100644 --- a/app/src/main/res/layout/treatments_extendedbolus_fragment.xml +++ b/app/src/main/res/layout/treatments_extendedbolus_fragment.xml @@ -1,34 +1,10 @@ - - - - - - - - - + android:contentDescription="@string/select_for_removal" + android:visibility="gone" /> + - - - - - - - - - - - + android:contentDescription="@string/select_for_removal" + android:visibility="gone" /> diff --git a/app/src/main/res/layout/treatments_tempbasals_fragment.xml b/app/src/main/res/layout/treatments_tempbasals_fragment.xml index b7dba96815..9ef25cff3e 100644 --- a/app/src/main/res/layout/treatments_tempbasals_fragment.xml +++ b/app/src/main/res/layout/treatments_tempbasals_fragment.xml @@ -1,36 +1,10 @@ - - - - - - - - - - - + android:contentDescription="@string/select_for_removal" + android:visibility="gone" /> - - - - - - - - - - - + android:contentDescription="@string/select_for_removal" + android:visibility="gone" /> diff --git a/app/src/main/res/layout/treatments_user_entry_fragment.xml b/app/src/main/res/layout/treatments_user_entry_fragment.xml index 2244b94f6d..8479bbfde9 100644 --- a/app/src/main/res/layout/treatments_user_entry_fragment.xml +++ b/app/src/main/res/layout/treatments_user_entry_fragment.xml @@ -6,40 +6,6 @@ android:orientation="vertical" tools:context="info.nightscout.androidaps.activities.fragments.TreatmentsUserEntryFragment"> - - - - - - - - - - - + + + + diff --git a/app/src/main/res/menu/menu_treatments_carbs_bolus.xml b/app/src/main/res/menu/menu_treatments_carbs_bolus.xml new file mode 100644 index 0000000000..c45d9562d3 --- /dev/null +++ b/app/src/main/res/menu/menu_treatments_carbs_bolus.xml @@ -0,0 +1,25 @@ + + + + + + + + + diff --git a/app/src/main/res/menu/menu_treatments_careportal.xml b/app/src/main/res/menu/menu_treatments_careportal.xml new file mode 100644 index 0000000000..7a90a0f10a --- /dev/null +++ b/app/src/main/res/menu/menu_treatments_careportal.xml @@ -0,0 +1,25 @@ + + + + + + + + + diff --git a/app/src/main/res/menu/menu_treatments_extended_bolus.xml b/app/src/main/res/menu/menu_treatments_extended_bolus.xml new file mode 100644 index 0000000000..2822a53819 --- /dev/null +++ b/app/src/main/res/menu/menu_treatments_extended_bolus.xml @@ -0,0 +1,17 @@ + + + + + + + diff --git a/app/src/main/res/menu/menu_treatments_profile_switch.xml b/app/src/main/res/menu/menu_treatments_profile_switch.xml new file mode 100644 index 0000000000..2822a53819 --- /dev/null +++ b/app/src/main/res/menu/menu_treatments_profile_switch.xml @@ -0,0 +1,17 @@ + + + + + + + diff --git a/app/src/main/res/menu/menu_treatments_temp_basal.xml b/app/src/main/res/menu/menu_treatments_temp_basal.xml new file mode 100644 index 0000000000..2822a53819 --- /dev/null +++ b/app/src/main/res/menu/menu_treatments_temp_basal.xml @@ -0,0 +1,17 @@ + + + + + + + diff --git a/app/src/main/res/menu/menu_treatments_temp_target.xml b/app/src/main/res/menu/menu_treatments_temp_target.xml new file mode 100644 index 0000000000..751c678f12 --- /dev/null +++ b/app/src/main/res/menu/menu_treatments_temp_target.xml @@ -0,0 +1,21 @@ + + + + + + + + diff --git a/app/src/main/res/menu/menu_treatments_user_entry.xml b/app/src/main/res/menu/menu_treatments_user_entry.xml new file mode 100644 index 0000000000..cab552390e --- /dev/null +++ b/app/src/main/res/menu/menu_treatments_user_entry.xml @@ -0,0 +1,17 @@ + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5ca1c26ea7..756c259561 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -827,6 +827,9 @@ When sensitivity is detected, raise the target glucose keep_screen_on Clean AndroidAPS started + Show invalidated + Hide invalidated + Remove items Stored settings found Attention: If you activate and connect to a hardware pump, AndroidAPS will copy the basal settings from the profile to the pump, overwriting the existing basal rate stored on the pump. Make sure you have the correct basal setting in AndroidAPS. If you are not sure or don\'t want to overwrite the basal settings on your pump, press cancel and repeat switching to the pump at a later time. Treatment data incomplete @@ -1208,4 +1211,14 @@ Unknown action command: Percentage Application default + Refresh from Nightscout + Remove selected items + Select for removal + Profile changes + Temp Targets + Carbs and bolus + Are you sure you want to remove %1$d items + Hide loop + Show loop + %1$d selected diff --git a/core/src/main/res/drawable/ic_close.xml b/core/src/main/res/drawable/ic_close.xml new file mode 100644 index 0000000000..49ac6f1398 --- /dev/null +++ b/core/src/main/res/drawable/ic_close.xml @@ -0,0 +1,10 @@ + + + diff --git a/core/src/main/res/values/layout.xml b/core/src/main/res/values/layout.xml index 5629fe5956..045d08e4e1 100644 --- a/core/src/main/res/values/layout.xml +++ b/core/src/main/res/values/layout.xml @@ -3,6 +3,8 @@ @color/colorPrimary @color/colorPrimaryDark @color/colorAccent + true + @drawable/ic_close \ No newline at end of file diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index 76428aa88f..4d6b4b1157 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -469,6 +469,7 @@ STAT RESET DELETE LOGS DELETE FUTURE TREATMENTS + Delete future treatments EXPORT SETTINGS IMPORT SETTINGS RESET DATABASES