InsulinDialog -> ui

This commit is contained in:
Milos Kozak 2022-11-11 10:41:15 +01:00
parent 228167820c
commit 18b434b2e2
16 changed files with 325 additions and 326 deletions

View file

@ -47,7 +47,7 @@ import info.nightscout.rx.logging.LTag
import info.nightscout.shared.interfaces.ResourceHelper import info.nightscout.shared.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.shared.utils.DateUtil import info.nightscout.shared.utils.DateUtil
import info.nightscout.ui.utils.ActivityMonitor import info.nightscout.ui.activityMonitor.ActivityMonitor
import info.nightscout.ui.widget.Widget import info.nightscout.ui.widget.Widget
import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.exceptions.UndeliverableException import io.reactivex.rxjava3.exceptions.UndeliverableException

View file

@ -3,7 +3,6 @@ package info.nightscout.androidaps.di
import dagger.Module import dagger.Module
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
import info.nightscout.androidaps.activities.MyPreferenceFragment import info.nightscout.androidaps.activities.MyPreferenceFragment
import info.nightscout.androidaps.dialogs.InsulinDialog
import info.nightscout.androidaps.dialogs.LoopDialog import info.nightscout.androidaps.dialogs.LoopDialog
import info.nightscout.androidaps.dialogs.NtpProgressDialog import info.nightscout.androidaps.dialogs.NtpProgressDialog
import info.nightscout.androidaps.dialogs.ProfileSwitchDialog import info.nightscout.androidaps.dialogs.ProfileSwitchDialog
@ -40,7 +39,6 @@ abstract class FragmentsModule {
@ContributesAndroidInjector abstract fun contributesEditQuickWizardDialog(): EditQuickWizardDialog @ContributesAndroidInjector abstract fun contributesEditQuickWizardDialog(): EditQuickWizardDialog
@ContributesAndroidInjector abstract fun contributesInsulinDialog(): InsulinDialog
@ContributesAndroidInjector abstract fun contributesLoopDialog(): LoopDialog @ContributesAndroidInjector abstract fun contributesLoopDialog(): LoopDialog
@ContributesAndroidInjector abstract fun contributesObjectivesExamDialog(): ObjectivesExamDialog @ContributesAndroidInjector abstract fun contributesObjectivesExamDialog(): ObjectivesExamDialog
@ContributesAndroidInjector abstract fun contributesProfileSwitchDialog(): ProfileSwitchDialog @ContributesAndroidInjector abstract fun contributesProfileSwitchDialog(): ProfileSwitchDialog

View file

@ -1,2 +0,0 @@
package info.nightscout.androidaps.dialogs

View file

@ -1,311 +1,2 @@
package info.nightscout.androidaps.dialogs package info.nightscout.androidaps.dialogs
import android.content.Context
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 com.google.common.base.Joiner
import info.nightscout.androidaps.R
import info.nightscout.androidaps.databinding.DialogInsulinBinding
import info.nightscout.androidaps.extensions.formatColor
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.CommandQueue
import info.nightscout.androidaps.interfaces.Constraints
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.utils.DecimalFormatter
import info.nightscout.androidaps.utils.DefaultValueHelper
import info.nightscout.androidaps.utils.ToastUtils
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.extensions.toSignedString
import info.nightscout.androidaps.utils.protection.ProtectionCheck
import info.nightscout.androidaps.utils.protection.ProtectionCheck.Protection.BOLUS
import info.nightscout.core.profile.toMgdl
import info.nightscout.core.pumpExtensions.insertBolusTransaction
import info.nightscout.database.entities.TemporaryTarget
import info.nightscout.database.entities.UserEntry.Action
import info.nightscout.database.entities.UserEntry.Sources
import info.nightscout.database.entities.ValueWithUnit
import info.nightscout.database.impl.AppRepository
import info.nightscout.database.impl.transactions.InsertAndCancelCurrentTemporaryTargetTransaction
import info.nightscout.interfaces.BolusTimer
import info.nightscout.interfaces.Config
import info.nightscout.interfaces.GlucoseUnit
import info.nightscout.interfaces.constraints.Constraint
import info.nightscout.interfaces.profile.Profile
import info.nightscout.interfaces.pump.DetailedBolusInfo
import info.nightscout.interfaces.queue.Callback
import info.nightscout.interfaces.ui.ActivityNames
import info.nightscout.interfaces.utils.HtmlHelper
import info.nightscout.rx.logging.LTag
import info.nightscout.shared.SafeParse
import info.nightscout.shared.extensions.toVisibility
import info.nightscout.shared.interfaces.ResourceHelper
import info.nightscout.shared.utils.T
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import java.text.DecimalFormat
import java.util.LinkedList
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlin.math.abs
import kotlin.math.max
class InsulinDialog : DialogFragmentWithDate() {
@Inject lateinit var constraintChecker: Constraints
@Inject lateinit var rh: ResourceHelper
@Inject lateinit var defaultValueHelper: DefaultValueHelper
@Inject lateinit var profileFunction: ProfileFunction
@Inject lateinit var commandQueue: CommandQueue
@Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var ctx: Context
@Inject lateinit var repository: AppRepository
@Inject lateinit var config: Config
@Inject lateinit var bolusTimer: BolusTimer
@Inject lateinit var uel: UserEntryLogger
@Inject lateinit var protectionCheck: ProtectionCheck
@Inject lateinit var activityNames: ActivityNames
companion object {
const val PLUS1_DEFAULT = 0.5
const val PLUS2_DEFAULT = 1.0
const val PLUS3_DEFAULT = 2.0
}
private var queryingProtection = false
private val disposable = CompositeDisposable()
private var _binding: DialogInsulinBinding? = null
// This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!!
private val textWatcher: TextWatcher = object : TextWatcher {
override fun afterTextChanged(s: Editable) {
validateInputs()
}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
}
private fun validateInputs() {
val maxInsulin = constraintChecker.getMaxBolusAllowed().value()
if (abs(binding.time.value.toInt()) > 12 * 60) {
binding.time.value = 0.0
ToastUtils.warnToast(context, R.string.constraint_applied)
}
if (binding.amount.value > maxInsulin) {
binding.amount.value = 0.0
ToastUtils.warnToast(context, R.string.bolus_constraint_applied)
}
}
override fun onSaveInstanceState(savedInstanceState: Bundle) {
super.onSaveInstanceState(savedInstanceState)
savedInstanceState.putDouble("time", binding.time.value)
savedInstanceState.putDouble("amount", binding.amount.value)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
onCreateViewGeneral()
_binding = DialogInsulinBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (config.NSCLIENT) {
binding.recordOnly.isChecked = true
binding.recordOnly.isEnabled = false
}
val maxInsulin = constraintChecker.getMaxBolusAllowed().value()
binding.time.setParams(
savedInstanceState?.getDouble("time")
?: 0.0, -12 * 60.0, 12 * 60.0, 5.0, DecimalFormat("0"), false, binding.okcancel.ok, textWatcher
)
binding.amount.setParams(
savedInstanceState?.getDouble("amount")
?: 0.0, 0.0, maxInsulin, activePlugin.activePump.pumpDescription.bolusStep, DecimalFormatter.pumpSupportedBolusFormat(activePlugin.activePump), false, binding.okcancel.ok, textWatcher
)
val plus05Text = sp.getDouble(rh.gs(R.string.key_insulin_button_increment_1), PLUS1_DEFAULT).toSignedString(activePlugin.activePump)
binding.plus05.text = plus05Text
binding.plus05.contentDescription = rh.gs(R.string.overview_insulin_label) + " " + plus05Text
binding.plus05.setOnClickListener {
binding.amount.value = max(
0.0, binding.amount.value
+ sp.getDouble(rh.gs(R.string.key_insulin_button_increment_1), PLUS1_DEFAULT)
)
validateInputs()
binding.amount.announceValue()
}
val plus10Text = sp.getDouble(rh.gs(R.string.key_insulin_button_increment_2), PLUS2_DEFAULT).toSignedString(activePlugin.activePump)
binding.plus10.text = plus10Text
binding.plus10.contentDescription = rh.gs(R.string.overview_insulin_label) + " " + plus10Text
binding.plus10.setOnClickListener {
binding.amount.value = max(
0.0, binding.amount.value
+ sp.getDouble(rh.gs(R.string.key_insulin_button_increment_2), PLUS2_DEFAULT)
)
validateInputs()
binding.amount.announceValue()
}
val plus20Text = sp.getDouble(rh.gs(R.string.key_insulin_button_increment_3), PLUS3_DEFAULT).toSignedString(activePlugin.activePump)
binding.plus20.text = plus20Text
binding.plus20.contentDescription = rh.gs(R.string.overview_insulin_label) + " " + plus20Text
binding.plus20.setOnClickListener {
binding.amount.value = max(
0.0, binding.amount.value
+ sp.getDouble(rh.gs(R.string.key_insulin_button_increment_3), PLUS3_DEFAULT)
)
validateInputs()
binding.amount.announceValue()
}
binding.timeLayout.visibility = View.GONE
binding.recordOnly.setOnCheckedChangeListener { _, isChecked: Boolean ->
binding.timeLayout.visibility = isChecked.toVisibility()
}
binding.insulinLabel.labelFor = binding.amount.editTextId
binding.timeLabel.labelFor = binding.time.editTextId
}
override fun onDestroyView() {
super.onDestroyView()
disposable.clear()
_binding = null
}
override fun submit(): Boolean {
if (_binding == null) return false
val pumpDescription = activePlugin.activePump.pumpDescription
val insulin = SafeParse.stringToDouble(binding.amount.text)
val insulinAfterConstraints = constraintChecker.applyBolusConstraints(Constraint(insulin)).value()
val actions: LinkedList<String?> = LinkedList()
val units = profileFunction.getUnits()
val unitLabel = if (units == GlucoseUnit.MMOL) rh.gs(R.string.mmol) else rh.gs(R.string.mgdl)
val recordOnlyChecked = binding.recordOnly.isChecked
val eatingSoonChecked = binding.startEatingSoonTt.isChecked
if (insulinAfterConstraints > 0) {
actions.add(rh.gs(R.string.bolus) + ": " + DecimalFormatter.toPumpSupportedBolus(insulinAfterConstraints, activePlugin.activePump, rh).formatColor(context, rh, R.attr.bolusColor))
if (recordOnlyChecked)
actions.add(rh.gs(R.string.bolusrecordedonly).formatColor(context, rh, R.attr.warningColor))
if (abs(insulinAfterConstraints - insulin) > pumpDescription.pumpType.determineCorrectBolusStepSize(insulinAfterConstraints))
actions.add(rh.gs(R.string.bolusconstraintappliedwarn, insulin, insulinAfterConstraints).formatColor(context, rh, R.attr.warningColor))
}
val eatingSoonTTDuration = defaultValueHelper.determineEatingSoonTTDuration()
val eatingSoonTT = defaultValueHelper.determineEatingSoonTT()
if (eatingSoonChecked)
actions.add(
rh.gs(R.string.temp_target_short) + ": " + (DecimalFormatter.to1Decimal(eatingSoonTT) + " " + unitLabel + " (" + rh.gs(R.string.format_mins, eatingSoonTTDuration) + ")")
.formatColor(context, rh, R.attr.tempTargetConfirmation)
)
val timeOffset = binding.time.value.toInt()
val time = dateUtil.now() + T.mins(timeOffset.toLong()).msecs()
if (timeOffset != 0)
actions.add(rh.gs(R.string.time) + ": " + dateUtil.dateAndTimeString(time))
val notes = binding.notesLayout.notes.text.toString()
if (notes.isNotEmpty())
actions.add(rh.gs(R.string.notes_label) + ": " + notes)
if (insulinAfterConstraints > 0 || eatingSoonChecked) {
activity?.let { activity ->
OKDialog.showConfirmation(activity, rh.gs(R.string.bolus), HtmlHelper.fromHtml(Joiner.on("<br/>").join(actions)), {
if (eatingSoonChecked) {
uel.log(
Action.TT, Sources.InsulinDialog,
notes,
ValueWithUnit.TherapyEventTTReason(TemporaryTarget.Reason.EATING_SOON),
ValueWithUnit.fromGlucoseUnit(eatingSoonTT, units.asText),
ValueWithUnit.Minute(eatingSoonTTDuration)
)
disposable += repository.runTransactionForResult(
InsertAndCancelCurrentTemporaryTargetTransaction(
timestamp = System.currentTimeMillis(),
duration = TimeUnit.MINUTES.toMillis(eatingSoonTTDuration.toLong()),
reason = TemporaryTarget.Reason.EATING_SOON,
lowTarget = Profile.toMgdl(eatingSoonTT, profileFunction.getUnits()),
highTarget = Profile.toMgdl(eatingSoonTT, profileFunction.getUnits())
)
).subscribe({ result ->
result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted temp target $it") }
result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated temp target $it") }
}, {
aapsLogger.error(LTag.DATABASE, "Error while saving temporary target", it)
})
}
if (insulinAfterConstraints > 0) {
val detailedBolusInfo = DetailedBolusInfo()
detailedBolusInfo.eventType = DetailedBolusInfo.EventType.CORRECTION_BOLUS
detailedBolusInfo.insulin = insulinAfterConstraints
detailedBolusInfo.context = context
detailedBolusInfo.notes = notes
detailedBolusInfo.timestamp = time
if (recordOnlyChecked) {
uel.log(Action.BOLUS, Sources.InsulinDialog,
rh.gs(R.string.record) + if (notes.isNotEmpty()) ": $notes" else "",
ValueWithUnit.SimpleString(rh.gsNotLocalised(R.string.record)),
ValueWithUnit.Insulin(insulinAfterConstraints),
ValueWithUnit.Minute(timeOffset).takeIf { timeOffset != 0 })
disposable += repository.runTransactionForResult(detailedBolusInfo.insertBolusTransaction())
.subscribe(
{ result -> result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted bolus $it") } },
{ aapsLogger.error(LTag.DATABASE, "Error while saving bolus", it) }
)
if (timeOffset == 0)
bolusTimer.removeAutomationEventBolusReminder()
} else {
uel.log(
Action.BOLUS, Sources.InsulinDialog,
notes,
ValueWithUnit.Insulin(insulinAfterConstraints)
)
commandQueue.bolus(detailedBolusInfo, object : Callback() {
override fun run() {
if (!result.success) {
activityNames.runAlarm(ctx, result.comment, rh.gs(R.string.treatmentdeliveryerror), R.raw.boluserror)
} else {
bolusTimer.removeAutomationEventBolusReminder()
}
}
})
}
}
})
}
} else
activity?.let { activity ->
OKDialog.show(activity, rh.gs(R.string.bolus), rh.gs(R.string.no_action_selected))
}
return true
}
override fun onResume() {
super.onResume()
if (!queryingProtection) {
queryingProtection = true
activity?.let { activity ->
val cancelFail = {
queryingProtection = false
aapsLogger.debug(LTag.APS, "Dialog canceled on resume protection: ${this.javaClass.simpleName}")
ToastUtils.warnToast(ctx, R.string.dialog_canceled)
dismiss()
}
protectionCheck.queryProtection(activity, BOLUS, { queryingProtection = false }, cancelFail, cancelFail)
}
}
}
}

View file

@ -30,7 +30,7 @@ import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.data.ProfileSealed import info.nightscout.androidaps.data.ProfileSealed
import info.nightscout.androidaps.databinding.OverviewFragmentBinding import info.nightscout.androidaps.databinding.OverviewFragmentBinding
import info.nightscout.androidaps.dialogs.InsulinDialog import info.nightscout.ui.dialogs.InsulinDialog
import info.nightscout.androidaps.dialogs.LoopDialog import info.nightscout.androidaps.dialogs.LoopDialog
import info.nightscout.androidaps.dialogs.ProfileSwitchDialog import info.nightscout.androidaps.dialogs.ProfileSwitchDialog
import info.nightscout.androidaps.dialogs.TempTargetDialog import info.nightscout.androidaps.dialogs.TempTargetDialog

View file

@ -4,7 +4,7 @@ import android.app.NotificationManager
import android.content.Context import android.content.Context
import dagger.android.HasAndroidInjector import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.dialogs.InsulinDialog import info.nightscout.ui.dialogs.InsulinDialog
import info.nightscout.androidaps.extensions.convertedToAbsolute import info.nightscout.androidaps.extensions.convertedToAbsolute
import info.nightscout.androidaps.extensions.toStringShort import info.nightscout.androidaps.extensions.toStringShort
import info.nightscout.androidaps.extensions.total import info.nightscout.androidaps.extensions.total

View file

@ -6,7 +6,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:focusableInTouchMode="true" android:focusableInTouchMode="true"
tools:context=".dialogs.InsulinDialog"> tools:context="info.nightscout.ui.dialogs.InsulinDialog">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"

View file

@ -361,9 +361,6 @@
<string name="key_show_treatment_button" translatable="false">show_treatment_button</string> <string name="key_show_treatment_button" translatable="false">show_treatment_button</string>
<string name="show_calibration_button_summary">Sends a calibration to xDrip+ or open BYODA calibration dialog</string> <string name="show_calibration_button_summary">Sends a calibration to xDrip+ or open BYODA calibration dialog</string>
<string name="show_cgm_button_summary">Opens xDrip+ or BYODA, back buttons returns to AAPS</string> <string name="show_cgm_button_summary">Opens xDrip+ or BYODA, back buttons returns to AAPS</string>
<string name="key_insulin_button_increment_1" translatable="false">insulin_button_increment_1</string>
<string name="key_insulin_button_increment_2" translatable="false">insulin_button_increment_2</string>
<string name="key_insulin_button_increment_3" translatable="false">insulin_button_increment_3</string>
<string name="carb_increment_button_message">Number of carbs to add when button is pressed</string> <string name="carb_increment_button_message">Number of carbs to add when button is pressed</string>
<string name="insulin_increment_button_message">Amount of insulin to add when button is pressed</string> <string name="insulin_increment_button_message">Amount of insulin to add when button is pressed</string>
<string name="error_starting_cgm">Could not launch CGM application. Make sure it is installed.</string> <string name="error_starting_cgm">Could not launch CGM application. Make sure it is installed.</string>
@ -381,8 +378,6 @@
<string name="allow_automated_crash_reporting">Allow automated crash reporting and feature usage data to be sent to the developers via the fabric.io service.</string> <string name="allow_automated_crash_reporting">Allow automated crash reporting and feature usage data to be sent to the developers via the fabric.io service.</string>
<string name="g5appnotdetected">Please update your Dexcom app to supported version</string> <string name="g5appnotdetected">Please update your Dexcom app to supported version</string>
<string name="dexcom_app_not_installed">Dexcom app is not installed.</string> <string name="dexcom_app_not_installed">Dexcom app is not installed.</string>
<string name="do_not_bolus_record_only">Do not bolus, record only</string>
<string name="bolusrecordedonly">Bolus will be recorded only (not delivered by pump)</string>
<string name="loop_smbsetbypump_label">SMB set by pump</string> <string name="loop_smbsetbypump_label">SMB set by pump</string>
<string name="overview_show_activity">Activity</string> <string name="overview_show_activity">Activity</string>
<string name="overview_show_bgi">Blood Glucose Impact</string> <string name="overview_show_bgi">Blood Glucose Impact</string>

View file

@ -15,7 +15,7 @@ import info.nightscout.database.entities.UserEntry.Sources
import info.nightscout.rx.AapsSchedulers import info.nightscout.rx.AapsSchedulers
import info.nightscout.ui.R import info.nightscout.ui.R
import info.nightscout.ui.databinding.ActivityStatsBinding import info.nightscout.ui.databinding.ActivityStatsBinding
import info.nightscout.ui.utils.ActivityMonitor import info.nightscout.ui.activityMonitor.ActivityMonitor
import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign import io.reactivex.rxjava3.kotlin.plusAssign

View file

@ -1,4 +1,4 @@
package info.nightscout.ui.utils package info.nightscout.ui.activityMonitor
import android.app.Activity import android.app.Activity
import android.app.Application import android.app.Application

View file

@ -21,6 +21,7 @@ import info.nightscout.ui.dialogs.CarbsDialog
import info.nightscout.ui.dialogs.CareDialog import info.nightscout.ui.dialogs.CareDialog
import info.nightscout.ui.dialogs.ExtendedBolusDialog import info.nightscout.ui.dialogs.ExtendedBolusDialog
import info.nightscout.ui.dialogs.FillDialog import info.nightscout.ui.dialogs.FillDialog
import info.nightscout.ui.dialogs.InsulinDialog
import info.nightscout.ui.dialogs.ProfileViewerDialog import info.nightscout.ui.dialogs.ProfileViewerDialog
import info.nightscout.ui.dialogs.WizardInfoDialog import info.nightscout.ui.dialogs.WizardInfoDialog
@ -35,6 +36,7 @@ abstract class UiModule {
@ContributesAndroidInjector abstract fun contributesProfileViewerDialog(): ProfileViewerDialog @ContributesAndroidInjector abstract fun contributesProfileViewerDialog(): ProfileViewerDialog
@ContributesAndroidInjector abstract fun contributesExtendedBolusDialog(): ExtendedBolusDialog @ContributesAndroidInjector abstract fun contributesExtendedBolusDialog(): ExtendedBolusDialog
@ContributesAndroidInjector abstract fun contributesFillDialog(): FillDialog @ContributesAndroidInjector abstract fun contributesFillDialog(): FillDialog
@ContributesAndroidInjector abstract fun contributesInsulinDialog(): InsulinDialog
@ContributesAndroidInjector abstract fun contributesTDDStatsActivity(): TDDStatsActivity @ContributesAndroidInjector abstract fun contributesTDDStatsActivity(): TDDStatsActivity
@ContributesAndroidInjector abstract fun contributeBolusProgressHelperActivity(): BolusProgressHelperActivity @ContributesAndroidInjector abstract fun contributeBolusProgressHelperActivity(): BolusProgressHelperActivity

View file

@ -0,0 +1,310 @@
package info.nightscout.ui.dialogs
import android.content.Context
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 com.google.common.base.Joiner
import info.nightscout.androidaps.dialogs.DialogFragmentWithDate
import info.nightscout.androidaps.extensions.formatColor
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.CommandQueue
import info.nightscout.androidaps.interfaces.Constraints
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.utils.DecimalFormatter
import info.nightscout.androidaps.utils.DefaultValueHelper
import info.nightscout.androidaps.utils.ToastUtils
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.extensions.toSignedString
import info.nightscout.androidaps.utils.protection.ProtectionCheck
import info.nightscout.core.profile.toMgdl
import info.nightscout.core.pumpExtensions.insertBolusTransaction
import info.nightscout.database.entities.TemporaryTarget
import info.nightscout.database.entities.UserEntry
import info.nightscout.database.entities.ValueWithUnit
import info.nightscout.database.impl.AppRepository
import info.nightscout.database.impl.transactions.InsertAndCancelCurrentTemporaryTargetTransaction
import info.nightscout.interfaces.BolusTimer
import info.nightscout.interfaces.Config
import info.nightscout.interfaces.GlucoseUnit
import info.nightscout.interfaces.constraints.Constraint
import info.nightscout.interfaces.profile.Profile
import info.nightscout.interfaces.pump.DetailedBolusInfo
import info.nightscout.interfaces.queue.Callback
import info.nightscout.interfaces.ui.ActivityNames
import info.nightscout.interfaces.utils.HtmlHelper
import info.nightscout.rx.logging.LTag
import info.nightscout.shared.SafeParse
import info.nightscout.shared.extensions.toVisibility
import info.nightscout.shared.interfaces.ResourceHelper
import info.nightscout.shared.utils.T
import info.nightscout.ui.R
import info.nightscout.ui.databinding.DialogInsulinBinding
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import java.text.DecimalFormat
import java.util.LinkedList
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlin.math.abs
import kotlin.math.max
class InsulinDialog : DialogFragmentWithDate() {
@Inject lateinit var constraintChecker: Constraints
@Inject lateinit var rh: ResourceHelper
@Inject lateinit var defaultValueHelper: DefaultValueHelper
@Inject lateinit var profileFunction: ProfileFunction
@Inject lateinit var commandQueue: CommandQueue
@Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var ctx: Context
@Inject lateinit var repository: AppRepository
@Inject lateinit var config: Config
@Inject lateinit var bolusTimer: BolusTimer
@Inject lateinit var uel: UserEntryLogger
@Inject lateinit var protectionCheck: ProtectionCheck
@Inject lateinit var activityNames: ActivityNames
companion object {
const val PLUS1_DEFAULT = 0.5
const val PLUS2_DEFAULT = 1.0
const val PLUS3_DEFAULT = 2.0
}
private var queryingProtection = false
private val disposable = CompositeDisposable()
private var _binding: DialogInsulinBinding? = null
// This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!!
private val textWatcher: TextWatcher = object : TextWatcher {
override fun afterTextChanged(s: Editable) {
validateInputs()
}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
}
private fun validateInputs() {
val maxInsulin = constraintChecker.getMaxBolusAllowed().value()
if (abs(binding.time.value.toInt()) > 12 * 60) {
binding.time.value = 0.0
ToastUtils.warnToast(context, R.string.constraint_applied)
}
if (binding.amount.value > maxInsulin) {
binding.amount.value = 0.0
ToastUtils.warnToast(context, R.string.bolus_constraint_applied)
}
}
override fun onSaveInstanceState(savedInstanceState: Bundle) {
super.onSaveInstanceState(savedInstanceState)
savedInstanceState.putDouble("time", binding.time.value)
savedInstanceState.putDouble("amount", binding.amount.value)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
onCreateViewGeneral()
_binding = DialogInsulinBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (config.NSCLIENT) {
binding.recordOnly.isChecked = true
binding.recordOnly.isEnabled = false
}
val maxInsulin = constraintChecker.getMaxBolusAllowed().value()
binding.time.setParams(
savedInstanceState?.getDouble("time")
?: 0.0, -12 * 60.0, 12 * 60.0, 5.0, DecimalFormat("0"), false, binding.okcancel.ok, textWatcher
)
binding.amount.setParams(
savedInstanceState?.getDouble("amount")
?: 0.0, 0.0, maxInsulin, activePlugin.activePump.pumpDescription.bolusStep, DecimalFormatter.pumpSupportedBolusFormat(activePlugin.activePump), false, binding.okcancel.ok, textWatcher
)
val plus05Text = sp.getDouble(rh.gs(R.string.key_insulin_button_increment_1), PLUS1_DEFAULT).toSignedString(activePlugin.activePump)
binding.plus05.text = plus05Text
binding.plus05.contentDescription = rh.gs(R.string.overview_insulin_label) + " " + plus05Text
binding.plus05.setOnClickListener {
binding.amount.value = max(
0.0, binding.amount.value
+ sp.getDouble(rh.gs(R.string.key_insulin_button_increment_1), PLUS1_DEFAULT)
)
validateInputs()
binding.amount.announceValue()
}
val plus10Text = sp.getDouble(rh.gs(R.string.key_insulin_button_increment_2), PLUS2_DEFAULT).toSignedString(activePlugin.activePump)
binding.plus10.text = plus10Text
binding.plus10.contentDescription = rh.gs(R.string.overview_insulin_label) + " " + plus10Text
binding.plus10.setOnClickListener {
binding.amount.value = max(
0.0, binding.amount.value
+ sp.getDouble(rh.gs(R.string.key_insulin_button_increment_2), PLUS2_DEFAULT)
)
validateInputs()
binding.amount.announceValue()
}
val plus20Text = sp.getDouble(rh.gs(R.string.key_insulin_button_increment_3), PLUS3_DEFAULT).toSignedString(activePlugin.activePump)
binding.plus20.text = plus20Text
binding.plus20.contentDescription = rh.gs(R.string.overview_insulin_label) + " " + plus20Text
binding.plus20.setOnClickListener {
binding.amount.value = max(
0.0, binding.amount.value
+ sp.getDouble(rh.gs(R.string.key_insulin_button_increment_3), PLUS3_DEFAULT)
)
validateInputs()
binding.amount.announceValue()
}
binding.timeLayout.visibility = View.GONE
binding.recordOnly.setOnCheckedChangeListener { _, isChecked: Boolean ->
binding.timeLayout.visibility = isChecked.toVisibility()
}
binding.insulinLabel.labelFor = binding.amount.editTextId
binding.timeLabel.labelFor = binding.time.editTextId
}
override fun onDestroyView() {
super.onDestroyView()
disposable.clear()
_binding = null
}
override fun submit(): Boolean {
if (_binding == null) return false
val pumpDescription = activePlugin.activePump.pumpDescription
val insulin = SafeParse.stringToDouble(binding.amount.text)
val insulinAfterConstraints = constraintChecker.applyBolusConstraints(Constraint(insulin)).value()
val actions: LinkedList<String?> = LinkedList()
val units = profileFunction.getUnits()
val unitLabel = if (units == GlucoseUnit.MMOL) rh.gs(R.string.mmol) else rh.gs(R.string.mgdl)
val recordOnlyChecked = binding.recordOnly.isChecked
val eatingSoonChecked = binding.startEatingSoonTt.isChecked
if (insulinAfterConstraints > 0) {
actions.add(rh.gs(R.string.bolus) + ": " + DecimalFormatter.toPumpSupportedBolus(insulinAfterConstraints, activePlugin.activePump, rh).formatColor(context, rh, R.attr.bolusColor))
if (recordOnlyChecked)
actions.add(rh.gs(R.string.bolusrecordedonly).formatColor(context, rh, R.attr.warningColor))
if (abs(insulinAfterConstraints - insulin) > pumpDescription.pumpType.determineCorrectBolusStepSize(insulinAfterConstraints))
actions.add(rh.gs(R.string.bolusconstraintappliedwarn, insulin, insulinAfterConstraints).formatColor(context, rh, R.attr.warningColor))
}
val eatingSoonTTDuration = defaultValueHelper.determineEatingSoonTTDuration()
val eatingSoonTT = defaultValueHelper.determineEatingSoonTT()
if (eatingSoonChecked)
actions.add(
rh.gs(R.string.temp_target_short) + ": " + (DecimalFormatter.to1Decimal(eatingSoonTT) + " " + unitLabel + " (" + rh.gs(R.string.format_mins, eatingSoonTTDuration) + ")")
.formatColor(context, rh, R.attr.tempTargetConfirmation)
)
val timeOffset = binding.time.value.toInt()
val time = dateUtil.now() + T.mins(timeOffset.toLong()).msecs()
if (timeOffset != 0)
actions.add(rh.gs(R.string.time) + ": " + dateUtil.dateAndTimeString(time))
val notes = binding.notesLayout.notes.text.toString()
if (notes.isNotEmpty())
actions.add(rh.gs(R.string.notes_label) + ": " + notes)
if (insulinAfterConstraints > 0 || eatingSoonChecked) {
activity?.let { activity ->
OKDialog.showConfirmation(activity, rh.gs(R.string.bolus), HtmlHelper.fromHtml(Joiner.on("<br/>").join(actions)), {
if (eatingSoonChecked) {
uel.log(
UserEntry.Action.TT, UserEntry.Sources.InsulinDialog,
notes,
ValueWithUnit.TherapyEventTTReason(TemporaryTarget.Reason.EATING_SOON),
ValueWithUnit.fromGlucoseUnit(eatingSoonTT, units.asText),
ValueWithUnit.Minute(eatingSoonTTDuration)
)
disposable += repository.runTransactionForResult(
InsertAndCancelCurrentTemporaryTargetTransaction(
timestamp = System.currentTimeMillis(),
duration = TimeUnit.MINUTES.toMillis(eatingSoonTTDuration.toLong()),
reason = TemporaryTarget.Reason.EATING_SOON,
lowTarget = Profile.toMgdl(eatingSoonTT, profileFunction.getUnits()),
highTarget = Profile.toMgdl(eatingSoonTT, profileFunction.getUnits())
)
).subscribe({ result ->
result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted temp target $it") }
result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated temp target $it") }
}, {
aapsLogger.error(LTag.DATABASE, "Error while saving temporary target", it)
})
}
if (insulinAfterConstraints > 0) {
val detailedBolusInfo = DetailedBolusInfo()
detailedBolusInfo.eventType = DetailedBolusInfo.EventType.CORRECTION_BOLUS
detailedBolusInfo.insulin = insulinAfterConstraints
detailedBolusInfo.context = context
detailedBolusInfo.notes = notes
detailedBolusInfo.timestamp = time
if (recordOnlyChecked) {
uel.log(UserEntry.Action.BOLUS, UserEntry.Sources.InsulinDialog,
rh.gs(R.string.record) + if (notes.isNotEmpty()) ": $notes" else "",
ValueWithUnit.SimpleString(rh.gsNotLocalised(R.string.record)),
ValueWithUnit.Insulin(insulinAfterConstraints),
ValueWithUnit.Minute(timeOffset).takeIf { timeOffset != 0 })
disposable += repository.runTransactionForResult(detailedBolusInfo.insertBolusTransaction())
.subscribe(
{ result -> result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted bolus $it") } },
{ aapsLogger.error(LTag.DATABASE, "Error while saving bolus", it) }
)
if (timeOffset == 0)
bolusTimer.removeAutomationEventBolusReminder()
} else {
uel.log(
UserEntry.Action.BOLUS, UserEntry.Sources.InsulinDialog,
notes,
ValueWithUnit.Insulin(insulinAfterConstraints)
)
commandQueue.bolus(detailedBolusInfo, object : Callback() {
override fun run() {
if (!result.success) {
activityNames.runAlarm(ctx, result.comment, rh.gs(R.string.treatmentdeliveryerror), R.raw.boluserror)
} else {
bolusTimer.removeAutomationEventBolusReminder()
}
}
})
}
}
})
}
} else
activity?.let { activity ->
OKDialog.show(activity, rh.gs(R.string.bolus), rh.gs(R.string.no_action_selected))
}
return true
}
override fun onResume() {
super.onResume()
if (!queryingProtection) {
queryingProtection = true
activity?.let { activity ->
val cancelFail = {
queryingProtection = false
aapsLogger.debug(LTag.APS, "Dialog canceled on resume protection: ${this.javaClass.simpleName}")
ToastUtils.warnToast(ctx, R.string.dialog_canceled)
dismiss()
}
protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, { queryingProtection = false }, cancelFail, cancelFail)
}
}
}
}

View file

@ -58,6 +58,13 @@
<string name="record_pump_site_change">Record pump site change</string> <string name="record_pump_site_change">Record pump site change</string>
<string name="record_insulin_cartridge_change">Record insulin cartridge change</string> <string name="record_insulin_cartridge_change">Record insulin cartridge change</string>
<!-- InsulinDialog -->
<string name="key_insulin_button_increment_1" translatable="false">insulin_button_increment_1</string>
<string name="key_insulin_button_increment_2" translatable="false">insulin_button_increment_2</string>
<string name="key_insulin_button_increment_3" translatable="false">insulin_button_increment_3</string>
<string name="do_not_bolus_record_only">Do not bolus, record only</string>
<string name="bolusrecordedonly">Bolus will be recorded only (not delivered by pump)</string>
<!-- Treatments --> <!-- Treatments -->
<string name="no_records_available">No records available</string> <string name="no_records_available">No records available</string>

View file

@ -206,8 +206,6 @@
<string name="key_dark" translatable="false">dark</string> <string name="key_dark" translatable="false">dark</string>
<string name="key_input_design" translatable="false">input_design</string> <string name="key_input_design" translatable="false">input_design</string>
<string name="key_complication_tap_action" translatable="false">complication_tap_action</string> <string name="key_complication_tap_action" translatable="false">complication_tap_action</string>
<string name="key_insulin_button_increment_1" translatable="false">insulin_button_increment_1</string>
<string name="key_insulin_button_increment_2" translatable="false">insulin_button_increment_2</string>
<string name="key_carbs_button_increment_1" translatable="false">carbs_button_increment_1</string> <string name="key_carbs_button_increment_1" translatable="false">carbs_button_increment_1</string>
<string name="key_carbs_button_increment_2" translatable="false">carbs_button_increment_2</string> <string name="key_carbs_button_increment_2" translatable="false">carbs_button_increment_2</string>
<string name="increment">increment</string> <string name="increment">increment</string>