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..dd359ef07d 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,10 @@ 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.view.* +import android.widget.CompoundButton +import android.view.ActionMode +import androidx.appcompat.widget.Toolbar import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import dagger.android.support.DaggerFragment @@ -79,9 +80,14 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() { private var _binding: TreatmentsBolusCarbsFragmentBinding? = null - // This property is only valid between onCreateView and - // onDestroyView. + // This property is only valid between onCreateView and onDestroyView. private val binding get() = _binding!! + private var selectedItems: MutableList = mutableListOf() + private var showInvalidated = false + private var removeActionMode: ActionMode? = null + + // val TAG = "TreatmentMenu" + 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,8 +128,8 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() { fun swapAdapter() { val now = System.currentTimeMillis() - if (binding.showInvalidated.isChecked) - disposable += carbsMealLinksWithInvalid(now) + disposable += if (showInvalidated) + carbsMealLinksWithInvalid(now) .zipWith(bolusMealLinksWithInvalid(now)) { first, second -> first + second } .zipWith(calcResultMealLinksWithInvalid(now)) { first, second -> first + second } .map { ml -> @@ -217,10 +141,9 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() { .observeOn(aapsSchedulers.main) .subscribe { list -> binding.recyclerview.swapAdapter(RecyclerViewAdapter(list), true) - binding.deleteFutureTreatments.visibility = list.isNotEmpty().toVisibility() } else - disposable += carbsMealLinks(now) + carbsMealLinks(now) .zipWith(bolusMealLinks(now)) { first, second -> first + second } .zipWith(calcResultMealLinks(now)) { first, second -> first + second } .map { ml -> @@ -232,7 +155,6 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() { .observeOn(aapsSchedulers.main) .subscribe { list -> binding.recyclerview.swapAdapter(RecyclerViewAdapter(list), true) - binding.deleteFutureTreatments.visibility = list.isNotEmpty().toVisibility() } } @@ -267,13 +189,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 +210,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 +244,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()) @@ -331,18 +254,26 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() { 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 + val onChange = CompoundButton.OnCheckedChangeListener { _, value -> + if (value) { + selectedItems.add(ml) + } else { + selectedItems.remove(ml) + } + removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size) + } + holder.binding.cbBolusRemove.visibility = ((ml.bolus?.isValid == true) && (removeActionMode != null)).toVisibility() + holder.binding.cbBolusRemove.setOnCheckedChangeListener(onChange) + + holder.binding.cbCarbsRemove.visibility = (ml.carbs?.isValid == true && (removeActionMode != null)).toVisibility() + holder.binding.cbCarbsRemove.setOnCheckedChangeListener(onChange) + 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 +290,203 @@ 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 { + 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 + } + + 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 = mutableListOf() + 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() + mode.finish() + 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.first() + 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.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 { 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 +497,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 + selectedItems = mutableListOf() + }, Runnable { + selectedItems = mutableListOf() + }) } - } } -} \ 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..59efc40861 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,9 @@ 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.view.* +import android.view.ActionMode +import androidx.appcompat.widget.Toolbar import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import dagger.android.support.DaggerFragment @@ -60,11 +59,14 @@ class TreatmentsCareportalFragment : DaggerFragment() { private val disposable = CompositeDisposable() private val millsToThePast = T.days(30).msecs() + private var selectedItems: MutableList = mutableListOf() + private var showInvalidated = false + private var toolbar: Toolbar? = null + private var removeActionMode: ActionMode? = null private var _binding: TreatmentsCareportalFragmentBinding? = null - // This property is only valid between onCreateView and - // onDestroyView. + // This property is only valid between onCreateView and onDestroyView. private val binding get() = _binding!! override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = @@ -72,46 +74,44 @@ class TreatmentsCareportalFragment : DaggerFragment() { 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 +148,7 @@ class TreatmentsCareportalFragment : DaggerFragment() { @Synchronized override fun onDestroyView() { super.onDestroyView() + removeActionMode?.let { it.finish() } binding.recyclerview.adapter = null // avoid leaks _binding = null } @@ -170,42 +171,135 @@ 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() + holder.binding.cbRemove.setOnCheckedChangeListener { _, value -> + if (value) { + selectedItems.add(therapyEvent) + } else { + selectedItems.remove(therapyEvent) + } + removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size) + } 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 { + 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 + } + + 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 = mutableListOf() + 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() + mode.finish() + 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.first() + 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) } + ) + } + + }) + } + } + +} 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..af1499bf76 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,10 @@ 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.view.* +import android.view.ActionMode +import androidx.appcompat.widget.Toolbar import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import dagger.android.support.DaggerFragment @@ -27,16 +25,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 @@ -61,22 +60,28 @@ class TreatmentsExtendedBolusesFragment : DaggerFragment() { 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: MutableList = mutableListOf() + private var showInvalidated = false + private var removeActionMode: ActionMode? = null + private var toolbar: Toolbar? = null + // val TAG = "TreatmentMenu" + + 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,119 @@ 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() + holder.binding.cbRemove.setOnCheckedChangeListener { _, value -> + if (value) { + selectedItems.add(extendedBolus) + } else { + selectedItems.remove(extendedBolus) + } + removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size) + } 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 = mutableListOf() + 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() + mode.finish() + true + } + + else -> false + } + } + + override fun onDestroyActionMode(mode: ActionMode?) { + removeActionMode = null + binding.recyclerview.adapter?.notifyDataSetChanged() + } + } + + private fun getConfirmationText(): String { + if (selectedItems.size == 1) { + return rh.gs(R.string.extended_bolus) + "\n" + + "${rh.gs(R.string.date)}: ${dateUtil.dateAndTimeString(selectedItems.first().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) }) + } + }) + } + } +} 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..3d2af3afd0 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,9 @@ 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.view.* +import android.view.ActionMode +import androidx.appcompat.widget.Toolbar import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import dagger.android.support.DaggerFragment @@ -65,9 +65,12 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() { private val disposable = CompositeDisposable() private val millsToThePast = T.days(30).msecs() + private var selectedItems: MutableList = mutableListOf() + private var showInvalidated = false + private var removeActionMode: ActionMode? = null + private var toolbar: Toolbar? = 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 = @@ -75,36 +78,34 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() { 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 +127,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 +169,7 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() { @Synchronized override fun onDestroyView() { super.onDestroyView() + removeActionMode?.let { it.finish() } binding.recyclerview.adapter = null // avoid leaks _binding = null } @@ -189,13 +191,20 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() { 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() + holder.binding.cbRemove.setOnCheckedChangeListener { _, value -> + if (value) { + selectedItems.add(profileSwitch) + } else { + selectedItems.remove(profileSwitch) + } + removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size) + } 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 +212,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 +267,103 @@ 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 { + 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 + } + + 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 = mutableListOf() + 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() + mode.finish() + 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.first() + 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) } + ) + } + }) + } + } +} 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..479f0008de 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,9 @@ 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.view.* +import androidx.appcompat.widget.Toolbar import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import dagger.android.support.DaggerFragment @@ -30,6 +27,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 +39,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 @@ -68,11 +68,14 @@ class TreatmentsTempTargetFragment : DaggerFragment() { private val disposable = CompositeDisposable() private val millsToThePast = T.days(30).msecs() + private var selectedItems: MutableList = mutableListOf() + private var showInvalidated = false + private var toolbar: Toolbar? = null + private var removeActionMode: ActionMode? = null private var _binding: TreatmentsTemptargetFragmentBinding? = null - // This property is only valid between onCreateView and - // onDestroyView. + // This property is only valid between onCreateView and onDestroyView. private val binding get() = _binding!! override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = @@ -80,42 +83,48 @@ class TreatmentsTempTargetFragment : DaggerFragment() { 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,16 @@ class TreatmentsTempTargetFragment : DaggerFragment() { val tempTarget = tempTargetList[position] holder.binding.ns.visibility = (tempTarget.interfaceIDs.nightscoutId != null).toVisibility() holder.binding.invalid.visibility = tempTarget.isValid.not().toVisibility() - holder.binding.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() + holder.binding.cbRemove.setOnCheckedChangeListener { _, value -> + if (value) { + selectedItems.add(tempTarget) + } else { + selectedItems.remove(tempTarget) + } + removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size) + } + 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 +195,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() { + // TODO check if item should not be delete val profile = profileFunction.getProfile(dateUtil.now()) == null + 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) }) + } + }) + } + } + + 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 { + 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 + } + + 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 = mutableListOf() + 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() + mode.finish() + 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.first() + 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..4e166fc46f 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,8 @@ 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.view.* +import androidx.appcompat.widget.Toolbar import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import dagger.android.support.DaggerFragment @@ -36,6 +33,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 @@ -66,9 +64,12 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() { private var _binding: TreatmentsTempbasalsFragmentBinding? = null private val millsToThePast = T.days(30).msecs() + private var selectedItems: MutableList = mutableListOf() + private var showInvalidated = false + private var toolbar: Toolbar? = null + private var removeActionMode: ActionMode? = 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 = @@ -76,6 +77,8 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() { 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 +101,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 +116,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 +153,7 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() { @Synchronized override fun onDestroyView() { super.onDestroyView() + removeActionMode?.let { it.finish() } binding.recyclerview.adapter = null // avoid leaks _binding = null } @@ -164,7 +168,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 +191,144 @@ 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() + holder.binding.cbRemove.setOnCheckedChangeListener { _, value -> + if (value) { + selectedItems.add(tempBasal) + } else { + selectedItems.remove(tempBasal) + } + removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size) + } 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 { + 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 = mutableListOf() + 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() + mode.finish() + 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.first() + 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() { + // TODO check if item should not be delete val profile = profileFunction.getProfile(dateUtil.now()) == null + 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) }) + } + } + }) + } + } +} 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..13a1b3de09 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,7 +50,7 @@ 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 @@ -63,35 +62,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 +96,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 +129,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 +149,41 @@ 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 { + return 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 fa3550bae6..018baa8b9c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -826,6 +826,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 @@ -1207,4 +1210,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..70db409b33 --- /dev/null +++ b/core/src/main/res/drawable/ic_close.xml @@ -0,0 +1,5 @@ + + + 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