package info.nightscout.androidaps.dialogs import android.os.Bundle import android.text.Editable import android.text.TextWatcher import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.Window import android.view.WindowManager import android.widget.AdapterView import android.widget.AdapterView.OnItemSelectedListener import android.widget.ArrayAdapter import android.widget.CompoundButton import androidx.fragment.app.DialogFragment 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.db.BgReading import info.nightscout.androidaps.db.DatabaseHelper import info.nightscout.androidaps.interfaces.Constraint import info.nightscout.androidaps.plugins.bus.RxBus import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventAutosensCalculationFinished import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin import info.nightscout.androidaps.utils.* import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable import kotlinx.android.synthetic.main.dialog_wizard.* import org.slf4j.LoggerFactory import java.text.DecimalFormat import java.util.* import kotlin.math.abs class WizardDialog : DialogFragment() { private val log = LoggerFactory.getLogger(WizardDialog::class.java) private var wizard: BolusWizard? = null //one shot guards private var okClicked: Boolean = false private val textWatcher = 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() } } private var disposable: CompositeDisposable = CompositeDisposable() override fun onStart() { super.onStart() dialog?.window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) } 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) } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, 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) } 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() val maxCarbs = MainApp.getConstraintChecker().maxCarbsAllowed.value() val maxCorrection = MainApp.getConstraintChecker().maxBolusAllowed.value() treatments_wizard_bg_input.setParams(savedInstanceState?.getDouble("treatments_wizard_bg_input") ?: 0.0, 0.0, 500.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) val bolusStep = ConfigBuilderPlugin.getPlugin().activePump?.pumpDescription?.bolusStep ?: 0.1 treatments_wizard_correction_input.setParams(savedInstanceState?.getDouble("treatments_wizard_correction_input") ?: 0.0, -maxCorrection, maxCorrection, bolusStep, DecimalFormatter.pumpSupportedBolusFormat(), 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) initDialog() treatments_wizard_percent_used.text = MainApp.gs(R.string.format_percent, SP.getInt(R.string.key_boluswizard_percentage, 100)) // ok button ok.setOnClickListener { if (okClicked) { log.debug("guarding: ok already clicked") } else { okClicked = true calculateInsulin() context?.let { context -> wizard?.confirmAndExecute(context) } } dismiss() } // cancel button 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) val showCalc = SP.getBoolean(MainApp.gs(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 -> run { SP.putBoolean(MainApp.gs(R.string.key_wizard_calculation_visible), isChecked) treatments_wizard_delimiter.visibility = isChecked.toVisibility() treatments_wizard_resulttable.visibility = isChecked.toVisibility() } } // profile spinner treatments_wizard_profile.onItemSelectedListener = object : OnItemSelectedListener { override fun onNothingSelected(parent: AdapterView<*>?) { ToastUtils.showToastInUiThread(MainApp.instance().applicationContext, MainApp.gs(R.string.noprofileselected)) ok.visibility = View.GONE } override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { calculateInsulin() ok.visibility = View.VISIBLE } } // bus disposable.add(RxBus .toObservable(EventAutosensCalculationFinished::class.java) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ activity?.runOnUiThread { calculateInsulin() } }, { FabricPrivacy.logException(it) }) ) } override fun onDestroyView() { super.onDestroyView() disposable.clear() } private fun onCheckedChanged(buttonView: CompoundButton, @Suppress("UNUSED_PARAMETER") state: Boolean) { saveCheckedStates() treatments_wizard_ttcheckbox.isEnabled = treatments_wizard_bgcheckbox.isChecked && TreatmentsPlugin.getPlugin().tempTargetFromHistory != null if (buttonView.id == treatments_wizard_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 } else { treatments_wizard_bolusiobcheckbox.isEnabled = true treatments_wizard_basaliobcheckbox.isEnabled = true } } private fun saveCheckedStates() { SP.putBoolean(MainApp.gs(R.string.key_wizard_include_cob), treatments_wizard_cobcheckbox.isChecked) SP.putBoolean(MainApp.gs(R.string.key_wizard_include_trend_bg), treatments_wizard_bgtrendcheckbox.isChecked) } private fun loadCheckedStates() { treatments_wizard_bgtrendcheckbox.isChecked = SP.getBoolean(MainApp.gs(R.string.key_wizard_include_trend_bg), false) treatments_wizard_cobcheckbox.isChecked = SP.getBoolean(MainApp.gs(R.string.key_wizard_include_cob), false) } private fun initDialog() { val profile = ProfileFunctions.getInstance().getProfile() val profileStore = ConfigBuilderPlugin.getPlugin().activeProfileInterface?.profile if (profile == null || profileStore == null) { ToastUtils.showToastInUiThread(MainApp.instance().applicationContext, MainApp.gs(R.string.noprofile)) dismiss() return } val profileList: ArrayList profileList = profileStore.getProfileList() profileList.add(0, MainApp.gs(R.string.active)) context?.let { context -> val adapter = ArrayAdapter(context, R.layout.spinner_centered, profileList) treatments_wizard_profile.adapter = adapter } ?: return val units = ProfileFunctions.getSystemUnits() treatments_wizard_bgunits.text = units if (units == Constants.MGDL) treatments_wizard_bg_input.setStep(1.0) else treatments_wizard_bg_input.setStep(0.1) // Set BG if not old val lastBg = DatabaseHelper.actualBg() if (lastBg != null) { treatments_wizard_bg_input.value = lastBg.valueToUnits(units) } else { treatments_wizard_bg_input.value = 0.0 } treatments_wizard_ttcheckbox.isEnabled = TreatmentsPlugin.getPlugin().tempTargetFromHistory != null // IOB calculation TreatmentsPlugin.getPlugin().updateTotalIOBTreatments() val bolusIob = TreatmentsPlugin.getPlugin().lastCalculationTreatments.round() TreatmentsPlugin.getPlugin().updateTotalIOBTempBasals() val basalIob = TreatmentsPlugin.getPlugin().lastCalculationTempBasals.round() treatments_wizard_bolusiobinsulin.text = StringUtils.formatInsulin(-bolusIob.iob) treatments_wizard_basaliobinsulin.text = StringUtils.formatInsulin(-basalIob.basaliob) calculateInsulin() treatments_wizard_percent_used.visibility = (SP.getInt(R.string.key_boluswizard_percentage, 100) != 100).toVisibility() } private fun calculateInsulin() { val profileStore = ConfigBuilderPlugin.getPlugin().activeProfileInterface?.profile if (treatments_wizard_profile.selectedItem == null || profileStore == null) return // not initialized yet var profileName = treatments_wizard_profile.selectedItem.toString() val specificProfile: Profile? if (profileName == MainApp.gs(R.string.active)) { specificProfile = ProfileFunctions.getInstance().getProfile() profileName = ProfileFunctions.getInstance().getProfileName() ?: return } else specificProfile = profileStore.getSpecificProfile(profileName) 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) val carbsAfterConstraint = MainApp.getConstraintChecker().applyCarbsConstraints(Constraint(carbs)).value() if (abs(carbs - carbsAfterConstraint) > 0.01) { treatments_wizard_carbs_input.value = 0.0 ToastUtils.showToastInUiThread(MainApp.instance().applicationContext, MainApp.gs(R.string.carbsconstraintapplied)) return } bg = if (treatments_wizard_bgcheckbox.isChecked) bg else 0.0 val tempTarget = if (treatments_wizard_ttcheckbox.isChecked) TreatmentsPlugin.getPlugin().tempTargetFromHistory else null // COB var cob = 0.0 if (treatments_wizard_cobcheckbox.isChecked) { val cobInfo = IobCobCalculatorPlugin.getPlugin().getCobInfo(false, "Wizard COB") cobInfo.displayCob?.let { cob = it } } val carbTime = SafeParse.stringToInt(treatments_wizard_carb_time_input.text) wizard = BolusWizard(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) wizard?.let { wizard -> treatments_wizard_bg.text = String.format(MainApp.gs(R.string.format_bg_isf), BgReading().value(Profile.toMgdl(bg, ProfileFunctions.getSystemUnits())).valueToUnitsToString(ProfileFunctions.getSystemUnits()), wizard.sens) treatments_wizard_bginsulin.text = StringUtils.formatInsulin(wizard.insulinFromBG) treatments_wizard_carbs.text = String.format(MainApp.gs(R.string.format_carbs_ic), carbs.toDouble(), wizard.ic) treatments_wizard_carbsinsulin.text = StringUtils.formatInsulin(wizard.insulinFromCarbs) treatments_wizard_bolusiobinsulin.text = StringUtils.formatInsulin(wizard.insulinFromBolusIOB) treatments_wizard_basaliobinsulin.text = StringUtils.formatInsulin(wizard.insulinFromBasalsIOB) treatments_wizard_correctioninsulin.text = StringUtils.formatInsulin(wizard.insulinFromCorrection) // Superbolus treatments_wizard_sb.text = if (treatments_wizard_sbcheckbox.isChecked) MainApp.gs(R.string.twohours) else "" treatments_wizard_sbinsulin.text = StringUtils.formatInsulin(wizard.insulinFromSuperBolus) // Trend if (treatments_wizard_bgtrendcheckbox.isChecked && wizard.glucoseStatus != null) { treatments_wizard_bgtrend.text = ((if (wizard.trend > 0) "+" else "") + Profile.toUnitsString(wizard.trend * 3, wizard.trend * 3 / Constants.MMOLL_TO_MGDL, ProfileFunctions.getSystemUnits()) + " " + ProfileFunctions.getSystemUnits()) } else { treatments_wizard_bgtrend.text = "" } treatments_wizard_bgtrendinsulin.text = StringUtils.formatInsulin(wizard.insulinFromTrend) // COB if (treatments_wizard_cobcheckbox.isChecked) { treatments_wizard_cob.text = String.format(MainApp.gs(R.string.format_cob_ic), cob, wizard.ic) treatments_wizard_cobinsulin.text = StringUtils.formatInsulin(wizard.insulinFromCOB) } else { treatments_wizard_cob.text = "" treatments_wizard_cobinsulin.text = "" } if (wizard.calculatedTotalInsulin > 0.0 || carbsAfterConstraint > 0.0) { val insulinText = if (wizard.calculatedTotalInsulin > 0.0) MainApp.gs(R.string.formatinsulinunits, wizard.calculatedTotalInsulin) else "" val carbsText = if (carbsAfterConstraint > 0.0) MainApp.gs(R.string.format_carbs, carbsAfterConstraint) else "" treatments_wizard_total.text = MainApp.gs(R.string.result_insulin_carbs, insulinText, carbsText) ok.visibility = View.VISIBLE } else { treatments_wizard_total.text = MainApp.gs(R.string.missing_carbs, wizard.carbsEquivalent.toInt()) ok.visibility = View.INVISIBLE } } } }