From c8f7dae2c83498a9cd4fa9b97dab7529d30f07ee Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Mon, 28 Dec 2020 16:42:00 +0100 Subject: [PATCH] Bolus advisor --- .../androidaps/dialogs/WizardDialog.kt | 220 ++++++++------- .../general/automation/AutomationFragment.kt | 42 ++- .../general/automation/AutomationPlugin.kt | 87 ++++-- .../general/automation/actions/ActionAlarm.kt | 3 + .../automation/dialogs/EditEventDialog.kt | 40 ++- .../automation/triggers/TriggerDelta.kt | 6 + .../general/wear/ActionStringHandler.kt | 4 +- .../androidaps/utils/wizard/BolusWizard.kt | 261 +++++++++++++----- .../utils/wizard/QuickWizardEntry.kt | 2 +- .../res/layout/automation_dialog_event.xml | 8 +- .../main/res/layout/automation_event_item.xml | 68 +++-- app/src/main/res/layout/dialog_wizard.xml | 112 +++++--- app/src/main/res/values/colors.xml | 1 + app/src/main/res/values/strings.xml | 11 +- app/src/main/res/xml/pref_overview.xml | 12 + .../androidaps/utils/alertDialogs/OKDialog.kt | 33 +++ core/src/main/res/values/strings.xml | 2 + 17 files changed, 602 insertions(+), 310 deletions(-) diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/WizardDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/WizardDialog.kt index 45eebec6f4..63102ed9ae 100644 --- a/app/src/main/java/info/nightscout/androidaps/dialogs/WizardDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/WizardDialog.kt @@ -18,6 +18,7 @@ import info.nightscout.androidaps.Constants import info.nightscout.androidaps.MainApp import info.nightscout.androidaps.R import info.nightscout.androidaps.data.Profile +import info.nightscout.androidaps.databinding.DialogWizardBinding import info.nightscout.androidaps.db.BgReading import info.nightscout.androidaps.interfaces.ActivePluginProvider import info.nightscout.androidaps.interfaces.Constraint @@ -39,7 +40,6 @@ import info.nightscout.androidaps.utils.sharedPreferences.SP import info.nightscout.androidaps.utils.wizard.BolusWizard import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable -import kotlinx.android.synthetic.main.dialog_wizard.* import java.text.DecimalFormat import java.util.* import javax.inject.Inject @@ -72,8 +72,23 @@ class WizardDialog : DaggerDialogFragment() { } } + private val timeTextWatcher = object : TextWatcher { + override fun afterTextChanged(s: Editable) {} + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { + calculateInsulin() + binding.alarm.isChecked = binding.carbTimeInput.value > 0 + } + } + private var disposable: CompositeDisposable = CompositeDisposable() + private var _binding: DialogWizardBinding? = null + + // This property is only valid between onCreateView and + // onDestroyView. + private val binding get() = _binding!! + override fun onStart() { super.onStart() dialog?.window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) @@ -81,49 +96,50 @@ class WizardDialog : DaggerDialogFragment() { override fun onSaveInstanceState(savedInstanceState: Bundle) { super.onSaveInstanceState(savedInstanceState) - savedInstanceState.putDouble("treatments_wizard_bg_input", treatments_wizard_bg_input.value) - savedInstanceState.putDouble("treatments_wizard_carbs_input", treatments_wizard_carbs_input.value) - savedInstanceState.putDouble("treatments_wizard_correction_input", treatments_wizard_correction_input.value) - savedInstanceState.putDouble("treatments_wizard_carb_time_input", treatments_wizard_carb_time_input.value) + savedInstanceState.putDouble("bg_input", binding.bgInput.value) + savedInstanceState.putDouble("carbs_input", binding.carbsInput.value) + savedInstanceState.putDouble("correction_input", binding.correctionInput.value) + savedInstanceState.putDouble("carb_time_input", binding.carbTimeInput.value) } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?): View? { + savedInstanceState: Bundle?): View { dialog?.window?.requestFeature(Window.FEATURE_NO_TITLE) dialog?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN) isCancelable = true dialog?.setCanceledOnTouchOutside(false) - return inflater.inflate(R.layout.dialog_wizard, container, false) + _binding = DialogWizardBinding.inflate(inflater, container, false) + return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { loadCheckedStates() processCobCheckBox() - treatments_wizard_sbcheckbox.visibility = sp.getBoolean(R.string.key_usesuperbolus, false).toVisibility() - treatments_wizard_notes_layout.visibility = sp.getBoolean(R.string.key_show_notes_entry_dialogs, false).toVisibility() + binding.sbcheckbox.visibility = sp.getBoolean(R.string.key_usesuperbolus, false).toVisibility() + binding.notesLayout.visibility = sp.getBoolean(R.string.key_show_notes_entry_dialogs, false).toVisibility() val maxCarbs = constraintChecker.getMaxCarbsAllowed().value() val maxCorrection = constraintChecker.getMaxBolusAllowed().value() if (profileFunction.getUnits() == Constants.MGDL) - treatments_wizard_bg_input.setParams(savedInstanceState?.getDouble("treatments_wizard_bg_input") - ?: 0.0, 0.0, 500.0, 1.0, DecimalFormat("0"), false, ok, textWatcher) + binding.bgInput.setParams(savedInstanceState?.getDouble("bg_input") + ?: 0.0, 0.0, 500.0, 1.0, DecimalFormat("0"), false, binding.ok, timeTextWatcher) else - treatments_wizard_bg_input.setParams(savedInstanceState?.getDouble("treatments_wizard_bg_input") - ?: 0.0, 0.0, 30.0, 0.1, DecimalFormat("0.0"), false, ok, textWatcher) - treatments_wizard_carbs_input.setParams(savedInstanceState?.getDouble("treatments_wizard_carbs_input") - ?: 0.0, 0.0, maxCarbs.toDouble(), 1.0, DecimalFormat("0"), false, ok, textWatcher) + binding.bgInput.setParams(savedInstanceState?.getDouble("bg_input") + ?: 0.0, 0.0, 30.0, 0.1, DecimalFormat("0.0"), false, binding.ok, textWatcher) + binding.carbsInput.setParams(savedInstanceState?.getDouble("carbs_input") + ?: 0.0, 0.0, maxCarbs.toDouble(), 1.0, DecimalFormat("0"), false, binding.ok, textWatcher) val bolusStep = activePlugin.activePump.pumpDescription.bolusStep - treatments_wizard_correction_input.setParams(savedInstanceState?.getDouble("treatments_wizard_correction_input") - ?: 0.0, -maxCorrection, maxCorrection, bolusStep, DecimalFormatter.pumpSupportedBolusFormat(activePlugin.activePump), false, ok, textWatcher) - treatments_wizard_carb_time_input.setParams(savedInstanceState?.getDouble("treatments_wizard_carb_time_input") - ?: 0.0, -60.0, 60.0, 5.0, DecimalFormat("0"), false, ok, textWatcher) + binding.correctionInput.setParams(savedInstanceState?.getDouble("correction_input") + ?: 0.0, -maxCorrection, maxCorrection, bolusStep, DecimalFormatter.pumpSupportedBolusFormat(activePlugin.activePump), false, binding.ok, textWatcher) + binding.carbTimeInput.setParams(savedInstanceState?.getDouble("carb_time_input") + ?: 0.0, -60.0, 60.0, 5.0, DecimalFormat("0"), false, binding.ok, timeTextWatcher) initDialog() - treatments_wizard_percent_used.text = resourceHelper.gs(R.string.format_percent, sp.getInt(R.string.key_boluswizard_percentage, 100)) + binding.percentUsed.text = resourceHelper.gs(R.string.format_percent, sp.getInt(R.string.key_boluswizard_percentage, 100)) // ok button - ok.setOnClickListener { + binding.ok.setOnClickListener { if (okClicked) { aapsLogger.debug(LTag.UI, "guarding: ok already clicked") } else { @@ -136,37 +152,37 @@ class WizardDialog : DaggerDialogFragment() { dismiss() } // cancel button - cancel.setOnClickListener { dismiss() } + binding.cancel.setOnClickListener { dismiss() } // checkboxes - treatments_wizard_bgcheckbox.setOnCheckedChangeListener(::onCheckedChanged) - treatments_wizard_ttcheckbox.setOnCheckedChangeListener(::onCheckedChanged) - treatments_wizard_cobcheckbox.setOnCheckedChangeListener(::onCheckedChanged) - treatments_wizard_basaliobcheckbox.setOnCheckedChangeListener(::onCheckedChanged) - treatments_wizard_bolusiobcheckbox.setOnCheckedChangeListener(::onCheckedChanged) - treatments_wizard_bgtrendcheckbox.setOnCheckedChangeListener(::onCheckedChanged) - treatments_wizard_sbcheckbox.setOnCheckedChangeListener(::onCheckedChanged) + binding.bgcheckbox.setOnCheckedChangeListener(::onCheckedChanged) + binding.ttcheckbox.setOnCheckedChangeListener(::onCheckedChanged) + binding.cobcheckbox.setOnCheckedChangeListener(::onCheckedChanged) + binding.basaliobcheckbox.setOnCheckedChangeListener(::onCheckedChanged) + binding.bolusiobcheckbox.setOnCheckedChangeListener(::onCheckedChanged) + binding.bgtrendcheckbox.setOnCheckedChangeListener(::onCheckedChanged) + binding.sbcheckbox.setOnCheckedChangeListener(::onCheckedChanged) val showCalc = sp.getBoolean(R.string.key_wizard_calculation_visible, false) - treatments_wizard_delimiter.visibility = showCalc.toVisibility() - treatments_wizard_resulttable.visibility = showCalc.toVisibility() - treatments_wizard_calculationcheckbox.isChecked = showCalc - treatments_wizard_calculationcheckbox.setOnCheckedChangeListener { _, isChecked -> + binding.delimiter.visibility = showCalc.toVisibility() + binding.resulttable.visibility = showCalc.toVisibility() + binding.calculationcheckbox.isChecked = showCalc + binding.calculationcheckbox.setOnCheckedChangeListener { _, isChecked -> run { sp.putBoolean(resourceHelper.gs(R.string.key_wizard_calculation_visible), isChecked) - treatments_wizard_delimiter.visibility = isChecked.toVisibility() - treatments_wizard_resulttable.visibility = isChecked.toVisibility() + binding.delimiter.visibility = isChecked.toVisibility() + binding.resulttable.visibility = isChecked.toVisibility() } } // profile spinner - treatments_wizard_profile.onItemSelectedListener = object : OnItemSelectedListener { + binding.profile.onItemSelectedListener = object : OnItemSelectedListener { override fun onNothingSelected(parent: AdapterView<*>?) { ToastUtils.showToastInUiThread(mainApp, resourceHelper.gs(R.string.noprofileselected)) - ok.visibility = View.GONE + binding.ok.visibility = View.GONE } override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { calculateInsulin() - ok.visibility = View.VISIBLE + binding.ok.visibility = View.VISIBLE } } // bus @@ -183,36 +199,37 @@ class WizardDialog : DaggerDialogFragment() { override fun onDestroyView() { super.onDestroyView() disposable.clear() + _binding = null } private fun onCheckedChanged(buttonView: CompoundButton, @Suppress("UNUSED_PARAMETER") state: Boolean) { saveCheckedStates() - treatments_wizard_ttcheckbox.isEnabled = treatments_wizard_bgcheckbox.isChecked && treatmentsPlugin.tempTargetFromHistory != null - if (buttonView.id == treatments_wizard_cobcheckbox.id) + binding.ttcheckbox.isEnabled = binding.bgcheckbox.isChecked && treatmentsPlugin.tempTargetFromHistory != null + if (buttonView.id == binding.cobcheckbox.id) processCobCheckBox() calculateInsulin() } private fun processCobCheckBox() { - if (treatments_wizard_cobcheckbox.isChecked) { - treatments_wizard_bolusiobcheckbox.isEnabled = false - treatments_wizard_basaliobcheckbox.isEnabled = false - treatments_wizard_bolusiobcheckbox.isChecked = true - treatments_wizard_basaliobcheckbox.isChecked = true + if (binding.cobcheckbox.isChecked) { + binding.bolusiobcheckbox.isEnabled = false + binding.basaliobcheckbox.isEnabled = false + binding.bolusiobcheckbox.isChecked = true + binding.basaliobcheckbox.isChecked = true } else { - treatments_wizard_bolusiobcheckbox.isEnabled = true - treatments_wizard_basaliobcheckbox.isEnabled = true + binding.bolusiobcheckbox.isEnabled = true + binding.basaliobcheckbox.isEnabled = true } } private fun saveCheckedStates() { - sp.putBoolean(resourceHelper.gs(R.string.key_wizard_include_cob), treatments_wizard_cobcheckbox.isChecked) - sp.putBoolean(resourceHelper.gs(R.string.key_wizard_include_trend_bg), treatments_wizard_bgtrendcheckbox.isChecked) + sp.putBoolean(R.string.key_wizard_include_cob, binding.cobcheckbox.isChecked) + sp.putBoolean(R.string.key_wizard_include_trend_bg, binding.bgtrendcheckbox.isChecked) } private fun loadCheckedStates() { - treatments_wizard_bgtrendcheckbox.isChecked = sp.getBoolean(R.string.key_wizard_include_trend_bg, false) - treatments_wizard_cobcheckbox.isChecked = sp.getBoolean(R.string.key_wizard_include_cob, false) + binding.bgtrendcheckbox.isChecked = sp.getBoolean(R.string.key_wizard_include_trend_bg, false) + binding.cobcheckbox.isChecked = sp.getBoolean(R.string.key_wizard_include_cob, false) } private fun initDialog() { @@ -230,25 +247,25 @@ class WizardDialog : DaggerDialogFragment() { profileList.add(0, resourceHelper.gs(R.string.active)) context?.let { context -> val adapter = ArrayAdapter(context, R.layout.spinner_centered, profileList) - treatments_wizard_profile.adapter = adapter + binding.profile.adapter = adapter } ?: return val units = profileFunction.getUnits() - treatments_wizard_bgunits.text = units + binding.bgunits.text = units if (units == Constants.MGDL) - treatments_wizard_bg_input.setStep(1.0) + binding.bgInput.setStep(1.0) else - treatments_wizard_bg_input.setStep(0.1) + binding.bgInput.setStep(0.1) // Set BG if not old val lastBg = iobCobCalculatorPlugin.actualBg() if (lastBg != null) { - treatments_wizard_bg_input.value = lastBg.valueToUnits(units) + binding.bgInput.value = lastBg.valueToUnits(units) } else { - treatments_wizard_bg_input.value = 0.0 + binding.bgInput.value = 0.0 } - treatments_wizard_ttcheckbox.isEnabled = treatmentsPlugin.tempTargetFromHistory != null + binding.ttcheckbox.isEnabled = treatmentsPlugin.tempTargetFromHistory != null // IOB calculation treatmentsPlugin.updateTotalIOBTreatments() @@ -256,19 +273,19 @@ class WizardDialog : DaggerDialogFragment() { treatmentsPlugin.updateTotalIOBTempBasals() val basalIob = treatmentsPlugin.lastCalculationTempBasals.round() - treatments_wizard_bolusiobinsulin.text = resourceHelper.gs(R.string.formatinsulinunits, -bolusIob.iob) - treatments_wizard_basaliobinsulin.text = resourceHelper.gs(R.string.formatinsulinunits, -basalIob.basaliob) + binding.bolusiobinsulin.text = resourceHelper.gs(R.string.formatinsulinunits, -bolusIob.iob) + binding.basaliobinsulin.text = resourceHelper.gs(R.string.formatinsulinunits, -basalIob.basaliob) calculateInsulin() - treatments_wizard_percent_used.visibility = (sp.getInt(R.string.key_boluswizard_percentage, 100) != 100).toVisibility() + binding.percentUsed.visibility = (sp.getInt(R.string.key_boluswizard_percentage, 100) != 100).toVisibility() } private fun calculateInsulin() { val profileStore = activePlugin.activeProfileInterface.profile - if (treatments_wizard_profile?.selectedItem == null || profileStore == null) + if (binding.profile.selectedItem == null || profileStore == null) return // not initialized yet - var profileName = treatments_wizard_profile.selectedItem.toString() + var profileName = binding.profile.selectedItem.toString() val specificProfile: Profile? if (profileName == resourceHelper.gs(R.string.active)) { specificProfile = profileFunction.getProfile() @@ -279,82 +296,83 @@ class WizardDialog : DaggerDialogFragment() { if (specificProfile == null) return // Entered values - var bg = SafeParse.stringToDouble(treatments_wizard_bg_input.text) - val carbs = SafeParse.stringToInt(treatments_wizard_carbs_input.text) - val correction = SafeParse.stringToDouble(treatments_wizard_correction_input.text) + var bg = SafeParse.stringToDouble(binding.bgInput.text) + val carbs = SafeParse.stringToInt(binding.carbsInput.text) + val correction = SafeParse.stringToDouble(binding.correctionInput.text) val carbsAfterConstraint = constraintChecker.applyCarbsConstraints(Constraint(carbs)).value() if (abs(carbs - carbsAfterConstraint) > 0.01) { - treatments_wizard_carbs_input.value = 0.0 + binding.carbsInput.value = 0.0 ToastUtils.showToastInUiThread(mainApp, resourceHelper.gs(R.string.carbsconstraintapplied)) return } - bg = if (treatments_wizard_bgcheckbox.isChecked) bg else 0.0 - val tempTarget = if (treatments_wizard_ttcheckbox.isChecked) treatmentsPlugin.tempTargetFromHistory else null + bg = if (binding.bgcheckbox.isChecked) bg else 0.0 + val tempTarget = if (binding.ttcheckbox.isChecked) treatmentsPlugin.tempTargetFromHistory else null // COB var cob = 0.0 - if (treatments_wizard_cobcheckbox.isChecked) { + if (binding.cobcheckbox.isChecked) { val cobInfo = iobCobCalculatorPlugin.getCobInfo(false, "Wizard COB") cobInfo.displayCob?.let { cob = it } } - val carbTime = SafeParse.stringToInt(treatments_wizard_carb_time_input.text) + val carbTime = SafeParse.stringToInt(binding.carbTimeInput.text) wizard = BolusWizard(mainApp).doCalc(specificProfile, profileName, tempTarget, carbsAfterConstraint, cob, bg, correction, sp.getInt(R.string.key_boluswizard_percentage, 100).toDouble(), - treatments_wizard_bgcheckbox.isChecked, - treatments_wizard_cobcheckbox.isChecked, - treatments_wizard_bolusiobcheckbox.isChecked, - treatments_wizard_basaliobcheckbox.isChecked, - treatments_wizard_sbcheckbox.isChecked, - treatments_wizard_ttcheckbox.isChecked, - treatments_wizard_bgtrendcheckbox.isChecked, - treatment_wizard_notes.text.toString(), carbTime) + binding.bgcheckbox.isChecked, + binding.cobcheckbox.isChecked, + binding.bolusiobcheckbox.isChecked, + binding.basaliobcheckbox.isChecked, + binding.sbcheckbox.isChecked, + binding.ttcheckbox.isChecked, + binding.bgtrendcheckbox.isChecked, + binding.alarm.isChecked, + binding.notes.text.toString(), carbTime) wizard?.let { wizard -> - treatments_wizard_bg.text = String.format(resourceHelper.gs(R.string.format_bg_isf), BgReading().value(Profile.toMgdl(bg, profileFunction.getUnits())).valueToUnitsToString(profileFunction.getUnits()), wizard.sens) - treatments_wizard_bginsulin.text = resourceHelper.gs(R.string.formatinsulinunits, wizard.insulinFromBG) + binding.bg.text = String.format(resourceHelper.gs(R.string.format_bg_isf), BgReading().value(Profile.toMgdl(bg, profileFunction.getUnits())).valueToUnitsToString(profileFunction.getUnits()), wizard.sens) + binding.bginsulin.text = resourceHelper.gs(R.string.formatinsulinunits, wizard.insulinFromBG) - treatments_wizard_carbs.text = String.format(resourceHelper.gs(R.string.format_carbs_ic), carbs.toDouble(), wizard.ic) - treatments_wizard_carbsinsulin.text = resourceHelper.gs(R.string.formatinsulinunits, wizard.insulinFromCarbs) + binding.carbs.text = String.format(resourceHelper.gs(R.string.format_carbs_ic), carbs.toDouble(), wizard.ic) + binding.carbsinsulin.text = resourceHelper.gs(R.string.formatinsulinunits, wizard.insulinFromCarbs) - treatments_wizard_bolusiobinsulin.text = resourceHelper.gs(R.string.formatinsulinunits, wizard.insulinFromBolusIOB) - treatments_wizard_basaliobinsulin.text = resourceHelper.gs(R.string.formatinsulinunits, wizard.insulinFromBasalsIOB) + binding.bolusiobinsulin.text = resourceHelper.gs(R.string.formatinsulinunits, wizard.insulinFromBolusIOB) + binding.basaliobinsulin.text = resourceHelper.gs(R.string.formatinsulinunits, wizard.insulinFromBasalIOB) - treatments_wizard_correctioninsulin.text = resourceHelper.gs(R.string.formatinsulinunits, wizard.insulinFromCorrection) + binding.correctioninsulin.text = resourceHelper.gs(R.string.formatinsulinunits, wizard.insulinFromCorrection) // Superbolus - treatments_wizard_sb.text = if (treatments_wizard_sbcheckbox.isChecked) resourceHelper.gs(R.string.twohours) else "" - treatments_wizard_sbinsulin.text = resourceHelper.gs(R.string.formatinsulinunits, wizard.insulinFromSuperBolus) + binding.sb.text = if (binding.sbcheckbox.isChecked) resourceHelper.gs(R.string.twohours) else "" + binding.sbinsulin.text = resourceHelper.gs(R.string.formatinsulinunits, wizard.insulinFromSuperBolus) // Trend - if (treatments_wizard_bgtrendcheckbox.isChecked && wizard.glucoseStatus != null) { - treatments_wizard_bgtrend.text = ((if (wizard.trend > 0) "+" else "") + if (binding.bgtrendcheckbox.isChecked && wizard.glucoseStatus != null) { + binding.bgtrend.text = ((if (wizard.trend > 0) "+" else "") + Profile.toUnitsString(wizard.trend * 3, wizard.trend * 3 / Constants.MMOLL_TO_MGDL, profileFunction.getUnits()) + " " + profileFunction.getUnits()) } else { - treatments_wizard_bgtrend.text = "" + binding.bgtrend.text = "" } - treatments_wizard_bgtrendinsulin.text = resourceHelper.gs(R.string.formatinsulinunits, wizard.insulinFromTrend) + binding.bgtrendinsulin.text = resourceHelper.gs(R.string.formatinsulinunits, wizard.insulinFromTrend) // COB - if (treatments_wizard_cobcheckbox.isChecked) { - treatments_wizard_cob.text = String.format(resourceHelper.gs(R.string.format_cob_ic), cob, wizard.ic) - treatments_wizard_cobinsulin.text = resourceHelper.gs(R.string.formatinsulinunits, wizard.insulinFromCOB) + if (binding.cobcheckbox.isChecked) { + binding.cob.text = String.format(resourceHelper.gs(R.string.format_cob_ic), cob, wizard.ic) + binding.cobinsulin.text = resourceHelper.gs(R.string.formatinsulinunits, wizard.insulinFromCOB) } else { - treatments_wizard_cob.text = "" - treatments_wizard_cobinsulin.text = "" + binding.cob.text = "" + binding.cobinsulin.text = "" } if (wizard.calculatedTotalInsulin > 0.0 || carbsAfterConstraint > 0.0) { val insulinText = if (wizard.calculatedTotalInsulin > 0.0) resourceHelper.gs(R.string.formatinsulinunits, wizard.calculatedTotalInsulin) else "" val carbsText = if (carbsAfterConstraint > 0.0) resourceHelper.gs(R.string.format_carbs, carbsAfterConstraint) else "" - treatments_wizard_total.text = resourceHelper.gs(R.string.result_insulin_carbs, insulinText, carbsText) - ok.visibility = View.VISIBLE + binding.total.text = resourceHelper.gs(R.string.result_insulin_carbs, insulinText, carbsText) + binding.ok.visibility = View.VISIBLE } else { - treatments_wizard_total.text = resourceHelper.gs(R.string.missing_carbs, wizard.carbsEquivalent.toInt()) - ok.visibility = View.INVISIBLE + binding.total.text = resourceHelper.gs(R.string.missing_carbs, wizard.carbsEquivalent.toInt()) + binding.ok.visibility = View.INVISIBLE } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationFragment.kt index 9f037ce5f8..5a96d71012 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationFragment.kt @@ -15,8 +15,8 @@ import androidx.annotation.DrawableRes import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import dagger.android.HasAndroidInjector import dagger.android.support.DaggerFragment -import info.nightscout.androidaps.MainApp import info.nightscout.androidaps.R import info.nightscout.androidaps.databinding.AutomationEventItemBinding import info.nightscout.androidaps.databinding.AutomationFragmentBinding @@ -46,7 +46,7 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener { @Inject lateinit var rxBus: RxBusWrapper @Inject lateinit var fabricPrivacy: FabricPrivacy @Inject lateinit var automationPlugin: AutomationPlugin - @Inject lateinit var mainApp: MainApp + @Inject lateinit var injector: HasAndroidInjector private var disposable: CompositeDisposable = CompositeDisposable() private lateinit var eventListAdapter: EventListAdapter @@ -76,7 +76,7 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener { binding.fabAddEvent.setOnClickListener { val dialog = EditEventDialog() val args = Bundle() - args.putString("event", AutomationEvent(mainApp).toJSON()) + args.putString("event", AutomationEvent(injector).toJSON()) args.putInt("position", -1) // New event dialog.arguments = args dialog.show(childFragmentManager, "EditEventDialog") @@ -159,7 +159,7 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener { @SuppressLint("ClickableViewAccessibility") override fun onBindViewHolder(holder: ViewHolder, position: Int) { - val event = automationPlugin.automationEvents[position] + val event = automationPlugin.at(position) holder.binding.eventTitle.text = event.title holder.binding.enabled.isChecked = event.isEnabled holder.binding.enabled.isEnabled = !event.readOnly @@ -190,15 +190,14 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener { rxBus.send(EventAutomationDataChanged()) } // edit event - if (!event.readOnly) - holder.binding.rootLayout.setOnClickListener { - val dialog = EditEventDialog() - val args = Bundle() - args.putString("event", event.toJSON()) - args.putInt("position", position) - dialog.arguments = args - dialog.show(childFragmentManager, "EditEventDialog") - } + holder.binding.rootLayout.setOnClickListener { + val dialog = EditEventDialog() + val args = Bundle() + args.putString("event", event.toJSON()) + args.putInt("position", position) + dialog.arguments = args + dialog.show(childFragmentManager, "EditEventDialog") + } // Start a drag whenever the handle view it touched holder.binding.iconSort.setOnTouchListener { v: View, motionEvent: MotionEvent -> if (motionEvent.action == MotionEvent.ACTION_DOWN) { @@ -209,36 +208,33 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener { } // remove event holder.binding.iconTrash.setOnClickListener { - showConfirmation(requireContext(), resourceHelper.gs(R.string.removerecord) + " " + automationPlugin.automationEvents[position].title, + showConfirmation(requireContext(), resourceHelper.gs(R.string.removerecord) + " " + automationPlugin.at(position).title, Runnable { - automationPlugin.automationEvents.removeAt(position) + automationPlugin.removeAt(position) notifyItemRemoved(position) - rxBus.send(EventAutomationDataChanged()) - rxBus.send(EventAutomationUpdateGui()) }, Runnable { rxBus.send(EventAutomationUpdateGui()) }) } holder.binding.iconTrash.visibility = (!event.readOnly).toVisibility() + holder.binding.aapsLogo.visibility = (event.systemAction).toVisibility() } - override fun getItemCount(): Int = automationPlugin.automationEvents.size + override fun getItemCount(): Int = automationPlugin.size() override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean { - Collections.swap(automationPlugin.automationEvents, fromPosition, toPosition) + automationPlugin.swap(fromPosition, toPosition) notifyItemMoved(fromPosition, toPosition) - rxBus.send(EventAutomationDataChanged()) return true } override fun onItemDismiss(position: Int) { activity?.let { activity -> - showConfirmation(activity, resourceHelper.gs(R.string.removerecord) + " " + automationPlugin.automationEvents[position].title, + showConfirmation(activity, resourceHelper.gs(R.string.removerecord) + " " + automationPlugin.at(position).title, Runnable { - automationPlugin.automationEvents.removeAt(position) + automationPlugin.removeAt(position) notifyItemRemoved(position) rxBus.send(EventAutomationDataChanged()) - rxBus.send(EventAutomationUpdateGui()) }, Runnable { rxBus.send(EventAutomationUpdateGui()) }) } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationPlugin.kt index ed6e758da1..1ec46d6131 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationPlugin.kt @@ -39,8 +39,10 @@ import io.reactivex.schedulers.Schedulers import org.json.JSONArray import org.json.JSONException import org.json.JSONObject +import java.util.* import javax.inject.Inject import javax.inject.Singleton +import kotlin.collections.ArrayList @Singleton class AutomationPlugin @Inject constructor( @@ -69,7 +71,7 @@ class AutomationPlugin @Inject constructor( private val keyAutomationEvents = "AUTOMATION_EVENTS" - val automationEvents = ArrayList() + private val automationEvents = ArrayList() var executionLog: MutableList = ArrayList() var btConnects: MutableList = ArrayList() @@ -77,6 +79,7 @@ class AutomationPlugin @Inject constructor( private lateinit var refreshLoop: Runnable companion object { + const val event = "{\"title\":\"Low\",\"enabled\":true,\"trigger\":\"{\\\"type\\\":\\\"info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnector\\\",\\\"data\\\":{\\\"connectorType\\\":\\\"AND\\\",\\\"triggerList\\\":[\\\"{\\\\\\\"type\\\\\\\":\\\\\\\"info.nightscout.androidaps.plugins.general.automation.triggers.TriggerBg\\\\\\\",\\\\\\\"data\\\\\\\":{\\\\\\\"bg\\\\\\\":4,\\\\\\\"comparator\\\\\\\":\\\\\\\"IS_LESSER\\\\\\\",\\\\\\\"units\\\\\\\":\\\\\\\"mmol\\\\\\\"}}\\\",\\\"{\\\\\\\"type\\\\\\\":\\\\\\\"info.nightscout.androidaps.plugins.general.automation.triggers.TriggerDelta\\\\\\\",\\\\\\\"data\\\\\\\":{\\\\\\\"value\\\\\\\":-0.1,\\\\\\\"units\\\\\\\":\\\\\\\"mmol\\\\\\\",\\\\\\\"deltaType\\\\\\\":\\\\\\\"DELTA\\\\\\\",\\\\\\\"comparator\\\\\\\":\\\\\\\"IS_LESSER\\\\\\\"}}\\\"]}}\",\"actions\":[\"{\\\"type\\\":\\\"info.nightscout.androidaps.plugins.general.automation.actions.ActionStartTempTarget\\\",\\\"data\\\":{\\\"value\\\":8,\\\"units\\\":\\\"mmol\\\",\\\"durationInMinutes\\\":60}}\"]}" } @@ -184,42 +187,46 @@ class AutomationPlugin @Inject constructor( @Synchronized private fun processActions() { + var userEventsEnabled = true if (loopPlugin.isSuspended || !loopPlugin.isEnabled()) { aapsLogger.debug(LTag.AUTOMATION, "Loop deactivated") executionLog.add(resourceHelper.gs(R.string.smscommunicator_loopisdisabled)) - return + userEventsEnabled = false } val enabled = constraintChecker.isAutomationEnabled() if (!enabled.value()) { executionLog.add(enabled.getMostLimitedReasons(aapsLogger)) - return + userEventsEnabled = false } aapsLogger.debug(LTag.AUTOMATION, "processActions") for (event in automationEvents) { if (event.isEnabled && event.shouldRun() && event.trigger.shouldRun() && event.getPreconditions().shouldRun()) { - val actions = event.actions - for (action in actions) { - action.doAction(object : Callback() { - override fun run() { - val sb = StringBuilder() - sb.append(dateUtil.timeString(DateUtil.now())) - sb.append(" ") - sb.append(if (result.success) "☺" else "▼") - sb.append(" ") - sb.append(event.title) - sb.append(": ") - sb.append(action.shortDescription()) - sb.append(": ") - sb.append(result.comment) - executionLog.add(sb.toString()) - aapsLogger.debug(LTag.AUTOMATION, "Executed: $sb") - rxBus.send(EventAutomationUpdateGui()) - } - }) + if (event.systemAction || userEventsEnabled) { + val actions = event.actions + for (action in actions) { + action.doAction(object : Callback() { + override fun run() { + val sb = StringBuilder() + sb.append(dateUtil.timeString(DateUtil.now())) + sb.append(" ") + sb.append(if (result.success) "☺" else "▼") + sb.append(" ") + sb.append(event.title) + sb.append(": ") + sb.append(action.shortDescription()) + sb.append(": ") + sb.append(result.comment) + executionLog.add(sb.toString()) + aapsLogger.debug(LTag.AUTOMATION, "Executed: $sb") + rxBus.send(EventAutomationUpdateGui()) + } + }) + } + SystemClock.sleep(1100) + event.lastRun = DateUtil.now() + if (event.autoRemove) automationEvents.remove(event) } - SystemClock.sleep(1100) - event.lastRun = DateUtil.now() } } // we cannot detect connected BT devices @@ -231,6 +238,38 @@ class AutomationPlugin @Inject constructor( storeToSP() // save last run time } + fun add(event: AutomationEvent) { + automationEvents.add(event) + rxBus.send(EventAutomationDataChanged()) + } + + fun addIfNotExists(event: AutomationEvent) { + for (e in automationEvents) { + if (event.title == e.title) return + } + automationEvents.add(event) + rxBus.send(EventAutomationDataChanged()) + } + + fun set(event: AutomationEvent, index: Int) { + automationEvents[index] = event + rxBus.send(EventAutomationDataChanged()) + } + + fun removeAt(index: Int) { + automationEvents.removeAt(index) + rxBus.send(EventAutomationDataChanged()) + } + + fun at(index: Int) = automationEvents[index] + + fun size() = automationEvents.size + + fun swap(fromPosition: Int, toPosition: Int) { + Collections.swap(automationEvents, fromPosition, toPosition) + rxBus.send(EventAutomationDataChanged()) + } + fun getActionDummyObjects(): List { return listOf( //ActionLoopDisable(injector), diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionAlarm.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionAlarm.kt index 1c26cdb8eb..7b1b3d4962 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionAlarm.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionAlarm.kt @@ -33,6 +33,9 @@ class ActionAlarm(injector: HasAndroidInjector) : Action(injector) { var text = InputString(injector) + constructor(injector: HasAndroidInjector, text: String) : this(injector) { + this.text = InputString(injector, text) + } override fun friendlyName(): Int = R.string.alarm override fun shortDescription(): String = resourceHelper.gs(R.string.alarm_message, text.value) @DrawableRes override fun icon(): Int = R.drawable.ic_access_alarm_24dp diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/dialogs/EditEventDialog.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/dialogs/EditEventDialog.kt index 84324fd256..61399eabdc 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/dialogs/EditEventDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/dialogs/EditEventDialog.kt @@ -26,6 +26,7 @@ import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerCon import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.ToastUtils import info.nightscout.androidaps.utils.extensions.plusAssign +import info.nightscout.androidaps.utils.extensions.toVisibility import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable import javax.inject.Inject @@ -66,9 +67,13 @@ class EditEventDialog : DialogFragmentWithDate() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + binding.okcancel.ok.visibility = (!event.readOnly).toVisibility() + binding.inputEventTitle.setText(event.title) + binding.inputEventTitle.isFocusable = false binding.triggerDescription.text = event.trigger.friendlyDescription() + binding.editTrigger.visibility = (!event.readOnly).toVisibility() binding.editTrigger.setOnClickListener { val args = Bundle() args.putString("trigger", event.trigger.toJSON()) @@ -82,6 +87,7 @@ class EditEventDialog : DialogFragmentWithDate() { binding.actionListView.layoutManager = LinearLayoutManager(context) binding.actionListView.adapter = actionListAdapter + binding.addAction.visibility = (!event.readOnly).toVisibility() binding.addAction.setOnClickListener { ChooseActionDialog().show(childFragmentManager, "ChooseActionDialog") } showPreconditions() @@ -140,9 +146,9 @@ class EditEventDialog : DialogFragmentWithDate() { } // store if (position == -1) - automationPlugin.automationEvents.add(event) + automationPlugin.add(event) else - automationPlugin.automationEvents[position] = event + automationPlugin.set(event, position) rxBus.send(EventAutomationDataChanged()) return true @@ -189,20 +195,24 @@ class EditEventDialog : DialogFragmentWithDate() { inner class ViewHolder(val view: View) : RecyclerView.ViewHolder(view) { fun bind(action: Action, recyclerView: RecyclerView.Adapter, position: Int) { - view.findViewById(R.id.automation_layoutText).setOnClickListener { - if (action.hasDialog()) { - val args = Bundle() - args.putInt("actionPosition", position) - args.putString("action", action.toJSON()) - val dialog = EditActionDialog() - dialog.arguments = args - dialog.show(childFragmentManager, "EditActionDialog") + if (!event.readOnly) + view.findViewById(R.id.automation_layoutText).setOnClickListener { + if (action.hasDialog()) { + val args = Bundle() + args.putInt("actionPosition", position) + args.putString("action", action.toJSON()) + val dialog = EditActionDialog() + dialog.arguments = args + dialog.show(childFragmentManager, "EditActionDialog") + } + } + view.findViewById(R.id.automation_iconTrash).run { + visibility = (!event.readOnly).toVisibility() + setOnClickListener { + event.actions.remove(action) + recyclerView.notifyDataSetChanged() + rxBus.send(EventAutomationUpdateGui()) } - } - view.findViewById(R.id.automation_iconTrash).setOnClickListener { - event.actions.remove(action) - recyclerView.notifyDataSetChanged() - rxBus.send(EventAutomationUpdateGui()) } view.findViewById(R.id.automation_action_image).setImageResource(action.icon()) view.findViewById(R.id.automation_viewActionTitle).text = action.shortDescription() diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerDelta.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerDelta.kt index fb85143fef..1fa82bd68f 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerDelta.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerDelta.kt @@ -35,6 +35,12 @@ class TriggerDelta(injector: HasAndroidInjector) : Trigger(injector) { else InputDelta(injector, 0.0, (-MGDL_MAX), MGDL_MAX, 1.0, DecimalFormat("1"), DeltaType.DELTA) } + constructor(injector: HasAndroidInjector, inputDelta: InputDelta, units: String, comparator: Comparator.Compare) : this(injector) { + this.units = units + this.delta = inputDelta + this.comparator.value = comparator + } + private constructor(injector: HasAndroidInjector, triggerDelta: TriggerDelta) : this(injector) { units = triggerDelta.units delta = InputDelta(injector, triggerDelta.delta) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/ActionStringHandler.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/ActionStringHandler.kt index 3aa96ae31d..61f1762b47 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/ActionStringHandler.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/ActionStringHandler.kt @@ -195,7 +195,7 @@ class ActionStringHandler @Inject constructor( val formatInt = DecimalFormat("0") val bolusWizard = BolusWizard(injector).doCalc(profile, profileName, activePlugin.activeTreatments.tempTargetFromHistory, carbsAfterConstraints, cobInfo.displayCob!!, bgReading!!.valueToUnits(profileFunction.getUnits()), - 0.0, percentage.toDouble(), useBG, useCOB, useBolusIOB, useBasalIOB, false, useTT, useTrend) + 0.0, percentage.toDouble(), useBG, useCOB, useBolusIOB, useBasalIOB, false, useTT, useTrend, false) if (Math.abs(bolusWizard.insulinAfterConstraints - bolusWizard.calculatedTotalInsulin) >= 0.01) { sendError("Insulin constraint violation!" + "\nCannot deliver " + format.format(bolusWizard.calculatedTotalInsulin) + "!") @@ -215,7 +215,7 @@ class ActionStringHandler @Inject constructor( if (useCOB) rMessage += "\nFrom" + formatInt.format(cobInfo.displayCob) + "g COB : " + format.format(bolusWizard.insulinFromCOB) + "U" if (useBG) rMessage += "\nFrom BG: " + format.format(bolusWizard.insulinFromBG) + "U" if (useBolusIOB) rMessage += "\nBolus IOB: " + format.format(bolusWizard.insulinFromBolusIOB) + "U" - if (useBasalIOB) rMessage += "\nBasal IOB: " + format.format(bolusWizard.insulinFromBasalsIOB) + "U" + if (useBasalIOB) rMessage += "\nBasal IOB: " + format.format(bolusWizard.insulinFromBasalIOB) + "U" if (useTrend) rMessage += "\nFrom 15' trend: " + format.format(bolusWizard.insulinFromTrend) + "U" if (percentage != 100) { rMessage += "\nPercentage: " + format.format(bolusWizard.totalBeforePercentageAdjustment) + "U * " + percentage + "% -> ~" + format.format(bolusWizard.calculatedTotalInsulin) + "U" diff --git a/app/src/main/java/info/nightscout/androidaps/utils/wizard/BolusWizard.kt b/app/src/main/java/info/nightscout/androidaps/utils/wizard/BolusWizard.kt index f802cadcd4..1bae1a17cf 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/wizard/BolusWizard.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/wizard/BolusWizard.kt @@ -6,6 +6,7 @@ import android.text.Spanned import com.google.common.base.Joiner import dagger.android.HasAndroidInjector import info.nightscout.androidaps.Config +import info.nightscout.androidaps.Constants import info.nightscout.androidaps.R import info.nightscout.androidaps.activities.ErrorHelperActivity import info.nightscout.androidaps.data.DetailedBolusInfo @@ -20,6 +21,15 @@ import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker +import info.nightscout.androidaps.plugins.general.automation.AutomationEvent +import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin +import info.nightscout.androidaps.plugins.general.automation.actions.ActionAlarm +import info.nightscout.androidaps.plugins.general.automation.elements.Comparator +import info.nightscout.androidaps.plugins.general.automation.elements.InputDelta +import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerBg +import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnector +import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerDelta +import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerTime import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin import info.nightscout.androidaps.queue.Callback @@ -30,8 +40,10 @@ import info.nightscout.androidaps.utils.T import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.utils.extensions.formatColor import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.androidaps.utils.sharedPreferences.SP import org.json.JSONException import org.json.JSONObject +import java.text.DecimalFormat import java.util.* import javax.inject.Inject import kotlin.math.abs @@ -43,13 +55,15 @@ class BolusWizard @Inject constructor( @Inject lateinit var aapsLogger: AAPSLogger @Inject lateinit var resourceHelper: ResourceHelper @Inject lateinit var rxBus: RxBusWrapper + @Inject lateinit var sp: SP @Inject lateinit var profileFunction: ProfileFunction @Inject lateinit var constraintChecker: ConstraintChecker - @Inject lateinit var context: Context @Inject lateinit var activePlugin: ActivePluginProvider @Inject lateinit var commandQueue: CommandQueueProvider @Inject lateinit var loopPlugin: LoopPlugin @Inject lateinit var iobCobCalculatorPlugin: IobCobCalculatorPlugin + @Inject lateinit var automationPlugin: AutomationPlugin + @Inject lateinit var dateUtil: DateUtil @Inject lateinit var config: Config init { @@ -72,7 +86,7 @@ class BolusWizard @Inject constructor( private set var insulinFromBolusIOB = 0.0 private set - var insulinFromBasalsIOB = 0.0 + var insulinFromBasalIOB = 0.0 private set var insulinFromCorrection = 0.0 private set @@ -104,7 +118,7 @@ class BolusWizard @Inject constructor( var carbs: Int = 0 var cob: Double = 0.0 var bg: Double = 0.0 - var correction: Double = 0.0 + private var correction: Double = 0.0 private var percentageCorrection: Double = 0.0 private var useBg: Boolean = false private var useCob: Boolean = false @@ -113,8 +127,9 @@ class BolusWizard @Inject constructor( private var useSuperBolus: Boolean = false private var useTT: Boolean = false private var useTrend: Boolean = false + private var useAlarm = false var notes: String = "" - var carbTime: Int = 0 + private var carbTime: Int = 0 @JvmOverloads fun doCalc(profile: Profile, @@ -132,6 +147,7 @@ class BolusWizard @Inject constructor( useSuperBolus: Boolean, useTT: Boolean, useTrend: Boolean, + useAlarm: Boolean, notes: String = "", carbTime: Int = 0 ): BolusWizard { @@ -151,6 +167,7 @@ class BolusWizard @Inject constructor( this.useSuperBolus = useSuperBolus this.useTT = useTT this.useTrend = useTrend + this.useAlarm = useAlarm this.notes = notes this.carbTime = carbTime @@ -193,7 +210,7 @@ class BolusWizard @Inject constructor( val basalIob = activePlugin.activeTreatments.lastCalculationTempBasals.round() insulinFromBolusIOB = if (includeBolusIOB) -bolusIob.iob else 0.0 - insulinFromBasalsIOB = if (includeBasalIOB) -basalIob.basaliob else 0.0 + insulinFromBasalIOB = if (includeBasalIOB) -basalIob.basaliob else 0.0 // Insulin from correction insulinFromCorrection = correction @@ -207,7 +224,7 @@ class BolusWizard @Inject constructor( } // Total - calculatedTotalInsulin = insulinFromBG + insulinFromTrend + insulinFromCarbs + insulinFromBolusIOB + insulinFromBasalsIOB + insulinFromCorrection + insulinFromSuperBolus + insulinFromCOB + calculatedTotalInsulin = insulinFromBG + insulinFromTrend + insulinFromCarbs + insulinFromBolusIOB + insulinFromBasalIOB + insulinFromCorrection + insulinFromSuperBolus + insulinFromCOB // Percentage adjustment totalBeforePercentageAdjustment = calculatedTotalInsulin @@ -229,6 +246,7 @@ class BolusWizard @Inject constructor( return this } + @Suppress("SpellCheckingInspection") private fun nsJSON(): JSONObject { val bolusCalcJSON = JSONObject() try { @@ -239,9 +257,9 @@ class BolusWizard @Inject constructor( bolusCalcJSON.put("targetBGHigh", targetBGHigh) bolusCalcJSON.put("isf", sens) bolusCalcJSON.put("ic", ic) - bolusCalcJSON.put("iob", -(insulinFromBolusIOB + insulinFromBasalsIOB)) + bolusCalcJSON.put("iob", -(insulinFromBolusIOB + insulinFromBasalIOB)) bolusCalcJSON.put("bolusiob", insulinFromBolusIOB) - bolusCalcJSON.put("basaliob", insulinFromBasalsIOB) + bolusCalcJSON.put("basaliob", insulinFromBasalIOB) bolusCalcJSON.put("bolusiobused", includeBolusIOB) bolusCalcJSON.put("basaliobused", includeBasalIOB) bolusCalcJSON.put("bg", bg) @@ -270,14 +288,14 @@ class BolusWizard @Inject constructor( return bolusCalcJSON } - private fun confirmMessageAfterConstraints(pump: PumpInterface): Spanned { + private fun confirmMessageAfterConstraints(advisor: Boolean): Spanned { val actions: LinkedList = LinkedList() if (insulinAfterConstraints > 0) { val pct = if (percentageCorrection != 100.0) " (" + percentageCorrection.toInt() + "%)" else "" actions.add(resourceHelper.gs(R.string.bolus) + ": " + resourceHelper.gs(R.string.formatinsulinunits, insulinAfterConstraints).formatColor(resourceHelper, R.color.bolus) + pct) } - if (carbs > 0) { + if (carbs > 0 && !advisor) { var timeShift = "" if (carbTime > 0) { timeShift += " (+" + resourceHelper.gs(R.string.mins, carbTime) + ")" @@ -287,23 +305,24 @@ class BolusWizard @Inject constructor( actions.add(resourceHelper.gs(R.string.carbs) + ": " + resourceHelper.gs(R.string.format_carbs, carbs).formatColor(resourceHelper, R.color.carbs) + timeShift) } if (insulinFromCOB > 0) { - actions.add(resourceHelper.gs(R.string.cobvsiob) + ": " + resourceHelper.gs(R.string.formatsignedinsulinunits, insulinFromBolusIOB + insulinFromBasalsIOB + insulinFromCOB + insulinFromBG).formatColor(resourceHelper, R.color.cobAlert)) + actions.add(resourceHelper.gs(R.string.cobvsiob) + ": " + resourceHelper.gs(R.string.formatsignedinsulinunits, insulinFromBolusIOB + insulinFromBasalIOB + insulinFromCOB + insulinFromBG).formatColor(resourceHelper, R.color.cobAlert)) val absorptionRate = iobCobCalculatorPlugin.slowAbsorptionPercentage(60) if (absorptionRate > .25) actions.add(resourceHelper.gs(R.string.slowabsorptiondetected, resourceHelper.gc(R.color.cobAlert), (absorptionRate * 100).toInt())) } - if (abs(insulinAfterConstraints - calculatedTotalInsulin) > pump.pumpDescription.pumpType.determineCorrectBolusStepSize(insulinAfterConstraints)) - actions.add(resourceHelper.gs(R.string.bolusconstraintappliedwarn, calculatedTotalInsulin, insulinAfterConstraints).formatColor(resourceHelper, R.color.warning)) - if (config.NSCLIENT) + if (abs(insulinAfterConstraints - calculatedTotalInsulin) > activePlugin.activePump.pumpDescription.pumpType.determineCorrectBolusStepSize(insulinAfterConstraints)) + actions.add(resourceHelper.gs(R.string.bolusconstraintappliedwarn, calculatedTotalInsulin, insulinAfterConstraints).formatColor(resourceHelper, R.color.warning)) + if (config.NSCLIENT && insulinAfterConstraints > 0) actions.add(resourceHelper.gs(R.string.bolusrecordedonly).formatColor(resourceHelper, R.color.warning)) + if (useAlarm && !advisor && carbs > 0 && carbTime > 0) + actions.add(resourceHelper.gs(R.string.alarminxmin, carbTime).formatColor(resourceHelper, R.color.info)) + if (advisor) + actions.add(resourceHelper.gs(R.string.advisoralarm).formatColor(resourceHelper, R.color.info)) return HtmlHelper.fromHtml(Joiner.on("
").join(actions)) } - fun confirmAndExecute(context: Context) { - val profile = profileFunction.getProfile() ?: return - val pump = activePlugin.activePump - + fun confirmAndExecute(ctx: Context) { if (calculatedTotalInsulin > 0.0 || carbs > 0.0) { if (accepted) { aapsLogger.debug(LTag.UI, "guarding: already accepted") @@ -311,76 +330,174 @@ class BolusWizard @Inject constructor( } accepted = true - val confirmMessage = confirmMessageAfterConstraints(pump) + if (sp.getBoolean(R.string.key_usebolusadvisor, false) && Profile.toMgdl(bg, profile.units) > 180 && carbs > 0 && carbTime >= 0) + OKDialog.showYesNoCancel(ctx, resourceHelper.gs(R.string.bolusadvisor), resourceHelper.gs(R.string.bolusadvisormessage), + { bolusAdvisorProcessing(ctx) }, + { commonProcessing(ctx) } + ) + else + commonProcessing(ctx) + } + } - OKDialog.showConfirmation(context, resourceHelper.gs(R.string.boluswizard), confirmMessage, Runnable { - if (insulinAfterConstraints > 0 || carbs > 0) { - if (useSuperBolus) { - aapsLogger.debug("USER ENTRY: SUPERBOLUS TBR") - if (loopPlugin.isEnabled(PluginType.LOOP)) { - loopPlugin.superBolusTo(System.currentTimeMillis() + 2 * 60L * 60 * 1000) - rxBus.send(EventRefreshOverview("WizardDialog")) + private fun bolusAdvisorProcessing(ctx: Context) { + val confirmMessage = confirmMessageAfterConstraints(advisor = true) + OKDialog.showConfirmation(ctx, resourceHelper.gs(R.string.boluswizard), confirmMessage, { + DetailedBolusInfo().apply { + eventType = CareportalEvent.CORRECTIONBOLUS + insulin = insulinAfterConstraints + carbs = 0.0 + context = ctx + glucose = bg + glucoseType = "Manual" + carbTime = 0 + boluscalc = nsJSON() + source = Source.USER + notes = this@BolusWizard.notes + aapsLogger.debug("USER ENTRY: BOLUS ADVISOR insulin $insulinAfterConstraints") + if (insulin > 0) { + commandQueue.bolus(this, object : Callback() { + override fun run() { + if (!result.success) { + val i = Intent(ctx, ErrorHelperActivity::class.java) + i.putExtra("soundid", R.raw.boluserror) + i.putExtra("status", result.comment) + i.putExtra("title", resourceHelper.gs(R.string.treatmentdeliveryerror)) + i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + ctx.startActivity(i) + } else + scheduleEatReminder() } + }) + } + } + }) + } - if (pump.pumpDescription.tempBasalStyle == PumpDescription.ABSOLUTE) { - commandQueue.tempBasalAbsolute(0.0, 120, true, profile, object : Callback() { - override fun run() { - if (!result.success) { - val i = Intent(context, ErrorHelperActivity::class.java) - i.putExtra("soundid", R.raw.boluserror) - i.putExtra("status", result.comment) - i.putExtra("title", resourceHelper.gs(R.string.tempbasaldeliveryerror)) - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - context.startActivity(i) - } - } - }) - } else { + private fun commonProcessing(ctx: Context) { + val profile = profileFunction.getProfile() ?: return + val pump = activePlugin.activePump - commandQueue.tempBasalPercent(0, 120, true, profile, object : Callback() { - override fun run() { - if (!result.success) { - val i = Intent(context, ErrorHelperActivity::class.java) - i.putExtra("soundid", R.raw.boluserror) - i.putExtra("status", result.comment) - i.putExtra("title", resourceHelper.gs(R.string.tempbasaldeliveryerror)) - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - context.startActivity(i) - } - } - }) - } + val confirmMessage = confirmMessageAfterConstraints(advisor = false) + OKDialog.showConfirmation(ctx, resourceHelper.gs(R.string.boluswizard), confirmMessage, { + if (insulinAfterConstraints > 0 || carbs > 0) { + if (useSuperBolus) { + aapsLogger.debug("USER ENTRY: SUPERBOLUS TBR") + if (loopPlugin.isEnabled(PluginType.LOOP)) { + loopPlugin.superBolusTo(System.currentTimeMillis() + 2 * 60L * 60 * 1000) + rxBus.send(EventRefreshOverview("WizardDialog")) } - val detailedBolusInfo = DetailedBolusInfo() - detailedBolusInfo.eventType = CareportalEvent.BOLUSWIZARD - detailedBolusInfo.insulin = insulinAfterConstraints - detailedBolusInfo.carbs = carbs.toDouble() - detailedBolusInfo.context = context - detailedBolusInfo.glucose = bg - detailedBolusInfo.glucoseType = "Manual" - detailedBolusInfo.carbTime = carbTime - detailedBolusInfo.boluscalc = nsJSON() - detailedBolusInfo.source = Source.USER - detailedBolusInfo.notes = notes - aapsLogger.debug("USER ENTRY: BOLUS insulin $insulinAfterConstraints carbs: $carbs") - if (detailedBolusInfo.insulin > 0 || pump.pumpDescription.storesCarbInfo) { - commandQueue.bolus(detailedBolusInfo, object : Callback() { + + if (pump.pumpDescription.tempBasalStyle == PumpDescription.ABSOLUTE) { + commandQueue.tempBasalAbsolute(0.0, 120, true, profile, object : Callback() { override fun run() { if (!result.success) { - val i = Intent(context, ErrorHelperActivity::class.java) + val i = Intent(ctx, ErrorHelperActivity::class.java) i.putExtra("soundid", R.raw.boluserror) i.putExtra("status", result.comment) - i.putExtra("title", resourceHelper.gs(R.string.treatmentdeliveryerror)) + i.putExtra("title", resourceHelper.gs(R.string.tempbasaldeliveryerror)) i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - context.startActivity(i) + ctx.startActivity(i) } } }) } else { - activePlugin.activeTreatments.addToHistoryTreatment(detailedBolusInfo, false) + + commandQueue.tempBasalPercent(0, 120, true, profile, object : Callback() { + override fun run() { + if (!result.success) { + val i = Intent(ctx, ErrorHelperActivity::class.java) + i.putExtra("soundid", R.raw.boluserror) + i.putExtra("status", result.comment) + i.putExtra("title", resourceHelper.gs(R.string.tempbasaldeliveryerror)) + i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + ctx.startActivity(i) + } + } + }) } } - }) + DetailedBolusInfo().apply { + eventType = CareportalEvent.BOLUSWIZARD + insulin = insulinAfterConstraints + carbs = this@BolusWizard.carbs.toDouble() + context = ctx + glucose = bg + glucoseType = "Manual" + carbTime = this@BolusWizard.carbTime + boluscalc = nsJSON() + source = Source.USER + notes = this@BolusWizard.notes + aapsLogger.debug("USER ENTRY: BOLUS WIZARD insulin $insulinAfterConstraints carbs: $carbs") + if (insulin > 0 || pump.pumpDescription.storesCarbInfo) { + commandQueue.bolus(this, object : Callback() { + override fun run() { + if (!result.success) { + val i = Intent(ctx, ErrorHelperActivity::class.java) + i.putExtra("soundid", R.raw.boluserror) + i.putExtra("status", result.comment) + i.putExtra("title", resourceHelper.gs(R.string.treatmentdeliveryerror)) + i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + ctx.startActivity(i) + } + } + }) + } else { + activePlugin.activeTreatments.addToHistoryTreatment(this, false) + } + } + if (useAlarm && carbs > 0 && carbTime > 0) { + scheduleReminder(dateUtil._now() + T.mins(carbTime.toLong()).msecs()) + } + } + }) + } + + private fun scheduleEatReminder() { + val event = AutomationEvent(injector).apply { + title = resourceHelper.gs(R.string.bolusadvisor) + readOnly = true + systemAction = true + autoRemove = true + trigger = TriggerConnector(injector, TriggerConnector.Type.OR).apply { + + // Bg under 180 mgdl and dropping by 15 mgdl + list.add(TriggerConnector(injector, TriggerConnector.Type.AND).apply { + list.add(TriggerBg(injector, 180.0, Constants.MGDL, Comparator.Compare.IS_LESSER)) + list.add(TriggerDelta(injector, InputDelta(injector, -15.0, -360.0, 360.0, 1.0, DecimalFormat("0"), InputDelta.DeltaType.DELTA), Constants.MGDL, Comparator.Compare.IS_EQUAL_OR_LESSER)) + list.add(TriggerDelta(injector, InputDelta(injector, -8.0, -360.0, 360.0, 1.0, DecimalFormat("0"), InputDelta.DeltaType.SHORT_AVERAGE), Constants.MGDL, Comparator.Compare.IS_EQUAL_OR_LESSER)) + }) + // Bg under 160 mgdl and dropping by 9 mgdl + list.add(TriggerConnector(injector, TriggerConnector.Type.AND).apply { + list.add(TriggerBg(injector, 160.0, Constants.MGDL, Comparator.Compare.IS_LESSER)) + list.add(TriggerDelta(injector, InputDelta(injector, -9.0, -360.0, 360.0, 1.0, DecimalFormat("0"), InputDelta.DeltaType.DELTA), Constants.MGDL, Comparator.Compare.IS_EQUAL_OR_LESSER)) + list.add(TriggerDelta(injector, InputDelta(injector, -5.0, -360.0, 360.0, 1.0, DecimalFormat("0"), InputDelta.DeltaType.SHORT_AVERAGE), Constants.MGDL, Comparator.Compare.IS_EQUAL_OR_LESSER)) + }) + // Bg under 145 mgdl and dropping + list.add(TriggerConnector(injector, TriggerConnector.Type.AND).apply { + list.add(TriggerBg(injector, 145.0, Constants.MGDL, Comparator.Compare.IS_LESSER)) + list.add(TriggerDelta(injector, InputDelta(injector, 0.0, -360.0, 360.0, 1.0, DecimalFormat("0"), InputDelta.DeltaType.DELTA), Constants.MGDL, Comparator.Compare.IS_EQUAL_OR_LESSER)) + list.add(TriggerDelta(injector, InputDelta(injector, 0.0, -360.0, 360.0, 1.0, DecimalFormat("0"), InputDelta.DeltaType.SHORT_AVERAGE), Constants.MGDL, Comparator.Compare.IS_EQUAL_OR_LESSER)) + }) + } + actions.add(ActionAlarm(injector, resourceHelper.gs(R.string.time_to_eat))) } + + automationPlugin.addIfNotExists(event) + } + + private fun scheduleReminder(time: Long) { + val event = AutomationEvent(injector).apply { + title = resourceHelper.gs(R.string.timetoeat) + readOnly = true + systemAction = true + autoRemove = true + trigger = TriggerConnector(injector, TriggerConnector.Type.AND).apply { + list.add(TriggerTime(injector, time)) + } + actions.add(ActionAlarm(injector, resourceHelper.gs(R.string.timetoeat))) + } + + automationPlugin.addIfNotExists(event) } } diff --git a/app/src/main/java/info/nightscout/androidaps/utils/wizard/QuickWizardEntry.kt b/app/src/main/java/info/nightscout/androidaps/utils/wizard/QuickWizardEntry.kt index 42f63c4b8c..44fb0cf8db 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/wizard/QuickWizardEntry.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/wizard/QuickWizardEntry.kt @@ -117,7 +117,7 @@ class QuickWizardEntry @Inject constructor(private val injector: HasAndroidInjec trend = true } val percentage = sp.getDouble(R.string.key_boluswizard_percentage, 100.0) - return BolusWizard(injector).doCalc(profile, profileName, tempTarget, carbs(), cob, bg, 0.0, percentage, true, useCOB() == YES, bolusIOB, basalIOB, superBolus, useTempTarget() == YES, trend, "QuickWizard") + return BolusWizard(injector).doCalc(profile, profileName, tempTarget, carbs(), cob, bg, 0.0, percentage, true, useCOB() == YES, bolusIOB, basalIOB, superBolus, useTempTarget() == YES, trend, false, "QuickWizard") } fun buttonText(): String = safeGetString(storage, "buttonText", "") diff --git a/app/src/main/res/layout/automation_dialog_event.xml b/app/src/main/res/layout/automation_dialog_event.xml index f41c5e9d00..165daf2bc8 100644 --- a/app/src/main/res/layout/automation_dialog_event.xml +++ b/app/src/main/res/layout/automation_dialog_event.xml @@ -51,8 +51,8 @@ + android:orientation="vertical" + android:padding="10dp"> - + diff --git a/app/src/main/res/layout/automation_event_item.xml b/app/src/main/res/layout/automation_event_item.xml index 25d1810601..bb88af4702 100644 --- a/app/src/main/res/layout/automation_event_item.xml +++ b/app/src/main/res/layout/automation_event_item.xml @@ -1,5 +1,5 @@ - + android:clickable="true" + android:focusable="true"> + app:layout_constraintBottom_toTopOf="@+id/iconLayout" + app:layout_constraintEnd_toStartOf="@+id/aapsLogo" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + + android:textStyle="bold" + app:layout_constraintBottom_toTopOf="@+id/iconLayout" + app:layout_constraintEnd_toStartOf="@+id/iconTrash" + app:layout_constraintStart_toEndOf="@+id/aapsLogo" + app:layout_constraintTop_toTopOf="parent" /> + android:src="@drawable/ic_trash_outline" + app:layout_constraintBottom_toTopOf="@+id/iconLayout" + app:layout_constraintEnd_toStartOf="@+id/iconSort" + app:layout_constraintStart_toEndOf="@+id/eventTitle" + app:layout_constraintTop_toTopOf="parent" /> + android:layout_marginStart="16dp" + android:orientation="horizontal" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/iconSort" /> - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_wizard.xml b/app/src/main/res/layout/dialog_wizard.xml index eccf8f38bb..d8dc4cb66a 100644 --- a/app/src/main/res/layout/dialog_wizard.xml +++ b/app/src/main/res/layout/dialog_wizard.xml @@ -49,9 +49,9 @@ android:padding="5dp" /> + android:layout_height="match_parent" + android:paddingEnd="5dp"> @@ -128,7 +128,7 @@ android:textStyle="bold" /> @@ -148,17 +148,43 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - + android:orientation="horizontal"> + + + + + + + + + +
@@ -191,7 +217,7 @@ android:textStyle="bold" />
@@ -217,7 +243,7 @@ android:orientation="horizontal"> @@ -250,14 +276,14 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" - android:labelFor="@+id/treatment_wizard_notes" + android:labelFor="@+id/notes" android:padding="10dp" android:text="@string/careportal_newnstreatment_notes_label" android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textStyle="bold" />