AndroidAPS/app/src/main/java/info/nightscout/androidaps/dialogs/ProfileSwitchDialog.kt
2022-04-23 18:34:04 +02:00

269 lines
13 KiB
Kotlin

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 android.widget.ArrayAdapter
import com.google.common.base.Joiner
import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R
import info.nightscout.androidaps.data.ProfileSealed
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.entities.TemporaryTarget
import info.nightscout.androidaps.database.entities.UserEntry.Action
import info.nightscout.androidaps.database.entities.UserEntry.Sources
import info.nightscout.androidaps.database.entities.ValueWithUnit
import info.nightscout.androidaps.database.transactions.InsertAndCancelCurrentTemporaryTargetTransaction
import info.nightscout.androidaps.databinding.DialogProfileswitchBinding
import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.utils.DefaultValueHelper
import info.nightscout.androidaps.utils.HardLimits
import info.nightscout.androidaps.utils.HtmlHelper
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.ToastUtils
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.protection.ProtectionCheck
import info.nightscout.androidaps.utils.protection.ProtectionCheck.Protection.BOLUS
import info.nightscout.androidaps.interfaces.ResourceHelper
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import java.text.DecimalFormat
import java.util.*
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlin.collections.ArrayList
class ProfileSwitchDialog : DialogFragmentWithDate() {
@Inject lateinit var rh: ResourceHelper
@Inject lateinit var profileFunction: ProfileFunction
@Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var repository: AppRepository
@Inject lateinit var uel: UserEntryLogger
@Inject lateinit var config: Config
@Inject lateinit var hardLimits: HardLimits
@Inject lateinit var rxBus: RxBus
@Inject lateinit var defaultValueHelper: DefaultValueHelper
@Inject lateinit var ctx: Context
@Inject lateinit var protectionCheck: ProtectionCheck
private var queryingProtection = false
private var profileName: String? = null
private val disposable = CompositeDisposable()
private var _binding: DialogProfileswitchBinding? = 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) {
val isDuration = binding.duration.value > 0
val isLowerPercentage = binding.percentage.value < 100
binding.ttLayout.visibility = (isDuration && isLowerPercentage).toVisibility()
}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
}
override fun onSaveInstanceState(savedInstanceState: Bundle) {
super.onSaveInstanceState(savedInstanceState)
savedInstanceState.putDouble("duration", binding.duration.value)
savedInstanceState.putDouble("percentage", binding.percentage.value)
savedInstanceState.putDouble("timeshift", binding.timeshift.value)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
onCreateViewGeneral()
arguments?.let { bundle ->
profileName = bundle.getString("profileName", null)
}
_binding = DialogProfileswitchBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.duration.setParams(
savedInstanceState?.getDouble("duration")
?: 0.0, 0.0, Constants.MAX_PROFILE_SWITCH_DURATION, 10.0, DecimalFormat("0"), false, binding.okcancel.ok,
textWatcher
)
binding.percentage.setParams(
savedInstanceState?.getDouble("percentage")
?: 100.0, Constants.CPP_MIN_PERCENTAGE.toDouble(), Constants.CPP_MAX_PERCENTAGE.toDouble(), 5.0,
DecimalFormat("0"), false, binding.okcancel.ok, textWatcher
)
binding.timeshift.setParams(
savedInstanceState?.getDouble("timeshift")
?: 0.0, Constants.CPP_MIN_TIMESHIFT.toDouble(), Constants.CPP_MAX_TIMESHIFT.toDouble(), 1.0, DecimalFormat("0"), false, binding.okcancel.ok
)
// profile
context?.let { context ->
val profileStore = activePlugin.activeProfileSource.profile ?: return
val profileListToCheck = profileStore.getProfileList()
val profileList = ArrayList<CharSequence>()
for (profileName in profileListToCheck) {
val profileToCheck = activePlugin.activeProfileSource.profile?.getSpecificProfile(profileName.toString())
if (profileToCheck != null && ProfileSealed.Pure(profileToCheck).isValid("ProfileSwitch", activePlugin.activePump, config, rh, rxBus, hardLimits, false).isValid)
profileList.add(profileName)
}
if (profileList.isEmpty()) {
dismiss()
return
}
binding.profileList.setAdapter(ArrayAdapter(context, R.layout.spinner_centered, profileList))
// set selected to actual profile
if (profileName != null)
binding.profileList.setText(profileName, false)
else {
binding.profileList.setText(profileList[0], false)
for (p in profileList.indices)
if (profileList[p] == profileFunction.getOriginalProfileName())
binding.profileList.setText(profileList[p], false)
}
}
profileFunction.getProfile()?.let { profile ->
if (profile is ProfileSealed.EPS)
if (profile.value.originalPercentage != 100 || profile.value.originalTimeshift != 0L) {
binding.reuselayout.visibility = View.VISIBLE
binding.reusebutton.text = rh.gs(R.string.reuse_profile_pct_hours, profile.value.originalPercentage, T.msecs(profile.value.originalTimeshift).hours().toInt())
binding.reusebutton.setOnClickListener {
binding.percentage.value = profile.value.originalPercentage.toDouble()
binding.timeshift.value = T.msecs(profile.value.originalTimeshift).hours().toDouble()
}
}
}
binding.ttLayout.visibility = View.GONE
binding.durationLabel.labelFor = binding.duration.editTextId
binding.percentageLabel.labelFor = binding.percentage.editTextId
binding.timeshiftLabel.labelFor = binding.timeshift.editTextId
}
override fun onDestroyView() {
super.onDestroyView()
disposable.clear()
_binding = null
}
override fun submit(): Boolean {
if (_binding == null) return false
val profileStore = activePlugin.activeProfileSource.profile
?: return false
val actions: LinkedList<String> = LinkedList()
val duration = binding.duration.value.toInt()
if (duration > 0L)
actions.add(rh.gs(R.string.duration) + ": " + rh.gs(R.string.format_mins, duration))
val profileName = binding.profileList.text.toString()
actions.add(rh.gs(R.string.profile) + ": " + profileName)
val percent = binding.percentage.value.toInt()
if (percent != 100)
actions.add(rh.gs(R.string.percent) + ": " + percent + "%")
val timeShift = binding.timeshift.value.toInt()
if (timeShift != 0)
actions.add(rh.gs(R.string.careportal_newnstreatment_timeshift_label) + ": " + rh.gs(R.string.format_hours, timeShift.toDouble()))
val notes = binding.notesLayout.notes.text.toString()
if (notes.isNotEmpty())
actions.add(rh.gs(R.string.notes_label) + ": " + notes)
if (eventTimeChanged)
actions.add(rh.gs(R.string.time) + ": " + dateUtil.dateAndTimeString(eventTime))
val isTT = binding.duration.value > 0 && binding.percentage.value < 100 && binding.tt.isChecked
val target = defaultValueHelper.determineActivityTT()
val units = profileFunction.getUnits()
if (isTT)
actions.add(rh.gs(R.string.careportal_temporarytarget) + ": " + rh.gs(R.string.activity))
activity?.let { activity ->
val ps = profileFunction.buildProfileSwitch(profileStore, profileName, duration, percent, timeShift, eventTime) ?: return@let
val validity = ProfileSealed.PS(ps).isValid(rh.gs(R.string.careportal_profileswitch), activePlugin.activePump, config, rh, rxBus, hardLimits, false)
if (validity.isValid)
OKDialog.showConfirmation(activity, rh.gs(R.string.careportal_profileswitch), HtmlHelper.fromHtml(Joiner.on("<br/>").join(actions)), {
if (profileFunction.createProfileSwitch(
profileStore,
profileName = profileName,
durationInMinutes = duration,
percentage = percent,
timeShiftInHours = timeShift,
timestamp = eventTime
)
) {
uel.log(Action.PROFILE_SWITCH,
Sources.ProfileSwitchDialog,
notes,
ValueWithUnit.Timestamp(eventTime).takeIf { eventTimeChanged },
ValueWithUnit.SimpleString(profileName),
ValueWithUnit.Percent(percent),
ValueWithUnit.Hour(timeShift).takeIf { timeShift != 0 },
ValueWithUnit.Minute(duration).takeIf { duration != 0 })
if (percent == 90 && duration == 10) sp.putBoolean(R.string.key_objectiveuseprofileswitch, true)
if (isTT) {
disposable += repository.runTransactionForResult(
InsertAndCancelCurrentTemporaryTargetTransaction(
timestamp = eventTime,
duration = TimeUnit.MINUTES.toMillis(duration.toLong()),
reason = TemporaryTarget.Reason.ACTIVITY,
lowTarget = Profile.toMgdl(target, profileFunction.getUnits()),
highTarget = Profile.toMgdl(target, 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)
})
uel.log(
Action.TT, Sources.TTDialog, ValueWithUnit.Timestamp(eventTime).takeIf { eventTimeChanged }, ValueWithUnit.TherapyEventTTReason(
TemporaryTarget.Reason.ACTIVITY
), ValueWithUnit.fromGlucoseUnit(target, units.asText), ValueWithUnit.Minute(duration)
)
}
}
})
else {
OKDialog.show(
activity,
rh.gs(R.string.careportal_profileswitch),
HtmlHelper.fromHtml(Joiner.on("<br/>").join(validity.reasons))
)
return false
}
}
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.name}")
ToastUtils.showToastInUiThread(ctx, R.string.dialog_canceled)
dismiss()
}
protectionCheck.queryProtection(activity, BOLUS, { queryingProtection = false }, cancelFail, cancelFail)
}
}
}
}