= profileStore.getProfileList()
+ profileList.add(0, rh.gs(R.string.active))
+ context?.let { context ->
+ binding.profileList.setAdapter(ArrayAdapter(context, R.layout.spinner_centered, profileList))
+ binding.profileList.setText(profileList[0], false)
+ }
- binding.percentUsed.visibility = (sp.getInt(R.string.key_boluswizard_percentage, 100) != 100 || correctionPercent).toVisibility()
+ val units = profileFunction.getUnits()
+ binding.bgUnits.text = units.asText
+ binding.bgInput.step = if (units == GlucoseUnit.MGDL) 1.0 else 0.1
+
+ // Set BG if not old
+ binding.bgInput.value = iobCobCalculator.ads.actualBg()?.valueToUnits(units) ?: 0.0
+
+ binding.ttCheckbox.isEnabled = tempTarget is ValueWrapper.Existing
+ binding.ttCheckboxIcon.visibility = binding.ttCheckbox.isEnabled.toVisibility()
+ binding.iobInsulin.text = rh.gs(R.string.formatinsulinunits, -bolusIob.iob - basalIob.basaliob)
+
+ calculateInsulin()
+ }
}
+ @SuppressLint("SetTextI18n")
private fun calculateInsulin() {
- val profileStore = activePlugin.activeProfileSource.profile
- if (binding.profile.selectedItem == null || profileStore == null)
- return // not initialized yet
- var profileName = binding.profile.selectedItem.toString()
+ val profileStore = activePlugin.activeProfileSource.profile ?: return // not initialized yet
+ var profileName = binding.profileList.text.toString()
val specificProfile: Profile?
if (profileName == rh.gs(R.string.active)) {
specificProfile = profileFunction.getProfile()
@@ -376,7 +400,7 @@ class WizardDialog : DaggerDialogFragment() {
} else
0.0
val percentageCorrection = if (usePercentage) {
- if (Round.roundTo(calculatedPercentage,1.0) == SafeParse.stringToDouble(binding.correctionInput.text))
+ if (Round.roundTo(calculatedPercentage, 1.0) == SafeParse.stringToDouble(binding.correctionInput.text))
calculatedPercentage
else
SafeParse.stringToDouble(binding.correctionInput.text)
@@ -402,7 +426,8 @@ class WizardDialog : DaggerDialogFragment() {
val carbTime = SafeParse.stringToInt(binding.carbTimeInput.text)
- wizard = BolusWizard(injector).doCalc(specificProfile, profileName, tempTarget, carbsAfterConstraint, cob, bg, correction, sp.getInt(R.string.key_boluswizard_percentage, 100),
+ wizard = BolusWizard(injector).doCalc(
+ specificProfile, profileName, tempTarget, carbsAfterConstraint, cob, bg, correction, sp.getInt(R.string.key_boluswizard_percentage, 100),
binding.bgCheckbox.isChecked,
binding.cobCheckbox.isChecked,
binding.iobCheckbox.isChecked,
@@ -411,17 +436,17 @@ class WizardDialog : DaggerDialogFragment() {
binding.ttCheckbox.isChecked,
binding.bgTrendCheckbox.isChecked,
binding.alarm.isChecked,
- binding.notes.text.toString(),
+ binding.notesLayout.notes.text.toString(),
carbTime,
usePercentage = usePercentage,
totalPercentage = percentageCorrection
)
wizard?.let { wizard ->
- binding.bg.text = String.format(rh.gs(R.string.format_bg_isf), valueToUnitsToString(Profile.toMgdl(bg, profileFunction.getUnits()), profileFunction.getUnits().asText), wizard.sens)
+ binding.bg.text = rh.gs(R.string.format_bg_isf, valueToUnitsToString(Profile.toMgdl(bg, profileFunction.getUnits()), profileFunction.getUnits().asText), wizard.sens)
binding.bgInsulin.text = rh.gs(R.string.formatinsulinunits, wizard.insulinFromBG)
- binding.carbs.text = String.format(rh.gs(R.string.format_carbs_ic), carbs.toDouble(), wizard.ic)
+ binding.carbs.text = rh.gs(R.string.format_carbs_ic, carbs.toDouble(), wizard.ic)
binding.carbsInsulin.text = rh.gs(R.string.formatinsulinunits, wizard.insulinFromCarbs)
binding.iobInsulin.text = rh.gs(R.string.formatinsulinunits, wizard.insulinFromBolusIOB + wizard.insulinFromBasalIOB)
@@ -444,7 +469,7 @@ class WizardDialog : DaggerDialogFragment() {
// COB
if (binding.cobCheckbox.isChecked) {
- binding.cob.text = String.format(rh.gs(R.string.format_cob_ic), cob, wizard.ic)
+ binding.cob.text = rh.gs(R.string.format_cob_ic, cob, wizard.ic)
binding.cobInsulin.text = rh.gs(R.string.formatinsulinunits, wizard.insulinFromCOB)
} else {
binding.cob.text = ""
@@ -452,12 +477,12 @@ class WizardDialog : DaggerDialogFragment() {
}
if (wizard.calculatedTotalInsulin > 0.0 || carbsAfterConstraint > 0.0) {
- val insulinText = if (wizard.calculatedTotalInsulin > 0.0) rh.gs(R.string.formatinsulinunits, wizard.calculatedTotalInsulin).formatColor(rh, R.color.bolus) else ""
- val carbsText = if (carbsAfterConstraint > 0.0) rh.gs(R.string.format_carbs, carbsAfterConstraint).formatColor(rh, R.color.carbs) else ""
+ val insulinText = if (wizard.calculatedTotalInsulin > 0.0) rh.gs(R.string.formatinsulinunits, wizard.calculatedTotalInsulin).formatColor(context, rh, R.attr.bolusColor) else ""
+ val carbsText = if (carbsAfterConstraint > 0.0) rh.gs(R.string.format_carbs, carbsAfterConstraint).formatColor(context, rh, R.attr.carbsColor) else ""
binding.total.text = HtmlHelper.fromHtml(rh.gs(R.string.result_insulin_carbs, insulinText, carbsText))
binding.okcancel.ok.visibility = View.VISIBLE
} else {
- binding.total.text = HtmlHelper.fromHtml(rh.gs(R.string.missing_carbs, wizard.carbsEquivalent.toInt()).formatColor(rh, R.color.carbs))
+ binding.total.text = HtmlHelper.fromHtml(rh.gs(R.string.missing_carbs, wizard.carbsEquivalent.toInt()).formatColor(context, rh, R.attr.carbsColor))
binding.okcancel.ok.visibility = View.INVISIBLE
}
binding.percentUsed.text = rh.gs(R.string.format_percent, wizard.percentageCorrection)
@@ -477,4 +502,20 @@ class WizardDialog : DaggerDialogFragment() {
aapsLogger.debug(e.localizedMessage ?: "")
}
}
+
+ 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)
+ }
+ }
+ }
}
diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/WizardInfoDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/WizardInfoDialog.kt
index 6a6aaf32fa..1ec1c3907d 100644
--- a/app/src/main/java/info/nightscout/androidaps/dialogs/WizardInfoDialog.kt
+++ b/app/src/main/java/info/nightscout/androidaps/dialogs/WizardInfoDialog.kt
@@ -17,7 +17,7 @@ import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.DecimalFormatter
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import org.json.JSONObject
import javax.inject.Inject
@@ -60,7 +60,7 @@ class WizardInfoDialog : DaggerDialogFragment() {
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
- outState.putString("data", data.toJson(true, dateUtil).toString())
+ outState.putString("data", data.toJson(true, dateUtil, profileFunction).toString())
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -69,13 +69,15 @@ class WizardInfoDialog : DaggerDialogFragment() {
binding.close.setOnClickListener { dismiss() }
val units = profileFunction.getUnits()
val bgString = Profile.toUnitsString(data.glucoseValue, data.glucoseValue * Constants.MGDL_TO_MMOLL, units)
+ val isf = Profile.toUnits(data.isf, data.isf * Constants.MGDL_TO_MMOLL, units)
+ val trend = Profile.toUnitsString(data.glucoseTrend * 3, data.glucoseTrend * 3 * Constants.MGDL_TO_MMOLL, units)
// BG
- binding.bg.text = rh.gs(R.string.format_bg_isf, bgString, data.isf)
+ binding.bg.text = rh.gs(R.string.format_bg_isf, bgString, isf)
binding.bgInsulin.text = rh.gs(R.string.formatinsulinunits, data.glucoseInsulin)
binding.bgCheckbox.isChecked = data.wasGlucoseUsed
binding.ttCheckbox.isChecked = data.wasTempTargetUsed
// Trend
- binding.bgTrend.text = DecimalFormatter.to1Decimal(data.glucoseTrend)
+ binding.bgTrend.text = trend
binding.bgTrendInsulin.text = rh.gs(R.string.formatinsulinunits, data.trendInsulin)
binding.bgTrendCheckbox.isChecked = data.wasTrendUsed
// COB
diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventBolusRequested.kt b/app/src/main/java/info/nightscout/androidaps/events/EventBolusRequested.kt
deleted file mode 100644
index a528ef1656..0000000000
--- a/app/src/main/java/info/nightscout/androidaps/events/EventBolusRequested.kt
+++ /dev/null
@@ -1,3 +0,0 @@
-package info.nightscout.androidaps.events
-
-class EventBolusRequested(var amount: Double) : Event()
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventReloadProfileSwitchData.kt b/app/src/main/java/info/nightscout/androidaps/events/EventReloadProfileSwitchData.kt
deleted file mode 100644
index 6f6d848b5e..0000000000
--- a/app/src/main/java/info/nightscout/androidaps/events/EventReloadProfileSwitchData.kt
+++ /dev/null
@@ -1,3 +0,0 @@
-package info.nightscout.androidaps.events
-
-class EventReloadProfileSwitchData : Event()
diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventScale.kt b/app/src/main/java/info/nightscout/androidaps/events/EventScale.kt
new file mode 100644
index 0000000000..830782c3d1
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/events/EventScale.kt
@@ -0,0 +1,3 @@
+package info.nightscout.androidaps.events
+
+class EventScale(val hours: Int) : Event()
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventTreatmentUpdateGui.kt b/app/src/main/java/info/nightscout/androidaps/events/EventTreatmentUpdateGui.kt
deleted file mode 100644
index 4a32b3c659..0000000000
--- a/app/src/main/java/info/nightscout/androidaps/events/EventTreatmentUpdateGui.kt
+++ /dev/null
@@ -1,3 +0,0 @@
-package info.nightscout.androidaps.events
-
-class EventTreatmentUpdateGui : EventUpdateGui()
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/events/EventLoopInvoked.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/events/EventLoopInvoked.kt
deleted file mode 100644
index e90e8cfb88..0000000000
--- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/events/EventLoopInvoked.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package info.nightscout.androidaps.plugins.aps.events
-
-import info.nightscout.androidaps.events.Event
-
-class EventLoopInvoked : Event()
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopFragment.kt
index c7cfe5b969..1970d7c179 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopFragment.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopFragment.kt
@@ -1,26 +1,24 @@
package info.nightscout.androidaps.plugins.aps.loop
import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
+import android.view.*
import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.R
import info.nightscout.androidaps.databinding.LoopFragmentBinding
import info.nightscout.androidaps.interfaces.Constraint
import info.nightscout.androidaps.interfaces.Loop
-import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.androidaps.plugins.aps.loop.events.EventLoopSetLastRunGui
import info.nightscout.androidaps.plugins.aps.loop.events.EventLoopUpdateGui
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.HtmlHelper
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
+import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.sharedPreferences.SP
-import io.reactivex.disposables.CompositeDisposable
-import io.reactivex.rxkotlin.plusAssign
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.kotlin.plusAssign
import javax.inject.Inject
class LoopFragment : DaggerFragment() {
@@ -34,6 +32,8 @@ class LoopFragment : DaggerFragment() {
@Inject lateinit var loop: Loop
@Inject lateinit var dateUtil: DateUtil
+ private val ID_MENU_RUN = 1
+
private var disposable: CompositeDisposable = CompositeDisposable()
private var _binding: LoopFragmentBinding? = null
@@ -42,21 +42,44 @@ class LoopFragment : DaggerFragment() {
// onDestroyView.
private val binding get() = _binding!!
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?): View {
- _binding = LoopFragmentBinding.inflate(inflater, container, false)
- return binding.root
- }
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
+ LoopFragmentBinding.inflate(inflater, container, false).also {
+ _binding = it
+ setHasOptionsMenu(true)
+ }.root
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- binding.run.setOnClickListener {
- binding.lastrun.text = rh.gs(R.string.executing)
- Thread { loop.invoke("Loop button", true) }.start()
+ with(binding.swipeRefresh) {
+ setColorSchemeColors(rh.gac(context, R.attr.colorPrimaryDark), rh.gac(context, R.attr.colorPrimary), rh.gac(context, R.attr.colorSecondary))
+ setOnRefreshListener {
+ binding.lastrun.text = rh.gs(info.nightscout.androidaps.R.string.executing)
+ Thread { loop.invoke("Loop swiperefresh", true) }.start()
+ }
}
}
+ override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+ super.onCreateOptionsMenu(menu, inflater)
+ if (isResumed) {
+ menu.removeItem(ID_MENU_RUN)
+ menu.add(Menu.FIRST, ID_MENU_RUN, 0, rh.gs(R.string.openapsma_run)).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
+ menu.setGroupDividerEnabled(true)
+ }
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean =
+ when (item.itemId) {
+ ID_MENU_RUN -> {
+ binding.lastrun.text = rh.gs(R.string.executing)
+ Thread { loop.invoke("Loop menu", true) }.start()
+ true
+ }
+
+ else -> false
+ }
+
@Synchronized
override fun onResume() {
super.onResume()
@@ -64,16 +87,16 @@ class LoopFragment : DaggerFragment() {
.toObservable(EventLoopUpdateGui::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({
- updateGUI()
- }, fabricPrivacy::logException)
+ updateGUI()
+ }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventLoopSetLastRunGui::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({
- clearGUI()
- binding.lastrun.text = it.text
- }, fabricPrivacy::logException)
+ clearGUI()
+ binding.lastrun.text = it.text
+ }, fabricPrivacy::logException)
updateGUI()
sp.putBoolean(R.string.key_objectiveuseloop, true)
@@ -117,6 +140,7 @@ class LoopFragment : DaggerFragment() {
allConstraints.getMostLimitedReasons(aapsLogger)
} ?: ""
binding.constraints.text = constraints
+ binding.swipeRefresh.isRefreshing = false
}
}
@@ -133,5 +157,6 @@ class LoopFragment : DaggerFragment() {
binding.tbrexecutionTime.text = ""
binding.tbrsetbypump.text = ""
binding.smbsetbypump.text = ""
+ binding.swipeRefresh.isRefreshing = false
}
}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopPlugin.kt
index 09e4896525..4303cb179f 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopPlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopPlugin.kt
@@ -10,11 +10,13 @@ import android.content.Intent
import android.os.SystemClock
import androidx.core.app.NotificationCompat
import dagger.android.HasAndroidInjector
-import info.nightscout.androidaps.*
+import info.nightscout.androidaps.BuildConfig
+import info.nightscout.androidaps.Constants
+import info.nightscout.androidaps.MainActivity
+import info.nightscout.androidaps.R
import info.nightscout.androidaps.activities.ErrorHelperActivity
import info.nightscout.androidaps.annotations.OpenForTesting
import info.nightscout.androidaps.data.DetailedBolusInfo
-import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.data.PumpEnactResult
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.ValueWrapper
@@ -25,13 +27,14 @@ import info.nightscout.androidaps.database.entities.ValueWithUnit
import info.nightscout.androidaps.database.transactions.InsertAndCancelCurrentOfflineEventTransaction
import info.nightscout.androidaps.database.transactions.InsertTherapyEventAnnouncementTransaction
import info.nightscout.androidaps.events.EventAcceptOpenLoopChange
-import info.nightscout.androidaps.events.EventAutosensCalculationFinished
-import info.nightscout.androidaps.events.EventNewBG
+import info.nightscout.androidaps.events.EventMobileToWear
import info.nightscout.androidaps.events.EventTempTargetChange
+import info.nightscout.androidaps.extensions.buildDeviceStatus
+import info.nightscout.androidaps.extensions.convertedToAbsolute
+import info.nightscout.androidaps.extensions.convertedToPercent
+import info.nightscout.androidaps.extensions.plannedRemainingMinutes
import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.interfaces.Loop.LastRun
-import info.nightscout.shared.logging.AAPSLogger
-import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.aps.loop.events.EventLoopSetLastRunGui
import info.nightscout.androidaps.plugins.aps.loop.events.EventLoopUpdateGui
@@ -42,8 +45,6 @@ import info.nightscout.androidaps.plugins.configBuilder.RunningConfiguration
import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification
import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification
import info.nightscout.androidaps.plugins.general.overview.notifications.Notification
-import info.nightscout.androidaps.plugins.general.wear.events.EventWearConfirmAction
-import info.nightscout.androidaps.plugins.general.wear.events.EventWearInitiateAction
import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin
import info.nightscout.androidaps.queue.Callback
import info.nightscout.androidaps.receivers.ReceiverStatusStore
@@ -51,16 +52,14 @@ import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.HardLimits
import info.nightscout.androidaps.utils.T
-import info.nightscout.androidaps.extensions.buildDeviceStatus
-import info.nightscout.androidaps.extensions.convertedToAbsolute
-import info.nightscout.androidaps.extensions.convertedToPercent
-import info.nightscout.androidaps.extensions.plannedRemainingMinutes
-import info.nightscout.androidaps.plugins.aps.events.EventLoopInvoked
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
-import io.reactivex.disposables.CompositeDisposable
-import io.reactivex.rxkotlin.plusAssign
+import info.nightscout.shared.weardata.EventData
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.kotlin.plusAssign
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.abs
@@ -88,20 +87,21 @@ class LoopPlugin @Inject constructor(
private val uel: UserEntryLogger,
private val repository: AppRepository,
private val runningConfiguration: RunningConfiguration
-) : PluginBase(PluginDescription()
- .mainType(PluginType.LOOP)
- .fragmentClass(LoopFragment::class.java.name)
- .pluginIcon(R.drawable.ic_loop_closed_white)
- .pluginName(R.string.loop)
- .shortName(R.string.loop_shortname)
- .preferencesId(R.xml.pref_loop)
- .enableByDefault(config.APS)
- .description(R.string.description_loop),
+) : PluginBase(
+ PluginDescription()
+ .mainType(PluginType.LOOP)
+ .fragmentClass(LoopFragment::class.java.name)
+ .pluginIcon(R.drawable.ic_loop_closed_white)
+ .pluginName(R.string.loop)
+ .shortName(R.string.loop_shortname)
+ .preferencesId(R.xml.pref_loop)
+ .enableByDefault(config.APS)
+ .description(R.string.description_loop),
aapsLogger, rh, injector
), Loop {
private val disposable = CompositeDisposable()
- private var lastBgTriggeredRun: Long = 0
+ override var lastBgTriggeredRun: Long = 0
private var carbsSuggestionsSuspendedUntil: Long = 0
private var prevCarbsreq = 0
override var lastRun: LastRun? = null
@@ -109,39 +109,19 @@ class LoopPlugin @Inject constructor(
override fun onStart() {
createNotificationChannel()
super.onStart()
- disposable.add(rxBus
+ disposable += rxBus
.toObservable(EventTempTargetChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ invoke("EventTempTargetChange", true) }, fabricPrivacy::logException)
- )
- /*
- This method is triggered once autosens calculation has completed, so the LoopPlugin
- has current data to work with. However, autosens calculation can be triggered by multiple
- sources and currently only a new BG should trigger a loop run. Hence we return early if
- the event causing the calculation is not EventNewBg.
-
- */
- disposable.add(rxBus
- .toObservable(EventAutosensCalculationFinished::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({ event: EventAutosensCalculationFinished ->
- // Autosens calculation not triggered by a new BG
- if (event.cause !is EventNewBG) return@subscribe
- val glucoseValue = iobCobCalculator.ads.actualBg() ?: return@subscribe
- // BG outdated
- // already looped with that value
- if (glucoseValue.timestamp <= lastBgTriggeredRun) return@subscribe
- lastBgTriggeredRun = glucoseValue.timestamp
- invoke("AutosenseCalculation for $glucoseValue", true)
- }, fabricPrivacy::logException)
- )
}
private fun createNotificationChannel() {
val mNotificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
- @SuppressLint("WrongConstant") val channel = NotificationChannel(CHANNEL_ID,
+ @SuppressLint("WrongConstant") val channel = NotificationChannel(
CHANNEL_ID,
- NotificationManager.IMPORTANCE_HIGH)
+ CHANNEL_ID,
+ NotificationManager.IMPORTANCE_HIGH
+ )
mNotificationManager.createNotificationChannel(channel)
}
@@ -255,7 +235,7 @@ class LoopPlugin @Inject constructor(
if (apsResult == null) {
rxBus.send(EventLoopSetLastRunGui(rh.gs(R.string.noapsselected)))
return
- } else rxBus.send(EventLoopInvoked())
+ }
if (!isEmptyQueue()) {
aapsLogger.debug(LTag.APS, rh.gs(R.string.pumpbusy))
@@ -296,9 +276,11 @@ class LoopPlugin @Inject constructor(
lastRun.lastTBRRequest = 0
lastRun.lastSMBEnact = 0
lastRun.lastSMBRequest = 0
- buildDeviceStatus(dateUtil, this, iobCobCalculator, profileFunction,
+ buildDeviceStatus(
+ dateUtil, this, iobCobCalculator, profileFunction,
activePlugin.activePump, receiverStatusStore, runningConfiguration,
- BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION)?.also {
+ BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION
+ )?.also {
repository.insert(it)
}
@@ -316,7 +298,11 @@ class LoopPlugin @Inject constructor(
if (closedLoopEnabled.value()) {
if (allowNotification) {
if (resultAfterConstraints.isCarbsRequired
- && resultAfterConstraints.carbsReq >= sp.getInt(R.string.key_smb_enable_carbs_suggestions_threshold, 0) && carbsSuggestionsSuspendedUntil < System.currentTimeMillis() && !treatmentTimeThreshold(-15)) {
+ && resultAfterConstraints.carbsReq >= sp.getInt(
+ R.string.key_smb_enable_carbs_suggestions_threshold,
+ 0
+ ) && carbsSuggestionsSuspendedUntil < System.currentTimeMillis() && !treatmentTimeThreshold(-15)
+ ) {
if (sp.getBoolean(R.string.key_enable_carbs_required_alert_local, true) && !sp.getBoolean(R.string.key_raise_notifications_as_android_notifications, true)) {
val carbReqLocal = Notification(Notification.CARBS_REQUIRED, resultAfterConstraints.carbsRequiredText, Notification.NORMAL)
rxBus.send(EventNewNotification(carbReqLocal))
@@ -353,15 +339,17 @@ class LoopPlugin @Inject constructor(
// mId allows you to update the notification later on.
mNotificationManager.notify(Constants.notificationID, builder.build())
- uel.log(Action.CAREPORTAL, Sources.Loop, rh.gs(R.string.carbsreq, resultAfterConstraints.carbsReq, resultAfterConstraints.carbsReqWithin),
- ValueWithUnit.Gram(resultAfterConstraints.carbsReq),
- ValueWithUnit.Minute(resultAfterConstraints.carbsReqWithin))
+ uel.log(
+ Action.CAREPORTAL, Sources.Loop, rh.gs(R.string.carbsreq, resultAfterConstraints.carbsReq, resultAfterConstraints.carbsReqWithin),
+ ValueWithUnit.Gram(resultAfterConstraints.carbsReq),
+ ValueWithUnit.Minute(resultAfterConstraints.carbsReqWithin)
+ )
rxBus.send(EventNewOpenLoopNotification())
//only send to wear if Native notifications are turned off
if (!sp.getBoolean(R.string.key_raise_notifications_as_android_notifications, true)) {
// Send to Wear
- rxBus.send(EventWearInitiateAction("changeRequest"))
+ sendToWear()
}
}
} else {
@@ -373,7 +361,8 @@ class LoopPlugin @Inject constructor(
}
}
if (resultAfterConstraints.isChangeRequested
- && !commandQueue.bolusInQueue()) {
+ && !commandQueue.bolusInQueue()
+ ) {
val waiting = PumpEnactResult(injector)
waiting.queued = true
if (resultAfterConstraints.tempBasalRequested) lastRun.tbrSetByPump = waiting
@@ -465,14 +454,28 @@ class LoopPlugin @Inject constructor(
rxBus.send(EventNewOpenLoopNotification())
// Send to Wear
- rxBus.send(EventWearInitiateAction("changeRequest"))
+ sendToWear()
}
private fun dismissSuggestion() {
// dismiss notifications
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.cancel(Constants.notificationID)
- rxBus.send(EventWearConfirmAction("cancelChangeRequest"))
+ rxBus.send(EventMobileToWear(EventData.CancelNotification(dateUtil.now())))
+ }
+
+ private fun sendToWear() {
+ lastRun?.let {
+ rxBus.send(
+ EventMobileToWear(
+ EventData.OpenLoopRequest(
+ rh.gs(R.string.openloop_newsuggestion),
+ it.constraintsProcessed.toString(),
+ EventData.OpenLoopRequestConfirmed(dateUtil.now())
+ )
+ )
+ )
+ }
}
override fun acceptChangeRequest() {
@@ -486,9 +489,11 @@ class LoopPlugin @Inject constructor(
lastRun.lastTBRRequest = lastRun.lastAPSRun
lastRun.lastTBREnact = dateUtil.now()
lastRun.lastOpenModeAccept = dateUtil.now()
- buildDeviceStatus(dateUtil, this@LoopPlugin, iobCobCalculator, profileFunction,
+ buildDeviceStatus(
+ dateUtil, this@LoopPlugin, iobCobCalculator, profileFunction,
activePlugin.activePump, receiverStatusStore, runningConfiguration,
- BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION)?.also {
+ BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION
+ )?.also {
repository.insert(it)
}
sp.incInt(R.string.key_ObjectivesmanualEnacts)
@@ -531,8 +536,10 @@ class LoopPlugin @Inject constructor(
commandQueue.cancelTempBasal(false, callback)
} else {
aapsLogger.debug(LTag.APS, "applyAPSRequest: Basal set correctly")
- callback?.result(PumpEnactResult(injector).absolute(request.rate).duration(0)
- .enacted(false).success(true).comment(R.string.basal_set_correctly))?.run()
+ callback?.result(
+ PumpEnactResult(injector).absolute(request.rate).duration(0)
+ .enacted(false).success(true).comment(R.string.basal_set_correctly)
+ )?.run()
}
} else if (request.usePercent && allowPercentage()) {
if (request.percent == 100 && request.duration == 0) {
@@ -542,32 +549,52 @@ class LoopPlugin @Inject constructor(
commandQueue.cancelTempBasal(false, callback)
} else {
aapsLogger.debug(LTag.APS, "applyAPSRequest: Basal set correctly")
- callback?.result(PumpEnactResult(injector).percent(request.percent).duration(0)
- .enacted(false).success(true).comment(R.string.basal_set_correctly))?.run()
+ callback?.result(
+ PumpEnactResult(injector).percent(request.percent).duration(0)
+ .enacted(false).success(true).comment(R.string.basal_set_correctly)
+ )?.run()
}
- } else if (activeTemp != null && activeTemp.plannedRemainingMinutes > 5 && request.duration - activeTemp.plannedRemainingMinutes < 30 && request.percent == activeTemp.convertedToPercent(now, profile)) {
+ } else if (activeTemp != null && activeTemp.plannedRemainingMinutes > 5 && request.duration - activeTemp.plannedRemainingMinutes < 30 && request.percent == activeTemp.convertedToPercent(
+ now,
+ profile
+ )
+ ) {
aapsLogger.debug(LTag.APS, "applyAPSRequest: Temp basal set correctly")
- callback?.result(PumpEnactResult(injector).percent(request.percent)
- .enacted(false).success(true).duration(activeTemp.plannedRemainingMinutes)
- .comment(R.string.let_temp_basal_run))?.run()
+ callback?.result(
+ PumpEnactResult(injector).percent(request.percent)
+ .enacted(false).success(true).duration(activeTemp.plannedRemainingMinutes)
+ .comment(R.string.let_temp_basal_run)
+ )?.run()
} else {
aapsLogger.debug(LTag.APS, "applyAPSRequest: tempBasalPercent()")
- uel.log(Action.TEMP_BASAL, Sources.Loop,
+ uel.log(
+ Action.TEMP_BASAL, Sources.Loop,
ValueWithUnit.Percent(request.percent),
- ValueWithUnit.Minute(request.duration))
+ ValueWithUnit.Minute(request.duration)
+ )
commandQueue.tempBasalPercent(request.percent, request.duration, false, profile, PumpSync.TemporaryBasalType.NORMAL, callback)
}
} else {
- if (activeTemp != null && activeTemp.plannedRemainingMinutes > 5 && request.duration - activeTemp.plannedRemainingMinutes < 30 && abs(request.rate - activeTemp.convertedToAbsolute(now, profile)) < pump.pumpDescription.basalStep) {
+ if (activeTemp != null && activeTemp.plannedRemainingMinutes > 5 && request.duration - activeTemp.plannedRemainingMinutes < 30 && abs(
+ request.rate - activeTemp.convertedToAbsolute(
+ now,
+ profile
+ )
+ ) < pump.pumpDescription.basalStep
+ ) {
aapsLogger.debug(LTag.APS, "applyAPSRequest: Temp basal set correctly")
- callback?.result(PumpEnactResult(injector).absolute(activeTemp.convertedToAbsolute(now, profile))
- .enacted(false).success(true).duration(activeTemp.plannedRemainingMinutes)
- .comment(R.string.let_temp_basal_run))?.run()
+ callback?.result(
+ PumpEnactResult(injector).absolute(activeTemp.convertedToAbsolute(now, profile))
+ .enacted(false).success(true).duration(activeTemp.plannedRemainingMinutes)
+ .comment(R.string.let_temp_basal_run)
+ )?.run()
} else {
aapsLogger.debug(LTag.APS, "applyAPSRequest: setTempBasalAbsolute()")
- uel.log(Action.TEMP_BASAL, Sources.Loop,
+ uel.log(
+ Action.TEMP_BASAL, Sources.Loop,
ValueWithUnit.UnitPerHour(request.rate),
- ValueWithUnit.Minute(request.duration))
+ ValueWithUnit.Minute(request.duration)
+ )
commandQueue.tempBasalAbsolute(request.rate, request.duration, false, profile, PumpSync.TemporaryBasalType.NORMAL, callback)
}
}
@@ -575,15 +602,18 @@ class LoopPlugin @Inject constructor(
private fun applySMBRequest(request: APSResult, callback: Callback?) {
if (!request.bolusRequested()) {
+ aapsLogger.debug(LTag.APS, "No SMB requested")
return
}
val pump = activePlugin.activePump
val lastBolusTime = repository.getLastBolusRecord()?.timestamp ?: 0L
if (lastBolusTime != 0L && lastBolusTime + 3 * 60 * 1000 > System.currentTimeMillis()) {
aapsLogger.debug(LTag.APS, "SMB requested but still in 3 min interval")
- callback?.result(PumpEnactResult(injector)
- .comment(R.string.smb_frequency_exceeded)
- .enacted(false).success(false))?.run()
+ callback?.result(
+ PumpEnactResult(injector)
+ .comment(R.string.smb_frequency_exceeded)
+ .enacted(false).success(false)
+ )?.run()
return
}
if (!pump.isInitialized()) {
@@ -619,11 +649,11 @@ class LoopPlugin @Inject constructor(
val pump = activePlugin.activePump
disposable += repository.runTransactionForResult(InsertAndCancelCurrentOfflineEventTransaction(dateUtil.now(), T.mins(durationInMinutes.toLong()).msecs(), reason))
.subscribe({ result ->
- result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated OfflineEvent $it") }
- result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted OfflineEvent $it") }
- }, {
- aapsLogger.error(LTag.DATABASE, "Error while saving OfflineEvent", it)
- })
+ result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated OfflineEvent $it") }
+ result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted OfflineEvent $it") }
+ }, {
+ aapsLogger.error(LTag.DATABASE, "Error while saving OfflineEvent", it)
+ })
if (pump.pumpDescription.tempBasalStyle == PumpDescription.ABSOLUTE) {
commandQueue.tempBasalAbsolute(0.0, durationInMinutes, true, profile, PumpSync.TemporaryBasalType.EMULATED_PUMP_SUSPEND, object : Callback() {
override fun run() {
@@ -655,11 +685,11 @@ class LoopPlugin @Inject constructor(
override fun suspendLoop(durationInMinutes: Int) {
disposable += repository.runTransactionForResult(InsertAndCancelCurrentOfflineEventTransaction(dateUtil.now(), T.mins(durationInMinutes.toLong()).msecs(), OfflineEvent.Reason.SUSPEND))
.subscribe({ result ->
- result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated OfflineEvent $it") }
- result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted OfflineEvent $it") }
- }, {
- aapsLogger.error(LTag.DATABASE, "Error while saving OfflineEvent", it)
- })
+ result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated OfflineEvent $it") }
+ result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted OfflineEvent $it") }
+ }, {
+ aapsLogger.error(LTag.DATABASE, "Error while saving OfflineEvent", it)
+ })
commandQueue.cancelTempBasal(true, object : Callback() {
override fun run() {
if (!result.success) {
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/DetermineBasalAdapterAMAJS.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/DetermineBasalAdapterAMAJS.kt
index 72196a9914..184b2b2bd3 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/DetermineBasalAdapterAMAJS.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/DetermineBasalAdapterAMAJS.kt
@@ -7,6 +7,7 @@ import info.nightscout.androidaps.data.MealData
import info.nightscout.androidaps.extensions.convertedToAbsolute
import info.nightscout.androidaps.extensions.getPassedDurationToTimeInMinutes
import info.nightscout.androidaps.extensions.plannedRemainingMinutes
+import info.nightscout.androidaps.interfaces.DetermineBasalAdapterInterface
import info.nightscout.androidaps.interfaces.GlucoseUnit
import info.nightscout.androidaps.interfaces.IobCobCalculator
import info.nightscout.androidaps.interfaces.Profile
@@ -14,6 +15,7 @@ import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.plugins.aps.logger.LoggerCallback
+import info.nightscout.androidaps.plugins.aps.loop.APSResult
import info.nightscout.androidaps.plugins.aps.loop.ScriptReader
import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
@@ -30,7 +32,7 @@ import java.nio.charset.StandardCharsets
import javax.inject.Inject
import kotlin.math.min
-class DetermineBasalAdapterAMAJS internal constructor(scriptReader: ScriptReader, injector: HasAndroidInjector) {
+class DetermineBasalAdapterAMAJS internal constructor(scriptReader: ScriptReader, injector: HasAndroidInjector) : DetermineBasalAdapterInterface {
private val injector: HasAndroidInjector
@@ -48,21 +50,15 @@ class DetermineBasalAdapterAMAJS internal constructor(scriptReader: ScriptReader
private var currentTemp = JSONObject()
private var autosensData = JSONObject()
- var currentTempParam: String? = null
- private set
- var iobDataParam: String? = null
- private set
- var glucoseStatusParam: String? = null
- private set
- var profileParam: String? = null
- private set
- var mealDataParam: String? = null
- private set
- var scriptDebug = ""
- private set
+ override var currentTempParam: String? = null
+ override var iobDataParam: String? = null
+ override var glucoseStatusParam: String? = null
+ override var profileParam: String? = null
+ override var mealDataParam: String? = null
+ override var scriptDebug = ""
@Suppress("SpellCheckingInspection")
- operator fun invoke(): DetermineBasalResultAMA? {
+ override operator fun invoke(): APSResult? {
aapsLogger.debug(LTag.APS, ">>> Invoking determine_basal <<<")
aapsLogger.debug(LTag.APS, "Glucose status: " + glucoseStatus.toString().also { glucoseStatusParam = it })
aapsLogger.debug(LTag.APS, "IOB data: " + iobData.toString().also { iobDataParam = it })
@@ -143,18 +139,25 @@ class DetermineBasalAdapterAMAJS internal constructor(scriptReader: ScriptReader
}
@Suppress("SpellCheckingInspection")
- @Throws(JSONException::class) fun setData(profile: Profile,
- maxIob: Double,
- maxBasal: Double,
- minBg: Double,
- maxBg: Double,
- targetBg: Double,
- basalRate: Double,
- iobArray: Array,
- glucoseStatus: GlucoseStatus,
- mealData: MealData,
- autosensDataRatio: Double,
- tempTargetSet: Boolean) {
+ @Throws(JSONException::class)
+ override fun setData(
+ profile: Profile,
+ maxIob: Double,
+ maxBasal: Double,
+ minBg: Double,
+ maxBg: Double,
+ targetBg: Double,
+ basalRate: Double,
+ iobArray: Array,
+ glucoseStatus: GlucoseStatus,
+ mealData: MealData,
+ autosensDataRatio: Double,
+ tempTargetSet: Boolean,
+ microBolusAllowed: Boolean,
+ uamAllowed: Boolean,
+ advancedFiltering: Boolean,
+ isSaveCgmSource: Boolean
+ ) {
this.profile = JSONObject()
this.profile.put("max_iob", maxIob)
this.profile.put("dia", min(profile.dia, 3.0))
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAFragment.kt
index 14deed2bb4..d3fa56acf1 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAFragment.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAFragment.kt
@@ -2,24 +2,22 @@ package info.nightscout.androidaps.plugins.aps.openAPSAMA
import android.os.Bundle
import android.text.TextUtils
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
+import android.view.*
import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.R
import info.nightscout.androidaps.databinding.OpenapsamaFragmentBinding
-import info.nightscout.shared.logging.AAPSLogger
-import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.plugins.aps.events.EventOpenAPSUpdateGui
import info.nightscout.androidaps.plugins.aps.events.EventOpenAPSUpdateResultGui
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.JSONFormatter
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
-import io.reactivex.disposables.CompositeDisposable
-import io.reactivex.rxkotlin.plusAssign
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.shared.logging.LTag
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.kotlin.plusAssign
import org.json.JSONArray
import org.json.JSONException
import javax.inject.Inject
@@ -37,26 +35,53 @@ class OpenAPSAMAFragment : DaggerFragment() {
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var jsonFormatter: JSONFormatter
+ private val ID_MENU_RUN = 1
+
private var _binding: OpenapsamaFragmentBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?): View {
- _binding = OpenapsamaFragmentBinding.inflate(inflater, container, false)
- return binding.root
- }
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
+ OpenapsamaFragmentBinding.inflate(inflater, container, false).also {
+ _binding = it
+ setHasOptionsMenu(true)
+ }.root
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- binding.run.setOnClickListener {
- openAPSAMAPlugin.invoke("OpenAPSAMA button", false)
+ with(binding.swipeRefresh) {
+ setColorSchemeColors(rh.gac(context, R.attr.colorPrimaryDark), rh.gac(context, R.attr.colorPrimary), rh.gac(context, R.attr.colorSecondary))
+ setOnRefreshListener {
+ binding.lastrun.text = rh.gs(info.nightscout.androidaps.R.string.executing)
+ Thread { openAPSAMAPlugin.invoke("OpenAPSAMA swiperefresh", false) }.start()
+ }
+ }
+
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+ super.onCreateOptionsMenu(menu, inflater)
+ if (isResumed) {
+ menu.removeItem(ID_MENU_RUN)
+ menu.add(Menu.FIRST, ID_MENU_RUN, 0, rh.gs(R.string.openapsma_run)).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
+ menu.setGroupDividerEnabled(true)
}
}
+ override fun onOptionsItemSelected(item: MenuItem): Boolean =
+ when (item.itemId) {
+ ID_MENU_RUN -> {
+ binding.lastrun.text = rh.gs(R.string.executing)
+ Thread { openAPSAMAPlugin.invoke("OpenAPSAMA menu", false) }.start()
+ true
+ }
+
+ else -> false
+ }
+
@Synchronized
override fun onResume() {
super.onResume()
@@ -65,14 +90,14 @@ class OpenAPSAMAFragment : DaggerFragment() {
.toObservable(EventOpenAPSUpdateGui::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({
- updateGUI()
- }, fabricPrivacy::logException)
+ updateGUI()
+ }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventOpenAPSUpdateResultGui::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({
- updateResultGUI(it.text)
- }, fabricPrivacy::logException)
+ updateResultGUI(it.text)
+ }, fabricPrivacy::logException)
updateGUI()
}
@@ -96,7 +121,7 @@ class OpenAPSAMAFragment : DaggerFragment() {
binding.result.text = jsonFormatter.format(lastAPSResult.json)
binding.request.text = lastAPSResult.toSpanned()
}
- openAPSAMAPlugin.lastDetermineBasalAdapterAMAJS?.let { determineBasalAdapterAMAJS ->
+ openAPSAMAPlugin.lastDetermineBasalAdapter?.let { determineBasalAdapterAMAJS ->
binding.glucosestatus.text = jsonFormatter.format(determineBasalAdapterAMAJS.glucoseStatusParam)
binding.currenttemp.text = jsonFormatter.format(determineBasalAdapterAMAJS.currentTempParam)
try {
@@ -118,6 +143,7 @@ class OpenAPSAMAFragment : DaggerFragment() {
openAPSAMAPlugin.lastAutosensResult.let {
binding.autosensdata.text = jsonFormatter.format(it.json())
}
+ binding.swipeRefresh.isRefreshing = false
}
private fun updateResultGUI(text: String) {
@@ -131,5 +157,6 @@ class OpenAPSAMAFragment : DaggerFragment() {
binding.scriptdebugdata.text = ""
binding.request.text = ""
binding.lastrun.text = ""
+ binding.swipeRefresh.isRefreshing = false
}
}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAPlugin.kt
index 5c883413ed..3e78943b98 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAPlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAPlugin.kt
@@ -23,7 +23,7 @@ import info.nightscout.androidaps.utils.HardLimits
import info.nightscout.androidaps.utils.Profiler
import info.nightscout.androidaps.utils.Round
import info.nightscout.androidaps.extensions.target
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import org.json.JSONException
import javax.inject.Inject
import javax.inject.Singleton
@@ -60,8 +60,8 @@ class OpenAPSAMAPlugin @Inject constructor(
// last values
override var lastAPSRun: Long = 0
override var lastAPSResult: DetermineBasalResultAMA? = null
- var lastDetermineBasalAdapterAMAJS: DetermineBasalAdapterAMAJS? = null
- var lastAutosensResult: AutosensResult = AutosensResult()
+ override var lastDetermineBasalAdapter: DetermineBasalAdapterInterface? = null
+ override var lastAutosensResult: AutosensResult = AutosensResult()
override fun specialEnableCondition(): Boolean {
return try {
@@ -158,7 +158,7 @@ class OpenAPSAMAPlugin @Inject constructor(
// Fix bug determine basal
if (determineBasalResultAMA == null) {
aapsLogger.error(LTag.APS, "SMB calculation returned null")
- lastDetermineBasalAdapterAMAJS = null
+ lastDetermineBasalAdapter = null
lastAPSResult = null
lastAPSRun = 0
} else {
@@ -167,8 +167,8 @@ class OpenAPSAMAPlugin @Inject constructor(
val now = System.currentTimeMillis()
determineBasalResultAMA.json?.put("timestamp", dateUtil.toISOString(now))
determineBasalResultAMA.inputConstraints = inputConstraints
- lastDetermineBasalAdapterAMAJS = determineBasalAdapterAMAJS
- lastAPSResult = determineBasalResultAMA
+ lastDetermineBasalAdapter = determineBasalAdapterAMAJS
+ lastAPSResult = determineBasalResultAMA as DetermineBasalResultAMA
lastAPSRun = now
}
rxBus.send(EventOpenAPSUpdateGui())
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalAdapterSMBJS.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalAdapterSMBJS.kt
index c498800cec..c23f14d54a 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalAdapterSMBJS.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalAdapterSMBJS.kt
@@ -7,19 +7,16 @@ import info.nightscout.androidaps.data.MealData
import info.nightscout.androidaps.extensions.convertedToAbsolute
import info.nightscout.androidaps.extensions.getPassedDurationToTimeInMinutes
import info.nightscout.androidaps.extensions.plannedRemainingMinutes
-import info.nightscout.androidaps.interfaces.ActivePlugin
-import info.nightscout.androidaps.interfaces.GlucoseUnit
-import info.nightscout.androidaps.interfaces.IobCobCalculator
-import info.nightscout.androidaps.interfaces.Profile
-import info.nightscout.androidaps.interfaces.ProfileFunction
+import info.nightscout.androidaps.interfaces.*
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.plugins.aps.logger.LoggerCallback
+import info.nightscout.androidaps.plugins.aps.loop.APSResult
import info.nightscout.androidaps.plugins.aps.loop.ScriptReader
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus
import info.nightscout.shared.SafeParse
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONArray
import org.json.JSONException
@@ -31,7 +28,7 @@ import java.lang.reflect.InvocationTargetException
import java.nio.charset.StandardCharsets
import javax.inject.Inject
-class DetermineBasalAdapterSMBJS internal constructor(private val scriptReader: ScriptReader, private val injector: HasAndroidInjector) {
+class DetermineBasalAdapterSMBJS internal constructor(private val scriptReader: ScriptReader, private val injector: HasAndroidInjector) : DetermineBasalAdapterInterface {
@Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var constraintChecker: ConstraintChecker
@@ -51,21 +48,16 @@ class DetermineBasalAdapterSMBJS internal constructor(private val scriptReader:
private var smbAlwaysAllowed = false
private var currentTime: Long = 0
private var saveCgmSource = false
- var currentTempParam: String? = null
- private set
- var iobDataParam: String? = null
- private set
- var glucoseStatusParam: String? = null
- private set
- var profileParam: String? = null
- private set
- var mealDataParam: String? = null
- private set
- var scriptDebug = ""
- private set
+
+ override var currentTempParam: String? = null
+ override var iobDataParam: String? = null
+ override var glucoseStatusParam: String? = null
+ override var profileParam: String? = null
+ override var mealDataParam: String? = null
+ override var scriptDebug = ""
@Suppress("SpellCheckingInspection")
- operator fun invoke(): DetermineBasalResultSMB? {
+ override operator fun invoke(): APSResult? {
aapsLogger.debug(LTag.APS, ">>> Invoking determine_basal <<<")
aapsLogger.debug(LTag.APS, "Glucose status: " + mGlucoseStatus.toString().also { glucoseStatusParam = it })
aapsLogger.debug(LTag.APS, "IOB data: " + iobData.toString().also { iobDataParam = it })
@@ -155,22 +147,24 @@ class DetermineBasalAdapterSMBJS internal constructor(private val scriptReader:
return determineBasalResultSMB
}
- @Suppress("SpellCheckingInspection") fun setData(profile: Profile,
- maxIob: Double,
- maxBasal: Double,
- minBg: Double,
- maxBg: Double,
- targetBg: Double,
- basalRate: Double,
- iobArray: Array,
- glucoseStatus: GlucoseStatus,
- mealData: MealData,
- autosensDataRatio: Double,
- tempTargetSet: Boolean,
- microBolusAllowed: Boolean,
- uamAllowed: Boolean,
- advancedFiltering: Boolean,
- isSaveCgmSource: Boolean
+ @Suppress("SpellCheckingInspection")
+ override fun setData(
+ profile: Profile,
+ maxIob: Double,
+ maxBasal: Double,
+ minBg: Double,
+ maxBg: Double,
+ targetBg: Double,
+ basalRate: Double,
+ iobArray: Array,
+ glucoseStatus: GlucoseStatus,
+ mealData: MealData,
+ autosensDataRatio: Double,
+ tempTargetSet: Boolean,
+ microBolusAllowed: Boolean,
+ uamAllowed: Boolean,
+ advancedFiltering: Boolean,
+ isSaveCgmSource: Boolean
) {
val pump = activePlugin.activePump
val pumpBolusStep = pump.pumpDescription.bolusStep
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalResultSMB.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalResultSMB.kt
index 03d9c714cc..d45fbfe26c 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalResultSMB.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalResultSMB.kt
@@ -10,6 +10,7 @@ class DetermineBasalResultSMB private constructor(injector: HasAndroidInjector)
private var eventualBG = 0.0
private var snoozeBG = 0.0
+ var variableSens: Double? = null
internal constructor(injector: HasAndroidInjector, result: JSONObject) : this(injector) {
date = dateUtil.now()
@@ -50,6 +51,7 @@ class DetermineBasalResultSMB private constructor(injector: HasAndroidInjector)
aapsLogger.error(LTag.APS, "Error parsing 'deliverAt' date: $date", e)
}
}
+ if (result.has("variable_sens")) variableSens = result.getDouble("variable_sens")
} catch (e: JSONException) {
aapsLogger.error(LTag.APS, "Error parsing determine-basal result JSON", e)
}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBFragment.kt
index b93170d2ac..db729031a0 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBFragment.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBFragment.kt
@@ -3,24 +3,23 @@ package info.nightscout.androidaps.plugins.aps.openAPSSMB
import android.annotation.SuppressLint
import android.os.Bundle
import android.text.TextUtils
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
+import android.view.*
import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.R
import info.nightscout.androidaps.databinding.OpenapsamaFragmentBinding
-import info.nightscout.shared.logging.AAPSLogger
-import info.nightscout.shared.logging.LTag
+import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.plugins.aps.events.EventOpenAPSUpdateGui
import info.nightscout.androidaps.plugins.aps.events.EventOpenAPSUpdateResultGui
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.JSONFormatter
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
-import io.reactivex.disposables.CompositeDisposable
-import io.reactivex.rxkotlin.plusAssign
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.shared.logging.LTag
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.kotlin.plusAssign
import org.json.JSONArray
import org.json.JSONException
import javax.inject.Inject
@@ -34,9 +33,12 @@ class OpenAPSSMBFragment : DaggerFragment() {
@Inject lateinit var rxBus: RxBus
@Inject lateinit var rh: ResourceHelper
@Inject lateinit var fabricPrivacy: FabricPrivacy
- @Inject lateinit var openAPSSMBPlugin: OpenAPSSMBPlugin
+ @Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var jsonFormatter: JSONFormatter
+ private lateinit var refreshDialog: Runnable
+
+ private val ID_MENU_RUN = 1
private var _binding: OpenapsamaFragmentBinding? = null
@@ -44,20 +46,44 @@ class OpenAPSSMBFragment : DaggerFragment() {
// onDestroyView.
private val binding get() = _binding!!
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?): View {
- _binding = OpenapsamaFragmentBinding.inflate(inflater, container, false)
- return binding.root
- }
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
+ OpenapsamaFragmentBinding.inflate(inflater, container, false).also {
+ _binding = it
+ setHasOptionsMenu(true)
+ }.root
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- binding.run.setOnClickListener {
- openAPSSMBPlugin.invoke("OpenAPSSMB button", false)
+ with(binding.swipeRefresh) {
+ setColorSchemeColors(rh.gac(context, R.attr.colorPrimaryDark), rh.gac(context, R.attr.colorPrimary), rh.gac(context, R.attr.colorSecondary))
+ setOnRefreshListener {
+ binding.lastrun.text = rh.gs(info.nightscout.androidaps.R.string.executing)
+ Thread { activePlugin.activeAPS.invoke("OpenAPSSMB swiperefresh", false) }.start()
+ }
}
}
+ override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+ super.onCreateOptionsMenu(menu, inflater)
+ if (isResumed) {
+ menu.removeItem(ID_MENU_RUN)
+ menu.add(Menu.FIRST, ID_MENU_RUN, 0, rh.gs(R.string.openapsma_run)).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
+ menu.setGroupDividerEnabled(true)
+ }
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean =
+ when (item.itemId) {
+ ID_MENU_RUN -> {
+ binding.lastrun.text = rh.gs(R.string.executing)
+ Thread { activePlugin.activeAPS.invoke("OpenAPSSMB menu", false) }.start()
+ true
+ }
+
+ else -> false
+ }
+
@Synchronized
override fun onResume() {
super.onResume()
@@ -65,14 +91,14 @@ class OpenAPSSMBFragment : DaggerFragment() {
.toObservable(EventOpenAPSUpdateGui::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({
- updateGUI()
- }, fabricPrivacy::logException)
+ updateGUI()
+ }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventOpenAPSUpdateResultGui::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({
- updateResultGUI(it.text)
- }, fabricPrivacy::logException)
+ updateResultGUI(it.text)
+ }, fabricPrivacy::logException)
updateGUI()
}
@@ -92,11 +118,12 @@ class OpenAPSSMBFragment : DaggerFragment() {
@Synchronized
fun updateGUI() {
if (_binding == null) return
+ val openAPSSMBPlugin = activePlugin.activeAPS
openAPSSMBPlugin.lastAPSResult?.let { lastAPSResult ->
binding.result.text = jsonFormatter.format(lastAPSResult.json)
binding.request.text = lastAPSResult.toSpanned()
}
- openAPSSMBPlugin.lastDetermineBasalAdapterSMBJS?.let { determineBasalAdapterSMBJS ->
+ openAPSSMBPlugin.lastDetermineBasalAdapter?.let { determineBasalAdapterSMBJS ->
binding.glucosestatus.text = jsonFormatter.format(determineBasalAdapterSMBJS.glucoseStatusParam)
binding.currenttemp.text = jsonFormatter.format(determineBasalAdapterSMBJS.currentTempParam)
try {
@@ -121,6 +148,7 @@ class OpenAPSSMBFragment : DaggerFragment() {
openAPSSMBPlugin.lastAutosensResult.let {
binding.autosensdata.text = jsonFormatter.format(it.json())
}
+ binding.swipeRefresh.isRefreshing = false
}
@Synchronized
@@ -136,5 +164,6 @@ class OpenAPSSMBFragment : DaggerFragment() {
binding.scriptdebugdata.text = ""
binding.request.text = ""
binding.lastrun.text = ""
+ binding.swipeRefresh.isRefreshing = false
}
}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBPlugin.kt
index 09e79999f4..156cceb4ce 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBPlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBPlugin.kt
@@ -10,8 +10,6 @@ import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.ValueWrapper
import info.nightscout.androidaps.extensions.target
import info.nightscout.androidaps.interfaces.*
-import info.nightscout.shared.logging.AAPSLogger
-import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.plugins.aps.events.EventOpenAPSUpdateGui
import info.nightscout.androidaps.plugins.aps.events.EventOpenAPSUpdateResultGui
import info.nightscout.androidaps.plugins.aps.loop.ScriptReader
@@ -23,7 +21,9 @@ import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.HardLimits
import info.nightscout.androidaps.utils.Profiler
import info.nightscout.androidaps.utils.Round
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import javax.inject.Inject
import javax.inject.Singleton
@@ -37,7 +37,7 @@ class OpenAPSSMBPlugin @Inject constructor(
private val constraintChecker: ConstraintChecker,
rh: ResourceHelper,
private val profileFunction: ProfileFunction,
- private val context: Context,
+ val context: Context,
private val activePlugin: ActivePlugin,
private val iobCobCalculator: IobCobCalculator,
private val hardLimits: HardLimits,
@@ -46,23 +46,24 @@ class OpenAPSSMBPlugin @Inject constructor(
private val dateUtil: DateUtil,
private val repository: AppRepository,
private val glucoseStatusProvider: GlucoseStatusProvider
-) : PluginBase(PluginDescription()
- .mainType(PluginType.APS)
- .fragmentClass(OpenAPSSMBFragment::class.java.name)
- .pluginIcon(R.drawable.ic_generic_icon)
- .pluginName(R.string.openapssmb)
- .shortName(R.string.smb_shortname)
- .preferencesId(R.xml.pref_openapssmb)
- .description(R.string.description_smb)
- .setDefault(),
+) : PluginBase(
+ PluginDescription()
+ .mainType(PluginType.APS)
+ .fragmentClass(OpenAPSSMBFragment::class.java.name)
+ .pluginIcon(R.drawable.ic_generic_icon)
+ .pluginName(R.string.openapssmb)
+ .shortName(R.string.smb_shortname)
+ .preferencesId(R.xml.pref_openapssmb)
+ .description(R.string.description_smb)
+ .setDefault(),
aapsLogger, rh, injector
), APS, Constraints {
// last values
override var lastAPSRun: Long = 0
override var lastAPSResult: DetermineBasalResultSMB? = null
- var lastDetermineBasalAdapterSMBJS: DetermineBasalAdapterSMBJS? = null
- var lastAutosensResult = AutosensResult()
+ override var lastDetermineBasalAdapter: DetermineBasalAdapterInterface? = null
+ override var lastAutosensResult = AutosensResult()
override fun specialEnableCondition(): Boolean {
return try {
@@ -120,15 +121,34 @@ class OpenAPSSMBPlugin @Inject constructor(
}.value()
var minBg = hardLimits.verifyHardLimits(Round.roundTo(profile.getTargetLowMgdl(), 0.1), R.string.profile_low_target, HardLimits.VERY_HARD_LIMIT_MIN_BG[0], HardLimits.VERY_HARD_LIMIT_MIN_BG[1])
- var maxBg = hardLimits.verifyHardLimits(Round.roundTo(profile.getTargetHighMgdl(), 0.1), R.string.profile_high_target, HardLimits.VERY_HARD_LIMIT_MAX_BG[0], HardLimits.VERY_HARD_LIMIT_MAX_BG[1])
+ var maxBg =
+ hardLimits.verifyHardLimits(Round.roundTo(profile.getTargetHighMgdl(), 0.1), R.string.profile_high_target, HardLimits.VERY_HARD_LIMIT_MAX_BG[0], HardLimits.VERY_HARD_LIMIT_MAX_BG[1])
var targetBg = hardLimits.verifyHardLimits(profile.getTargetMgdl(), R.string.temp_target_value, HardLimits.VERY_HARD_LIMIT_TARGET_BG[0], HardLimits.VERY_HARD_LIMIT_TARGET_BG[1])
var isTempTarget = false
val tempTarget = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet()
if (tempTarget is ValueWrapper.Existing) {
isTempTarget = true
- minBg = hardLimits.verifyHardLimits(tempTarget.value.lowTarget, R.string.temp_target_low_target, HardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[0].toDouble(), HardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[1].toDouble())
- maxBg = hardLimits.verifyHardLimits(tempTarget.value.highTarget, R.string.temp_target_high_target, HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[0].toDouble(), HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[1].toDouble())
- targetBg = hardLimits.verifyHardLimits(tempTarget.value.target(), R.string.temp_target_value, HardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[0].toDouble(), HardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[1].toDouble())
+ minBg =
+ hardLimits.verifyHardLimits(
+ tempTarget.value.lowTarget,
+ R.string.temp_target_low_target,
+ HardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[0].toDouble(),
+ HardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[1].toDouble()
+ )
+ maxBg =
+ hardLimits.verifyHardLimits(
+ tempTarget.value.highTarget,
+ R.string.temp_target_high_target,
+ HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[0].toDouble(),
+ HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[1].toDouble()
+ )
+ targetBg =
+ hardLimits.verifyHardLimits(
+ tempTarget.value.target(),
+ R.string.temp_target_value,
+ HardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[0].toDouble(),
+ HardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[1].toDouble()
+ )
}
if (!hardLimits.checkHardLimits(profile.dia, R.string.profile_dia, hardLimits.minDia(), hardLimits.maxDia())) return
if (!hardLimits.checkHardLimits(profile.getIcTimeFromMidnight(Profile.secondsFromMidnight()), R.string.profile_carbs_ratio_value, hardLimits.minIC(), hardLimits.maxIC())) return
@@ -165,8 +185,9 @@ class OpenAPSSMBPlugin @Inject constructor(
profiler.log(LTag.APS, "SMB data gathering", start)
start = System.currentTimeMillis()
- DetermineBasalAdapterSMBJS(ScriptReader(context), injector).also { determineBasalAdapterSMBJS ->
- determineBasalAdapterSMBJS.setData(profile, maxIob, maxBasal, minBg, maxBg, targetBg,
+ provideDetermineBasalAdapter().also { determineBasalAdapterSMBJS ->
+ determineBasalAdapterSMBJS.setData(
+ profile, maxIob, maxBasal, minBg, maxBg, targetBg,
activePlugin.activePump.baseBasalRate,
iobArray,
glucoseStatus,
@@ -176,24 +197,26 @@ class OpenAPSSMBPlugin @Inject constructor(
smbAllowed.value(),
uam.value(),
advancedFiltering.value(),
- activePlugin.activeBgSource.javaClass.simpleName == "DexcomPlugin")
+ activePlugin.activeBgSource.javaClass.simpleName == "DexcomPlugin"
+ )
val now = System.currentTimeMillis()
val determineBasalResultSMB = determineBasalAdapterSMBJS.invoke()
profiler.log(LTag.APS, "SMB calculation", start)
if (determineBasalResultSMB == null) {
aapsLogger.error(LTag.APS, "SMB calculation returned null")
- lastDetermineBasalAdapterSMBJS = null
+ lastDetermineBasalAdapter = null
lastAPSResult = null
lastAPSRun = 0
} else {
// TODO still needed with oref1?
// Fix bug determine basal
- if (determineBasalResultSMB.rate == 0.0 && determineBasalResultSMB.duration == 0 && iobCobCalculator.getTempBasalIncludingConvertedExtended(dateUtil.now()) == null) determineBasalResultSMB.tempBasalRequested = false
+ if (determineBasalResultSMB.rate == 0.0 && determineBasalResultSMB.duration == 0 && iobCobCalculator.getTempBasalIncludingConvertedExtended(dateUtil.now()) == null) determineBasalResultSMB.tempBasalRequested =
+ false
determineBasalResultSMB.iob = iobArray[0]
determineBasalResultSMB.json?.put("timestamp", dateUtil.toISOString(now))
determineBasalResultSMB.inputConstraints = inputConstraints
- lastDetermineBasalAdapterSMBJS = determineBasalAdapterSMBJS
- lastAPSResult = determineBasalResultSMB
+ lastDetermineBasalAdapter = determineBasalAdapterSMBJS
+ lastAPSResult = determineBasalResultSMB as DetermineBasalResultSMB
lastAPSRun = now
}
}
@@ -204,4 +227,6 @@ class OpenAPSSMBPlugin @Inject constructor(
value.set(aapsLogger, false)
return value
}
+
+ fun provideDetermineBasalAdapter(): DetermineBasalAdapterInterface = DetermineBasalAdapterSMBJS(ScriptReader(context), injector)
}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMBDynamicISF/DetermineBasalAdapterSMBDynamicISFJS.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMBDynamicISF/DetermineBasalAdapterSMBDynamicISFJS.kt
new file mode 100644
index 0000000000..d5a33606eb
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMBDynamicISF/DetermineBasalAdapterSMBDynamicISFJS.kt
@@ -0,0 +1,313 @@
+package info.nightscout.androidaps.plugins.aps.openAPSSMBDynamicISF
+
+import dagger.android.HasAndroidInjector
+import info.nightscout.androidaps.R
+import info.nightscout.androidaps.data.IobTotal
+import info.nightscout.androidaps.data.MealData
+import info.nightscout.androidaps.database.AppRepository
+import info.nightscout.androidaps.extensions.convertedToAbsolute
+import info.nightscout.androidaps.extensions.getPassedDurationToTimeInMinutes
+import info.nightscout.androidaps.extensions.plannedRemainingMinutes
+import info.nightscout.androidaps.interfaces.ActivePlugin
+import info.nightscout.androidaps.interfaces.GlucoseUnit
+import info.nightscout.androidaps.interfaces.IobCobCalculator
+import info.nightscout.androidaps.interfaces.Profile
+import info.nightscout.androidaps.interfaces.ProfileFunction
+import info.nightscout.androidaps.plugins.aps.logger.LoggerCallback
+import info.nightscout.androidaps.plugins.aps.loop.ScriptReader
+import info.nightscout.androidaps.interfaces.DetermineBasalAdapterInterface
+import info.nightscout.androidaps.plugins.aps.openAPSSMB.DetermineBasalResultSMB
+import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults
+import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
+import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus
+import info.nightscout.androidaps.utils.DateUtil
+import info.nightscout.androidaps.interfaces.ResourceHelper
+import info.nightscout.androidaps.utils.stats.TddCalculator
+import info.nightscout.shared.SafeParse
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.shared.logging.LTag
+import info.nightscout.shared.sharedPreferences.SP
+import org.json.JSONArray
+import org.json.JSONException
+import org.json.JSONObject
+import org.mozilla.javascript.*
+import org.mozilla.javascript.Function
+import java.io.IOException
+import java.lang.reflect.InvocationTargetException
+import java.nio.charset.StandardCharsets
+import javax.inject.Inject
+
+class DetermineBasalAdapterSMBDynamicISFJS internal constructor(private val scriptReader: ScriptReader, private val injector: HasAndroidInjector) : DetermineBasalAdapterInterface {
+
+ @Inject lateinit var aapsLogger: AAPSLogger
+ @Inject lateinit var constraintChecker: ConstraintChecker
+ @Inject lateinit var sp: SP
+ @Inject lateinit var rh: ResourceHelper
+ @Inject lateinit var profileFunction: ProfileFunction
+ @Inject lateinit var iobCobCalculator: IobCobCalculator
+ @Inject lateinit var activePlugin: ActivePlugin
+ @Inject lateinit var repository: AppRepository
+ @Inject lateinit var dateUtil: DateUtil
+ @Inject lateinit var tddCalculator: TddCalculator
+
+ private var profile = JSONObject()
+ private var mGlucoseStatus = JSONObject()
+ private var iobData: JSONArray? = null
+ private var mealData = JSONObject()
+ private var currentTemp = JSONObject()
+ private var autosensData = JSONObject()
+ private var microBolusAllowed = false
+ private var smbAlwaysAllowed = false
+ private var currentTime: Long = 0
+ private var saveCgmSource = false
+
+ override var currentTempParam: String? = null
+ override var iobDataParam: String? = null
+ override var glucoseStatusParam: String? = null
+ override var profileParam: String? = null
+ override var mealDataParam: String? = null
+ override var scriptDebug = ""
+
+ @Suppress("SpellCheckingInspection")
+ override operator fun invoke(): DetermineBasalResultSMB? {
+ aapsLogger.debug(LTag.APS, ">>> Invoking determine_basal <<<")
+ aapsLogger.debug(LTag.APS, "Glucose status: " + mGlucoseStatus.toString().also { glucoseStatusParam = it })
+ aapsLogger.debug(LTag.APS, "IOB data: " + iobData.toString().also { iobDataParam = it })
+ aapsLogger.debug(LTag.APS, "Current temp: " + currentTemp.toString().also { currentTempParam = it })
+ aapsLogger.debug(LTag.APS, "Profile: " + profile.toString().also { profileParam = it })
+ aapsLogger.debug(LTag.APS, "Meal data: " + mealData.toString().also { mealDataParam = it })
+ aapsLogger.debug(LTag.APS, "Autosens data: $autosensData")
+ aapsLogger.debug(LTag.APS, "Reservoir data: " + "undefined")
+ aapsLogger.debug(LTag.APS, "MicroBolusAllowed: $microBolusAllowed")
+ aapsLogger.debug(LTag.APS, "SMBAlwaysAllowed: $smbAlwaysAllowed")
+ aapsLogger.debug(LTag.APS, "CurrentTime: $currentTime")
+ aapsLogger.debug(LTag.APS, "isSaveCgmSource: $saveCgmSource")
+ var determineBasalResultSMB: DetermineBasalResultSMB? = null
+ val rhino = Context.enter()
+ val scope: Scriptable = rhino.initStandardObjects()
+ // Turn off optimization to make Rhino Android compatible
+ rhino.optimizationLevel = -1
+ try {
+
+ //register logger callback for console.log and console.error
+ ScriptableObject.defineClass(scope, LoggerCallback::class.java)
+ val myLogger = rhino.newObject(scope, "LoggerCallback", null)
+ scope.put("console2", scope, myLogger)
+ rhino.evaluateString(scope, readFile("OpenAPSAMA/loggerhelper.js"), "JavaScript", 0, null)
+
+ //set module parent
+ rhino.evaluateString(scope, "var module = {\"parent\":Boolean(1)};", "JavaScript", 0, null)
+ rhino.evaluateString(scope, "var round_basal = function round_basal(basal, profile) { return basal; };", "JavaScript", 0, null)
+ rhino.evaluateString(scope, "require = function() {return round_basal;};", "JavaScript", 0, null)
+
+ //generate functions "determine_basal" and "setTempBasal"
+ rhino.evaluateString(scope, readFile("OpenAPSSMBDynamicISF/determine-basal.js"), "JavaScript", 0, null)
+ rhino.evaluateString(scope, readFile("OpenAPSSMB/basal-set-temp.js"), "setTempBasal.js", 0, null)
+ val determineBasalObj = scope["determine_basal", scope]
+ val setTempBasalFunctionsObj = scope["tempBasalFunctions", scope]
+
+ //call determine-basal
+ if (determineBasalObj is Function && setTempBasalFunctionsObj is NativeObject) {
+
+ //prepare parameters
+ val params = arrayOf(
+ makeParam(mGlucoseStatus, rhino, scope),
+ makeParam(currentTemp, rhino, scope),
+ makeParamArray(iobData, rhino, scope),
+ makeParam(profile, rhino, scope),
+ makeParam(autosensData, rhino, scope),
+ makeParam(mealData, rhino, scope),
+ setTempBasalFunctionsObj,
+ java.lang.Boolean.valueOf(microBolusAllowed),
+ makeParam(null, rhino, scope), // reservoir data as undefined
+ java.lang.Long.valueOf(currentTime),
+ java.lang.Boolean.valueOf(saveCgmSource)
+ )
+ val jsResult = determineBasalObj.call(rhino, scope, scope, params) as NativeObject
+ scriptDebug = LoggerCallback.scriptDebug
+
+ // Parse the jsResult object to a JSON-String
+ val result = NativeJSON.stringify(rhino, scope, jsResult, null, null).toString()
+ aapsLogger.debug(LTag.APS, "Result: $result")
+ try {
+ val resultJson = JSONObject(result)
+ determineBasalResultSMB = DetermineBasalResultSMB(injector, resultJson)
+ } catch (e: JSONException) {
+ aapsLogger.error(LTag.APS, "Unhandled exception", e)
+ }
+ } else {
+ aapsLogger.error(LTag.APS, "Problem loading JS Functions")
+ }
+ } catch (e: IOException) {
+ aapsLogger.error(LTag.APS, "IOException")
+ } catch (e: RhinoException) {
+ aapsLogger.error(LTag.APS, "RhinoException: (" + e.lineNumber() + "," + e.columnNumber() + ") " + e.toString())
+ } catch (e: IllegalAccessException) {
+ aapsLogger.error(LTag.APS, e.toString())
+ } catch (e: InstantiationException) {
+ aapsLogger.error(LTag.APS, e.toString())
+ } catch (e: InvocationTargetException) {
+ aapsLogger.error(LTag.APS, e.toString())
+ } finally {
+ Context.exit()
+ }
+ glucoseStatusParam = mGlucoseStatus.toString()
+ iobDataParam = iobData.toString()
+ currentTempParam = currentTemp.toString()
+ profileParam = profile.toString()
+ mealDataParam = mealData.toString()
+ return determineBasalResultSMB
+ }
+
+ @Suppress("SpellCheckingInspection")
+ override fun setData(
+ profile: Profile,
+ maxIob: Double,
+ maxBasal: Double,
+ minBg: Double,
+ maxBg: Double,
+ targetBg: Double,
+ basalRate: Double,
+ iobArray: Array,
+ glucoseStatus: GlucoseStatus,
+ mealData: MealData,
+ autosensDataRatio: Double,
+ tempTargetSet: Boolean,
+ microBolusAllowed: Boolean,
+ uamAllowed: Boolean,
+ advancedFiltering: Boolean,
+ isSaveCgmSource: Boolean
+ ) {
+ val pump = activePlugin.activePump
+ val pumpBolusStep = pump.pumpDescription.bolusStep
+ this.profile.put("max_iob", maxIob)
+ //mProfile.put("dia", profile.getDia());
+ this.profile.put("type", "current")
+ this.profile.put("max_daily_basal", profile.getMaxDailyBasal())
+ this.profile.put("max_basal", maxBasal)
+ this.profile.put("min_bg", minBg)
+ this.profile.put("max_bg", maxBg)
+ this.profile.put("target_bg", targetBg)
+ this.profile.put("carb_ratio", profile.getIc())
+ this.profile.put("sens", profile.getIsfMgdl())
+ this.profile.put("max_daily_safety_multiplier", sp.getInt(R.string.key_openapsama_max_daily_safety_multiplier, 3))
+ this.profile.put("current_basal_safety_multiplier", sp.getDouble(R.string.key_openapsama_current_basal_safety_multiplier, 4.0))
+ this.profile.put("lgsThreshold", Profile.toMgdl(sp.getDouble(R.string.key_lgs_threshold, 65.0)))
+
+ val insulin = activePlugin.activeInsulin
+ val insulinType = insulin.friendlyName
+ val insulinPeak = insulin.peak
+
+ //mProfile.put("high_temptarget_raises_sensitivity", SP.getBoolean(R.string.key_high_temptarget_raises_sensitivity, SMBDefaults.high_temptarget_raises_sensitivity));
+ this.profile.put("high_temptarget_raises_sensitivity", sp.getBoolean(R.string.key_high_temptarget_raises_sensitivity, SMBDefaults.high_temptarget_raises_sensitivity))
+ //mProfile.put("low_temptarget_lowers_sensitivity", SP.getBoolean(R.string.key_low_temptarget_lowers_sensitivity, SMBDefaults.low_temptarget_lowers_sensitivity));
+ this.profile.put("low_temptarget_lowers_sensitivity", sp.getBoolean(R.string.key_low_temptarget_lowers_sensitivity, SMBDefaults.low_temptarget_lowers_sensitivity))
+ this.profile.put("sensitivity_raises_target", sp.getBoolean(R.string.key_sensitivity_raises_target, SMBDefaults.sensitivity_raises_target))
+ this.profile.put("resistance_lowers_target", sp.getBoolean(R.string.key_resistance_lowers_target, SMBDefaults.resistance_lowers_target))
+ this.profile.put("adv_target_adjustments", SMBDefaults.adv_target_adjustments)
+ this.profile.put("exercise_mode", SMBDefaults.exercise_mode)
+ this.profile.put("half_basal_exercise_target", SMBDefaults.half_basal_exercise_target)
+ this.profile.put("maxCOB", SMBDefaults.maxCOB)
+ this.profile.put("skip_neutral_temps", pump.setNeutralTempAtFullHour())
+ // min_5m_carbimpact is not used within SMB determinebasal
+ //if (mealData.usedMinCarbsImpact > 0) {
+ // mProfile.put("min_5m_carbimpact", mealData.usedMinCarbsImpact);
+ //} else {
+ // mProfile.put("min_5m_carbimpact", SP.getDouble(R.string.key_openapsama_min_5m_carbimpact, SMBDefaults.min_5m_carbimpact));
+ //}
+ this.profile.put("remainingCarbsCap", SMBDefaults.remainingCarbsCap)
+ this.profile.put("enableUAM", uamAllowed)
+ this.profile.put("A52_risk_enable", SMBDefaults.A52_risk_enable)
+ val smbEnabled = sp.getBoolean(R.string.key_use_smb, false)
+ this.profile.put("SMBInterval", sp.getInt(R.string.key_smbinterval, SMBDefaults.SMBInterval))
+ this.profile.put("enableSMB_with_COB", smbEnabled && sp.getBoolean(R.string.key_enableSMB_with_COB, false))
+ this.profile.put("enableSMB_with_temptarget", smbEnabled && sp.getBoolean(R.string.key_enableSMB_with_temptarget, false))
+ this.profile.put("allowSMB_with_high_temptarget", smbEnabled && sp.getBoolean(R.string.key_allowSMB_with_high_temptarget, false))
+ this.profile.put("enableSMB_always", smbEnabled && sp.getBoolean(R.string.key_enableSMB_always, false) && advancedFiltering)
+ this.profile.put("enableSMB_after_carbs", smbEnabled && sp.getBoolean(R.string.key_enableSMB_after_carbs, false) && advancedFiltering)
+ this.profile.put("maxSMBBasalMinutes", sp.getInt(R.string.key_smbmaxminutes, SMBDefaults.maxSMBBasalMinutes))
+ this.profile.put("maxUAMSMBBasalMinutes", sp.getInt(R.string.key_uamsmbmaxminutes, SMBDefaults.maxUAMSMBBasalMinutes))
+ this.profile.put("DynISFAdjust", SafeParse.stringToDouble(sp.getString(R.string.key_DynISFAdjust, "100")))
+ this.profile.put("insulinType", insulinType)
+ this.profile.put("insulinPeak", insulinPeak)
+ this.profile.put("maxUAMSMBBasalMinutes", sp.getInt(R.string.key_uamsmbmaxminutes, SMBDefaults.maxUAMSMBBasalMinutes))
+ //set the min SMB amount to be the amount set by the pump.
+ this.profile.put("bolus_increment", pumpBolusStep)
+ this.profile.put("carbsReqThreshold", sp.getInt(R.string.key_carbsReqThreshold, SMBDefaults.carbsReqThreshold))
+ this.profile.put("current_basal", basalRate)
+ this.profile.put("temptargetSet", tempTargetSet)
+ this.profile.put("autosens_max", SafeParse.stringToDouble(sp.getString(R.string.key_openapsama_autosens_max, "1.2")))
+ this.profile.put("autosens_min", SafeParse.stringToDouble(sp.getString(R.string.key_openapsama_autosens_min, "0.8")))
+ this.profile.put("openapsama_useautosens", sp.getBoolean(R.string.key_openapsama_useautosens, false))
+ //set the min SMB amount to be the amount set by the pump.
+ if (profileFunction.getUnits() == GlucoseUnit.MMOL) {
+ this.profile.put("out_units", "mmol/L")
+ }
+ val now = System.currentTimeMillis()
+ val tb = iobCobCalculator.getTempBasalIncludingConvertedExtended(now)
+ currentTemp.put("temp", "absolute")
+ currentTemp.put("duration", tb?.plannedRemainingMinutes ?: 0)
+ currentTemp.put("rate", tb?.convertedToAbsolute(now, profile) ?: 0.0)
+ // as we have non default temps longer than 30 mintues
+ if (tb != null) currentTemp.put("minutesrunning", tb.getPassedDurationToTimeInMinutes(now))
+
+ iobData = iobCobCalculator.convertToJSONArray(iobArray)
+ mGlucoseStatus.put("glucose", glucoseStatus.glucose)
+ mGlucoseStatus.put("noise", glucoseStatus.noise)
+ if (sp.getBoolean(R.string.key_always_use_shortavg, false)) {
+ mGlucoseStatus.put("delta", glucoseStatus.shortAvgDelta)
+ } else {
+ mGlucoseStatus.put("delta", glucoseStatus.delta)
+ }
+
+ mGlucoseStatus.put("short_avgdelta", glucoseStatus.shortAvgDelta)
+ mGlucoseStatus.put("long_avgdelta", glucoseStatus.longAvgDelta)
+ mGlucoseStatus.put("date", glucoseStatus.date)
+ this.mealData.put("carbs", mealData.carbs)
+ this.mealData.put("mealCOB", mealData.mealCOB)
+ this.mealData.put("slopeFromMaxDeviation", mealData.slopeFromMaxDeviation)
+ this.mealData.put("slopeFromMinDeviation", mealData.slopeFromMinDeviation)
+ this.mealData.put("lastBolusTime", mealData.lastBolusTime)
+ this.mealData.put("lastCarbTime", mealData.lastCarbTime)
+
+ this.mealData.put("TDDAIMI1", tddCalculator.averageTDD(tddCalculator.calculate(1))?.totalAmount)
+ this.mealData.put("TDDAIMI7", tddCalculator.averageTDD(tddCalculator.calculate(7))?.totalAmount)
+ this.mealData.put("TDDLast4", tddCalculator.calculateDaily(-4, 0).totalAmount)
+ this.mealData.put("TDD4to8", tddCalculator.calculateDaily(-8, -4).totalAmount)
+ this.mealData.put("TDD24", tddCalculator.calculateDaily(-24, 0).totalAmount)
+
+
+
+ if (constraintChecker.isAutosensModeEnabled().value()) {
+ autosensData.put("ratio", autosensDataRatio)
+ } else {
+ autosensData.put("ratio", 1.0)
+ }
+ this.microBolusAllowed = microBolusAllowed
+ smbAlwaysAllowed = advancedFiltering
+ currentTime = now
+ saveCgmSource = isSaveCgmSource
+ }
+
+ private fun makeParam(jsonObject: JSONObject?, rhino: Context, scope: Scriptable): Any {
+ return if (jsonObject == null) Undefined.instance
+ else NativeJSON.parse(rhino, scope, jsonObject.toString()) { _: Context?, _: Scriptable?, _: Scriptable?, objects: Array -> objects[1] }
+ }
+
+ private fun makeParamArray(jsonArray: JSONArray?, rhino: Context, scope: Scriptable): Any {
+ return NativeJSON.parse(rhino, scope, jsonArray.toString()) { _: Context?, _: Scriptable?, _: Scriptable?, objects: Array -> objects[1] }
+ }
+
+ @Throws(IOException::class) private fun readFile(filename: String): String {
+ val bytes = scriptReader.readFile(filename)
+ var string = String(bytes, StandardCharsets.UTF_8)
+ if (string.startsWith("#!/usr/bin/env node")) {
+ string = string.substring(20)
+ }
+ return string
+ }
+
+ init {
+ injector.androidInjector().inject(this)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMBDynamicISF/OpenAPSSMBDynamicISFPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMBDynamicISF/OpenAPSSMBDynamicISFPlugin.kt
new file mode 100644
index 0000000000..f3978aacf1
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMBDynamicISF/OpenAPSSMBDynamicISFPlugin.kt
@@ -0,0 +1,76 @@
+package info.nightscout.androidaps.plugins.aps.openAPSSMBDynamicISF
+
+import android.content.Context
+import dagger.android.HasAndroidInjector
+import info.nightscout.androidaps.R
+import info.nightscout.androidaps.annotations.OpenForTesting
+import info.nightscout.androidaps.database.AppRepository
+import info.nightscout.androidaps.interfaces.ActivePlugin
+import info.nightscout.androidaps.interfaces.IobCobCalculator
+import info.nightscout.androidaps.interfaces.ProfileFunction
+import info.nightscout.androidaps.plugins.aps.loop.ScriptReader
+import info.nightscout.androidaps.interfaces.DetermineBasalAdapterInterface
+import info.nightscout.androidaps.plugins.aps.openAPSSMB.OpenAPSSMBPlugin
+import info.nightscout.androidaps.plugins.bus.RxBus
+import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
+import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProvider
+import info.nightscout.androidaps.utils.DateUtil
+import info.nightscout.androidaps.utils.HardLimits
+import info.nightscout.androidaps.utils.Profiler
+import info.nightscout.androidaps.interfaces.BuildHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.shared.sharedPreferences.SP
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@OpenForTesting
+@Singleton
+class OpenAPSSMBDynamicISFPlugin @Inject constructor(
+ injector: HasAndroidInjector,
+ aapsLogger: AAPSLogger,
+ rxBus: RxBus,
+ constraintChecker: ConstraintChecker,
+ rh: ResourceHelper,
+ profileFunction: ProfileFunction,
+ context: Context,
+ activePlugin: ActivePlugin,
+ iobCobCalculator: IobCobCalculator,
+ hardLimits: HardLimits,
+ profiler: Profiler,
+ sp: SP,
+ dateUtil: DateUtil,
+ repository: AppRepository,
+ glucoseStatusProvider: GlucoseStatusProvider,
+ private val buildHelper: BuildHelper
+) : OpenAPSSMBPlugin(
+ injector,
+ aapsLogger,
+ rxBus,
+ constraintChecker,
+ rh,
+ profileFunction,
+ context,
+ activePlugin,
+ iobCobCalculator,
+ hardLimits,
+ profiler,
+ sp,
+ dateUtil,
+ repository,
+ glucoseStatusProvider
+) {
+
+ init {
+ pluginDescription
+ .pluginName(R.string.openaps_smb_dynamic_isf)
+ .description(R.string.description_smb_dynamic_isf)
+ .shortName(R.string.dynisf_shortname)
+ .preferencesId(R.xml.pref_openapssmbdynamicisf)
+ .setDefault(false)
+ }
+
+ override fun specialEnableCondition(): Boolean = buildHelper.isEngineeringMode() && buildHelper.isDev()
+
+ override fun provideDetermineBasalAdapter(): DetermineBasalAdapterInterface = DetermineBasalAdapterSMBDynamicISFJS(ScriptReader(context), injector)
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ConfigBuilderFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ConfigBuilderFragment.kt
index 528b89f683..34d058ac5c 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ConfigBuilderFragment.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ConfigBuilderFragment.kt
@@ -10,23 +10,26 @@ import android.widget.*
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import dagger.android.support.DaggerFragment
-import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.R
import info.nightscout.androidaps.activities.PreferencesActivity
+import info.nightscout.androidaps.activities.SingleFragmentActivity
import info.nightscout.androidaps.databinding.ConfigbuilderFragmentBinding
import info.nightscout.androidaps.events.EventRebuildTabs
-import info.nightscout.androidaps.interfaces.*
+import info.nightscout.androidaps.extensions.toVisibility
+import info.nightscout.androidaps.interfaces.ActivePlugin
+import info.nightscout.androidaps.interfaces.Config
+import info.nightscout.androidaps.interfaces.PluginBase
+import info.nightscout.androidaps.interfaces.PluginType
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.configBuilder.events.EventConfigBuilderUpdateGui
import info.nightscout.androidaps.utils.FabricPrivacy
-import io.reactivex.rxkotlin.plusAssign
-import info.nightscout.androidaps.extensions.toVisibility
-import info.nightscout.androidaps.utils.buildHelper.BuildHelper
+import info.nightscout.androidaps.interfaces.BuildHelper
import info.nightscout.androidaps.utils.protection.ProtectionCheck
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.utils.protection.ProtectionCheck.Protection.PREFERENCES
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
-import io.reactivex.disposables.CompositeDisposable
-import java.util.*
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.kotlin.plusAssign
import javax.inject.Inject
class ConfigBuilderFragment : DaggerFragment() {
@@ -44,48 +47,36 @@ class ConfigBuilderFragment : DaggerFragment() {
private var disposable: CompositeDisposable = CompositeDisposable()
private val pluginViewHolders = ArrayList()
-
+ private var inMenu = false
+ private var queryingProtection = false
private var _binding: ConfigbuilderFragmentBinding? = null
- // This property is only valid between onCreateView and
- // onDestroyView.
+ // This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!!
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?): View {
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = ConfigbuilderFragmentBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
-
- if (protectionCheck.isLocked(ProtectionCheck.Protection.PREFERENCES))
- binding.mainLayout.visibility = View.GONE
- else
- binding.unlock.visibility = View.GONE
-
- binding.unlock.setOnClickListener {
- activity?.let { activity ->
- protectionCheck.queryProtection(activity, ProtectionCheck.Protection.PREFERENCES, {
- activity.runOnUiThread {
- binding.mainLayout.visibility = View.VISIBLE
- binding.unlock.visibility = View.GONE
- }
- })
- }
- }
+ val parentClass = this.activity?.let { it::class.java }
+ inMenu = parentClass == SingleFragmentActivity::class.java
+ updateProtectedUi()
+ binding.unlock.setOnClickListener { queryProtection() }
}
@Synchronized
override fun onResume() {
super.onResume()
+ if (inMenu) queryProtection() else updateProtectedUi()
disposable += rxBus
.toObservable(EventConfigBuilderUpdateGui::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({
- for (pluginViewHolder in pluginViewHolders) pluginViewHolder.update()
- }, fabricPrivacy::logException)
+ for (pluginViewHolder in pluginViewHolders) pluginViewHolder.update()
+ }, fabricPrivacy::logException)
updateGUI()
}
@@ -215,4 +206,21 @@ class ConfigBuilderFragment : DaggerFragment() {
return type == PluginType.GENERAL || type == PluginType.CONSTRAINTS || type == PluginType.LOOP
}
}
+
+ private fun updateProtectedUi() {
+ val isLocked = protectionCheck.isLocked(PREFERENCES)
+ binding.mainLayout.visibility = isLocked.not().toVisibility()
+ binding.unlock.visibility = isLocked.toVisibility()
+ }
+
+ private fun queryProtection() {
+ val isLocked = protectionCheck.isLocked(PREFERENCES)
+ if (isLocked && !queryingProtection) {
+ activity?.let { activity ->
+ queryingProtection = true
+ val doUpdate = { activity.runOnUiThread { queryingProtection = false; updateProtectedUi() } }
+ protectionCheck.queryProtection(activity, PREFERENCES, doUpdate, doUpdate, doUpdate)
+ }
+ }
+ }
}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ConfigBuilderPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ConfigBuilderPlugin.kt
index 72b650e284..e45ec36117 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ConfigBuilderPlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ConfigBuilderPlugin.kt
@@ -16,7 +16,7 @@ import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.configBuilder.events.EventConfigBuilderUpdateGui
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import java.util.*
import javax.inject.Inject
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/PluginStore.kt b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/PluginStore.kt
index 3993606e73..a574219396 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/PluginStore.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/PluginStore.kt
@@ -169,6 +169,9 @@ class PluginStore @Inject constructor(
override val activeSafety: Safety
get() = getSpecificPluginsListByInterface(Safety::class.java).first() as Safety
+ override val activeIobCobCalculator: IobCobCalculator
+ get() = getSpecificPluginsListByInterface(IobCobCalculator::class.java).first() as IobCobCalculator
+
override fun getPluginsList(): ArrayList = ArrayList(plugins)
}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ProfileFunctionImplementation.kt b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ProfileFunctionImplementation.kt
index 405daabc9e..b851e40b22 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ProfileFunctionImplementation.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ProfileFunctionImplementation.kt
@@ -19,11 +19,11 @@ import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.HardLimits
import info.nightscout.androidaps.utils.T
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.shared.sharedPreferences.SP
-import io.reactivex.disposables.CompositeDisposable
-import io.reactivex.rxkotlin.plusAssign
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.kotlin.plusAssign
import javax.inject.Inject
import javax.inject.Singleton
@@ -130,7 +130,6 @@ class ProfileFunctionImplementation @Inject constructor(
}
}
-
return null
}
@@ -205,4 +204,4 @@ class ProfileFunctionImplementation @Inject constructor(
} else returnValue = false
return returnValue
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/bgQualityCheck/BgQualityCheckPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/bgQualityCheck/BgQualityCheckPlugin.kt
index 4b0c9c8e4b..528da25cff 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/bgQualityCheck/BgQualityCheckPlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/bgQualityCheck/BgQualityCheckPlugin.kt
@@ -8,12 +8,12 @@ import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventBucke
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.T
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
-import io.reactivex.disposables.CompositeDisposable
-import io.reactivex.rxkotlin.plusAssign
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.kotlin.plusAssign
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.abs
@@ -102,4 +102,11 @@ class BgQualityCheckPlugin @Inject constructor(
State.RECALCULATED -> R.drawable.ic_baseline_warning_24_yellow
State.DOUBLED -> R.drawable.ic_baseline_warning_24_red
}
+
+ fun stateDescription(): String =
+ when (state) {
+ State.RECALCULATED -> rh.gs(R.string.a11y_bg_quality_recalculated)
+ State.DOUBLED -> rh.gs(R.string.a11y_bg_quality_doubles)
+ else -> ""
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/dstHelper/DstHelperPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/dstHelper/DstHelperPlugin.kt
index 7f8f637bd1..3bd7984943 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/dstHelper/DstHelperPlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/dstHelper/DstHelperPlugin.kt
@@ -10,7 +10,7 @@ import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotifi
import info.nightscout.androidaps.plugins.general.overview.notifications.Notification
import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationWithAction
import info.nightscout.androidaps.utils.T
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import java.util.*
import javax.inject.Inject
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesFragment.kt
index f1f83e5603..09c8b50dc6 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesFragment.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesFragment.kt
@@ -1,6 +1,6 @@
package info.nightscout.androidaps.plugins.constraints.objectives
-import android.graphics.Color
+import android.annotation.SuppressLint
import android.os.Bundle
import android.os.Handler
import android.os.HandlerThread
@@ -37,11 +37,11 @@ import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.HtmlHelper
import info.nightscout.androidaps.utils.SntpClient
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
-import io.reactivex.rxkotlin.plusAssign
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import io.reactivex.rxjava3.kotlin.plusAssign
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.shared.sharedPreferences.SP
-import io.reactivex.disposables.CompositeDisposable
+import io.reactivex.rxjava3.disposables.CompositeDisposable
import javax.inject.Inject
class ObjectivesFragment : DaggerFragment() {
@@ -153,6 +153,7 @@ class ObjectivesFragment : DaggerFragment() {
return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.objectives_item, parent, false))
}
+ @SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val objective = objectivesPlugin.objectives[position]
holder.binding.title.text = rh.gs(R.string.nth_objective, position + 1)
@@ -167,7 +168,7 @@ class ObjectivesFragment : DaggerFragment() {
} else
holder.binding.gate.visibility = View.GONE
if (!objective.isStarted) {
- holder.binding.gate.setTextColor(-0x1)
+ holder.binding.gate.setTextColor(rh.gac(context, R.attr.defaultTextColor))
holder.binding.verify.visibility = View.GONE
holder.binding.progress.visibility = View.GONE
holder.binding.accomplished.visibility = View.GONE
@@ -178,7 +179,7 @@ class ObjectivesFragment : DaggerFragment() {
else
holder.binding.start.visibility = View.GONE
} else if (objective.isAccomplished) {
- holder.binding.gate.setTextColor(-0xb350b0)
+ holder.binding.gate.setTextColor(rh.gac(context, R.attr.isAccomplishedColor))
holder.binding.verify.visibility = View.GONE
holder.binding.progress.visibility = View.GONE
holder.binding.start.visibility = View.GONE
@@ -186,7 +187,7 @@ class ObjectivesFragment : DaggerFragment() {
holder.binding.unfinish.visibility = View.VISIBLE
holder.binding.unstart.visibility = View.GONE
} else if (objective.isStarted) {
- holder.binding.gate.setTextColor(-0x1)
+ holder.binding.gate.setTextColor(rh.gac(context,R.attr.defaultTextColor))
holder.binding.verify.visibility = View.VISIBLE
holder.binding.verify.isEnabled = objective.isCompleted || binding.fake.isChecked
holder.binding.start.visibility = View.GONE
@@ -200,7 +201,7 @@ class ObjectivesFragment : DaggerFragment() {
// name
val name = TextView(holder.binding.progress.context)
name.text = "${rh.gs(task.task)}:"
- name.setTextColor(-0x1)
+ name.setTextColor(rh.gac(context,R.attr.defaultTextColor) )
holder.binding.progress.addView(name, LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)
// hint
task.hints.forEach { h ->
@@ -209,9 +210,9 @@ class ObjectivesFragment : DaggerFragment() {
}
// state
val state = TextView(holder.binding.progress.context)
- state.setTextColor(-0x1)
+ state.setTextColor(rh.gac(context,R.attr.defaultTextColor))
val basicHTML = "%2\$s"
- val formattedHTML = String.format(basicHTML, if (task.isCompleted()) "#4CAF50" else "#FF9800", task.progress)
+ val formattedHTML = String.format(basicHTML, if (task.isCompleted()) rh.gac(context, R.attr.isCompletedColor) else rh.gac(context, R.attr.isNotCompletedColor), task.progress)
state.text = HtmlHelper.fromHtml(formattedHTML)
state.gravity = Gravity.END
holder.binding.progress.addView(state, LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)
@@ -228,12 +229,12 @@ class ObjectivesFragment : DaggerFragment() {
}
// horizontal line
val separator = View(holder.binding.progress.context)
- separator.setBackgroundColor(Color.DKGRAY)
+ separator.setBackgroundColor(rh.gac(context, R.attr.separatorColor))
holder.binding.progress.addView(separator, LinearLayout.LayoutParams.MATCH_PARENT, 2)
}
}
holder.binding.accomplished.text = rh.gs(R.string.accomplished, dateUtil.dateAndTimeString(objective.accomplishedOn))
- holder.binding.accomplished.setTextColor(-0x3e3e3f)
+ holder.binding.accomplished.setTextColor(rh.gac(context,R.attr.defaultTextColor))
holder.binding.verify.setOnClickListener {
receiverStatusStore.updateNetworkStatus()
if (binding.fake.isChecked) {
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesPlugin.kt
index 73f4d99cc9..76e5f397e5 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesPlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesPlugin.kt
@@ -14,7 +14,7 @@ import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.constraints.objectives.objectives.*
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import java.util.*
import javax.inject.Inject
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/activities/ObjectivesExamDialog.kt b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/activities/ObjectivesExamDialog.kt
index ecf004f82f..e2ae0121a0 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/activities/ObjectivesExamDialog.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/activities/ObjectivesExamDialog.kt
@@ -15,7 +15,7 @@ import info.nightscout.androidaps.plugins.constraints.objectives.objectives.Obje
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.ToastUtils
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import javax.inject.Inject
class ObjectivesExamDialog : DaggerDialogFragment() {
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/objectives/Objective.kt b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/objectives/Objective.kt
index 0da7daa37c..9929db80e8 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/objectives/Objective.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/objectives/Objective.kt
@@ -11,7 +11,7 @@ import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.T
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import java.util.*
import javax.inject.Inject
@@ -176,7 +176,7 @@ abstract class Objective(injector: HasAndroidInjector, spName: String, @StringRe
textView.setText(hint)
textView.autoLinkMask = Linkify.WEB_URLS
textView.linksClickable = true
- textView.setLinkTextColor(Color.YELLOW)
+ textView.setLinkTextColor(rh.gac(context, R.attr.colorSecondary))
Linkify.addLinks(textView, Linkify.WEB_URLS)
return textView
}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/objectives/Objective2.kt b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/objectives/Objective2.kt
index 34b1f7a3b9..4c79036f9b 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/objectives/Objective2.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/objectives/Objective2.kt
@@ -126,6 +126,7 @@ class Objective2(injector: HasAndroidInjector) : Objective(injector, "exam", R.s
.option(Option(R.string.sensitivity_cannula, true))
.option(Option(R.string.sensitivity_time, true))
.hint(Hint(R.string.sensitivity_hint1))
+ .hint(Hint(R.string.sensitivity_hint2))
)
tasks.add(ExamTask(this, R.string.objectives_label, R.string.objectives_howtosave, "objectives")
.option(Option(R.string.objectives_notesettings, false))
@@ -177,6 +178,21 @@ class Objective2(injector: HasAndroidInjector) : Objective(injector, "exam", R.s
.option(Option(R.string.iob_negiob, true))
.option(Option(R.string.iob_posiob, true))
)
+ tasks.add(ExamTask(this, R.string.cob_label, R.string.cob_question, "cob1")
+ .option(Option(R.string.cob_longer, true))
+ .option(Option(R.string.cob_shorter, false))
+ .option(Option(R.string.cob_no_effect, false))
+ )
+ tasks.add(ExamTask(this, R.string.cob_label, R.string.cob2_question, "cob2")
+ .option(Option(R.string.cob2_longer, false))
+ .option(Option(R.string.cob2_shorter, true))
+ .option(Option(R.string.cob2_no_effect, false))
+ )
+ tasks.add(ExamTask(this, R.string.cob_label, R.string.cob3_question, "cob3")
+ .option(Option(R.string.cob3_longer, false))
+ .option(Option(R.string.cob3_shorter, false))
+ .option(Option(R.string.cob3_no_effect, true))
+ )
tasks.add(ExamTask(this, R.string.breadgrams_label, R.string.blank, "breadgrams")
.option(Option(R.string.breadgrams_grams, true))
.option(Option(R.string.breadgrams_exchange, false))
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/objectives/Objective6.kt b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/objectives/Objective6.kt
index edcdf54bd5..dd10f4a33d 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/objectives/Objective6.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/objectives/Objective6.kt
@@ -2,7 +2,9 @@ package info.nightscout.androidaps.plugins.constraints.objectives.objectives
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R
+import info.nightscout.androidaps.interfaces.Constraint
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
+import info.nightscout.androidaps.plugins.constraints.safety.SafetyPlugin
import info.nightscout.androidaps.utils.T
import javax.inject.Inject
@@ -10,14 +12,21 @@ import javax.inject.Inject
class Objective6(injector: HasAndroidInjector) : Objective(injector, "maxiob", R.string.objectives_maxiob_objective, R.string.objectives_maxiob_gate) {
@Inject lateinit var constraintChecker: ConstraintChecker
+ @Inject lateinit var safetyPlugin: SafetyPlugin
init {
tasks.add(MinimumDurationTask(this, T.days(1).msecs()))
- tasks.add(object : Task(this, R.string.maxiobset) {
- override fun isCompleted(): Boolean {
- val maxIOB = constraintChecker.getMaxIOBAllowed().value()
- return maxIOB > 0
- }
- })
+ tasks.add(
+ object : Task(this, R.string.closedmodeenabled) {
+ override fun isCompleted(): Boolean = sp.getString(R.string.key_aps_mode, "open") == "closed"
+ })
+ tasks.add(
+ object : Task(this, R.string.maxiobset) {
+
+ override fun isCompleted(): Boolean {
+ val maxIOB = constraintChecker.getMaxIOBAllowed().value()
+ return maxIOB > 0
+ }
+ })
}
}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/phoneChecker/PhoneCheckerPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/phoneChecker/PhoneCheckerPlugin.kt
index fed6ccfd31..8d5e1fdd62 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/phoneChecker/PhoneCheckerPlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/phoneChecker/PhoneCheckerPlugin.kt
@@ -10,7 +10,7 @@ import info.nightscout.androidaps.interfaces.PluginBase
import info.nightscout.androidaps.interfaces.PluginDescription
import info.nightscout.androidaps.interfaces.PluginType
import info.nightscout.shared.logging.AAPSLogger
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import javax.inject.Inject
import javax.inject.Singleton
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/safety/SafetyPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/safety/SafetyPlugin.kt
index cff84db028..4f4a835e9f 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/safety/SafetyPlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/safety/SafetyPlugin.kt
@@ -9,6 +9,7 @@ import info.nightscout.androidaps.interfaces.*
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.androidaps.plugins.aps.openAPSAMA.OpenAPSAMAPlugin
import info.nightscout.androidaps.plugins.aps.openAPSSMB.OpenAPSSMBPlugin
+import info.nightscout.androidaps.plugins.aps.openAPSSMBDynamicISF.OpenAPSSMBDynamicISFPlugin
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification
@@ -18,8 +19,8 @@ import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.DecimalFormatter
import info.nightscout.androidaps.utils.HardLimits
import info.nightscout.androidaps.utils.Round
-import info.nightscout.androidaps.utils.buildHelper.BuildHelper
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.BuildHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONObject
import javax.inject.Inject
@@ -36,6 +37,7 @@ class SafetyPlugin @Inject constructor(
private val constraintChecker: ConstraintChecker,
private val openAPSAMAPlugin: OpenAPSAMAPlugin,
private val openAPSSMBPlugin: OpenAPSSMBPlugin,
+ private val openAPSSMBDynamicISFPlugin: OpenAPSSMBDynamicISFPlugin,
private val sensitivityOref1Plugin: SensitivityOref1Plugin,
private val activePlugin: ActivePlugin,
private val hardLimits: HardLimits,
@@ -107,29 +109,29 @@ class SafetyPlugin @Inject constructor(
}
override fun applyBasalConstraints(absoluteRate: Constraint, profile: Profile): Constraint {
- absoluteRate.setIfGreater(aapsLogger, 0.0, String.format(rh.gs(R.string.limitingbasalratio), 0.0, rh.gs(R.string.itmustbepositivevalue)), this)
+ absoluteRate.setIfGreater(aapsLogger, 0.0, rh.gs(R.string.limitingbasalratio, 0.0, rh.gs(R.string.itmustbepositivevalue)), this)
if (config.APS) {
var maxBasal = sp.getDouble(R.string.key_openapsma_max_basal, 1.0)
if (maxBasal < profile.getMaxDailyBasal()) {
maxBasal = profile.getMaxDailyBasal()
absoluteRate.addReason(rh.gs(R.string.increasingmaxbasal), this)
}
- absoluteRate.setIfSmaller(aapsLogger, maxBasal, String.format(rh.gs(R.string.limitingbasalratio), maxBasal, rh.gs(R.string.maxvalueinpreferences)), this)
+ absoluteRate.setIfSmaller(aapsLogger, maxBasal,rh.gs(R.string.limitingbasalratio, maxBasal, rh.gs(R.string.maxvalueinpreferences)), this)
// Check percentRate but absolute rate too, because we know real current basal in pump
val maxBasalMultiplier = sp.getDouble(R.string.key_openapsama_current_basal_safety_multiplier, 4.0)
val maxFromBasalMultiplier = floor(maxBasalMultiplier * profile.getBasal() * 100) / 100
- absoluteRate.setIfSmaller(aapsLogger, maxFromBasalMultiplier, String.format(rh.gs(R.string.limitingbasalratio), maxFromBasalMultiplier, rh.gs(R.string.maxbasalmultiplier)), this)
+ absoluteRate.setIfSmaller(aapsLogger, maxFromBasalMultiplier, rh.gs(R.string.limitingbasalratio, maxFromBasalMultiplier, rh.gs(R.string.maxbasalmultiplier)), this)
val maxBasalFromDaily = sp.getDouble(R.string.key_openapsama_max_daily_safety_multiplier, 3.0)
val maxFromDaily = floor(profile.getMaxDailyBasal() * maxBasalFromDaily * 100) / 100
- absoluteRate.setIfSmaller(aapsLogger, maxFromDaily, String.format(rh.gs(R.string.limitingbasalratio), maxFromDaily, rh.gs(R.string.maxdailybasalmultiplier)), this)
+ absoluteRate.setIfSmaller(aapsLogger, maxFromDaily,rh.gs(R.string.limitingbasalratio, maxFromDaily, rh.gs(R.string.maxdailybasalmultiplier)), this)
}
- absoluteRate.setIfSmaller(aapsLogger, hardLimits.maxBasal(), String.format(rh.gs(R.string.limitingbasalratio), hardLimits.maxBasal(), rh.gs(R.string.hardlimit)), this)
+ absoluteRate.setIfSmaller(aapsLogger, hardLimits.maxBasal(),rh.gs(R.string.limitingbasalratio, hardLimits.maxBasal(), rh.gs(R.string.hardlimit)), this)
val pump = activePlugin.activePump
// check for pump max
if (pump.pumpDescription.tempBasalStyle == PumpDescription.ABSOLUTE) {
val pumpLimit = pump.pumpDescription.pumpType.tbrSettings?.maxDose ?: 0.0
- absoluteRate.setIfSmaller(aapsLogger, pumpLimit, String.format(rh.gs(R.string.limitingbasalratio), pumpLimit, rh.gs(R.string.pumplimit)), this)
+ absoluteRate.setIfSmaller(aapsLogger, pumpLimit, rh.gs(R.string.limitingbasalratio, pumpLimit, rh.gs(R.string.pumplimit)), this)
}
// do rounding
@@ -149,19 +151,19 @@ class SafetyPlugin @Inject constructor(
val pump = activePlugin.activePump
var percentRateAfterConst = java.lang.Double.valueOf(absoluteConstraint.value() / currentBasal * 100).toInt()
percentRateAfterConst = if (percentRateAfterConst < 100) Round.ceilTo(percentRateAfterConst.toDouble(), pump.pumpDescription.tempPercentStep.toDouble()).toInt() else Round.floorTo(percentRateAfterConst.toDouble(), pump.pumpDescription.tempPercentStep.toDouble()).toInt()
- percentRate.set(aapsLogger, percentRateAfterConst, String.format(rh.gs(R.string.limitingpercentrate), percentRateAfterConst, rh.gs(R.string.pumplimit)), this)
+ percentRate.set(aapsLogger, percentRateAfterConst, rh.gs(R.string.limitingpercentrate, percentRateAfterConst, rh.gs(R.string.pumplimit)), this)
if (pump.pumpDescription.tempBasalStyle == PumpDescription.PERCENT) {
val pumpLimit = pump.pumpDescription.pumpType.tbrSettings?.maxDose ?: 0.0
- percentRate.setIfSmaller(aapsLogger, pumpLimit.toInt(), String.format(rh.gs(R.string.limitingbasalratio), pumpLimit, rh.gs(R.string.pumplimit)), this)
+ percentRate.setIfSmaller(aapsLogger, pumpLimit.toInt(), rh.gs(R.string.limitingbasalratio, pumpLimit, rh.gs(R.string.pumplimit)), this)
}
return percentRate
}
override fun applyBolusConstraints(insulin: Constraint): Constraint {
- insulin.setIfGreater(aapsLogger, 0.0, String.format(rh.gs(R.string.limitingbolus), 0.0, rh.gs(R.string.itmustbepositivevalue)), this)
+ insulin.setIfGreater(aapsLogger, 0.0, rh.gs(R.string.limitingbolus, 0.0, rh.gs(R.string.itmustbepositivevalue)), this)
val maxBolus = sp.getDouble(R.string.key_treatmentssafety_maxbolus, 3.0)
- insulin.setIfSmaller(aapsLogger, maxBolus, String.format(rh.gs(R.string.limitingbolus), maxBolus, rh.gs(R.string.maxvalueinpreferences)), this)
- insulin.setIfSmaller(aapsLogger, hardLimits.maxBolus(), String.format(rh.gs(R.string.limitingbolus), hardLimits.maxBolus(), rh.gs(R.string.hardlimit)), this)
+ insulin.setIfSmaller(aapsLogger, maxBolus, rh.gs(R.string.limitingbolus, maxBolus, rh.gs(R.string.maxvalueinpreferences)), this)
+ insulin.setIfSmaller(aapsLogger, hardLimits.maxBolus(), rh.gs(R.string.limitingbolus, hardLimits.maxBolus(), rh.gs(R.string.hardlimit)), this)
val pump = activePlugin.activePump
val rounded = pump.pumpDescription.pumpType.determineCorrectBolusSize(insulin.value())
insulin.setIfDifferent(aapsLogger, rounded, rh.gs(R.string.pumplimit), this)
@@ -169,10 +171,10 @@ class SafetyPlugin @Inject constructor(
}
override fun applyExtendedBolusConstraints(insulin: Constraint): Constraint {
- insulin.setIfGreater(aapsLogger, 0.0, String.format(rh.gs(R.string.limitingextendedbolus), 0.0, rh.gs(R.string.itmustbepositivevalue)), this)
+ insulin.setIfGreater(aapsLogger, 0.0, rh.gs(R.string.limitingextendedbolus, 0.0, rh.gs(R.string.itmustbepositivevalue)), this)
val maxBolus = sp.getDouble(R.string.key_treatmentssafety_maxbolus, 3.0)
- insulin.setIfSmaller(aapsLogger, maxBolus, String.format(rh.gs(R.string.limitingextendedbolus), maxBolus, rh.gs(R.string.maxvalueinpreferences)), this)
- insulin.setIfSmaller(aapsLogger, hardLimits.maxBolus(), String.format(rh.gs(R.string.limitingextendedbolus), hardLimits.maxBolus(), rh.gs(R.string.hardlimit)), this)
+ insulin.setIfSmaller(aapsLogger, maxBolus, rh.gs(R.string.limitingextendedbolus, maxBolus, rh.gs(R.string.maxvalueinpreferences)), this)
+ insulin.setIfSmaller(aapsLogger, hardLimits.maxBolus(), rh.gs(R.string.limitingextendedbolus, hardLimits.maxBolus(), rh.gs(R.string.hardlimit)), this)
val pump = activePlugin.activePump
val rounded = pump.pumpDescription.pumpType.determineCorrectExtendedBolusSize(insulin.value())
insulin.setIfDifferent(aapsLogger, rounded, rh.gs(R.string.pumplimit), this)
@@ -180,19 +182,21 @@ class SafetyPlugin @Inject constructor(
}
override fun applyCarbsConstraints(carbs: Constraint): Constraint {
- carbs.setIfGreater(aapsLogger, 0, String.format(rh.gs(R.string.limitingcarbs), 0, rh.gs(R.string.itmustbepositivevalue)), this)
+ carbs.setIfGreater(aapsLogger, 0, rh.gs(R.string.limitingcarbs, 0, rh.gs(R.string.itmustbepositivevalue)), this)
val maxCarbs = sp.getInt(R.string.key_treatmentssafety_maxcarbs, 48)
- carbs.setIfSmaller(aapsLogger, maxCarbs, String.format(rh.gs(R.string.limitingcarbs), maxCarbs, rh.gs(R.string.maxvalueinpreferences)), this)
+ carbs.setIfSmaller(aapsLogger, maxCarbs, rh.gs(R.string.limitingcarbs, maxCarbs, rh.gs(R.string.maxvalueinpreferences)), this)
return carbs
}
override fun applyMaxIOBConstraints(maxIob: Constraint): Constraint {
val apsMode = sp.getString(R.string.key_aps_mode, "open")
- val maxIobPref: Double = if (openAPSSMBPlugin.isEnabled()) sp.getDouble(R.string.key_openapssmb_max_iob, 3.0) else sp.getDouble(R.string.key_openapsma_max_iob, 1.5)
- maxIob.setIfSmaller(aapsLogger, maxIobPref, String.format(rh.gs(R.string.limitingiob), maxIobPref, rh.gs(R.string.maxvalueinpreferences)), this)
- if (openAPSAMAPlugin.isEnabled()) maxIob.setIfSmaller(aapsLogger, hardLimits.maxIobAMA(), String.format(rh.gs(R.string.limitingiob), hardLimits.maxIobAMA(), rh.gs(R.string.hardlimit)), this)
- if (openAPSSMBPlugin.isEnabled()) maxIob.setIfSmaller(aapsLogger, hardLimits.maxIobSMB(), String.format(rh.gs(R.string.limitingiob), hardLimits.maxIobSMB(), rh.gs(R.string.hardlimit)), this)
- if (apsMode == "lgs") maxIob.setIfSmaller(aapsLogger, HardLimits.MAX_IOB_LGS, String.format(rh.gs(R.string.limitingiob), HardLimits.MAX_IOB_LGS, rh.gs(R.string.lowglucosesuspend)), this)
+ val maxIobPref: Double = if (openAPSSMBPlugin.isEnabled() || openAPSSMBDynamicISFPlugin.isEnabled()) sp.getDouble(R.string.key_openapssmb_max_iob, 3.0) else sp.getDouble(R.string
+ .key_openapsma_max_iob, 1.5)
+ maxIob.setIfSmaller(aapsLogger, maxIobPref, rh.gs(R.string.limitingiob, maxIobPref, rh.gs(R.string.maxvalueinpreferences)), this)
+ if (openAPSAMAPlugin.isEnabled()) maxIob.setIfSmaller(aapsLogger, hardLimits.maxIobAMA(), rh.gs(R.string.limitingiob, hardLimits.maxIobAMA(), rh.gs(R.string.hardlimit)), this)
+ if (openAPSSMBPlugin.isEnabled()) maxIob.setIfSmaller(aapsLogger, hardLimits.maxIobSMB(), rh.gs(R.string.limitingiob, hardLimits.maxIobSMB(), rh.gs(R.string.hardlimit)), this)
+ if (openAPSSMBDynamicISFPlugin.isEnabled()) maxIob.setIfSmaller(aapsLogger, hardLimits.maxIobSMB(), rh.gs(R.string.limitingiob, hardLimits.maxIobSMB(), rh.gs(R.string.hardlimit)), this)
+ if (apsMode == "lgs") maxIob.setIfSmaller(aapsLogger, HardLimits.MAX_IOB_LGS, rh.gs(R.string.limitingiob, HardLimits.MAX_IOB_LGS, rh.gs(R.string.lowglucosesuspend)), this)
return maxIob
}
@@ -207,4 +211,4 @@ class SafetyPlugin @Inject constructor(
configuration.storeDouble(R.string.key_treatmentssafety_maxbolus, sp, rh)
configuration.storeInt(R.string.key_treatmentssafety_maxcarbs, sp, rh)
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/signatureVerifier/SignatureVerifierPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/signatureVerifier/SignatureVerifierPlugin.kt
index 19b0415f96..aba2b0247f 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/signatureVerifier/SignatureVerifierPlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/signatureVerifier/SignatureVerifierPlugin.kt
@@ -13,7 +13,7 @@ import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification
import info.nightscout.androidaps.plugins.general.overview.notifications.Notification
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import org.spongycastle.util.encoders.Hex
import java.io.*
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/storage/StorageConstraintPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/storage/StorageConstraintPlugin.kt
index 2bc2dfef67..2dd8c80832 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/storage/StorageConstraintPlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/storage/StorageConstraintPlugin.kt
@@ -16,7 +16,7 @@ import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification
import info.nightscout.androidaps.plugins.general.overview.notifications.Notification
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import javax.inject.Inject
import javax.inject.Singleton
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/versionChecker/VersionCheckerPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/versionChecker/VersionCheckerPlugin.kt
index a45ae8dd50..9c55f5cdf3 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/versionChecker/VersionCheckerPlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/versionChecker/VersionCheckerPlugin.kt
@@ -9,7 +9,7 @@ import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotifi
import info.nightscout.androidaps.plugins.general.overview.notifications.Notification
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.extensions.daysToMillis
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.sharedPreferences.SP
import java.util.concurrent.TimeUnit
@@ -80,7 +80,6 @@ class VersionCheckerPlugin @Inject constructor(
return
}
-
if (isOldVersion(gracePeriod.warning.daysToMillis()) && shouldWarnAgain()) {
// store last notification time
sp.putLong(R.string.key_last_versionchecker_plugin_warning, now)
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/actions/ActionsFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/actions/ActionsFragment.kt
index 111df21369..92002eab4d 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/actions/ActionsFragment.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/actions/ActionsFragment.kt
@@ -8,10 +8,8 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
-import android.widget.TextView
import androidx.core.content.ContextCompat
import dagger.android.support.DaggerFragment
-import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R
import info.nightscout.androidaps.activities.ErrorHelperActivity
import info.nightscout.androidaps.activities.HistoryBrowseActivity
@@ -20,7 +18,7 @@ import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.ValueWrapper
import info.nightscout.androidaps.database.entities.UserEntry.Action
import info.nightscout.androidaps.database.entities.UserEntry.Sources
-import info.nightscout.androidaps.diaconn.DiaconnG8Plugin
+import info.nightscout.androidaps.databinding.ActionsFragmentBinding
import info.nightscout.androidaps.dialogs.*
import info.nightscout.androidaps.events.EventCustomActionsChanged
import info.nightscout.androidaps.events.EventExtendedBolusChange
@@ -36,21 +34,20 @@ import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.actions.defs.CustomAction
import info.nightscout.androidaps.plugins.general.overview.StatusLightHandler
-import info.nightscout.androidaps.plugins.pump.omnipod.eros.OmnipodErosPumpPlugin
import info.nightscout.androidaps.queue.Callback
import info.nightscout.androidaps.skins.SkinProvider
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
-import info.nightscout.androidaps.utils.buildHelper.BuildHelper
+import info.nightscout.androidaps.interfaces.BuildHelper
import info.nightscout.androidaps.utils.protection.ProtectionCheck
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.androidaps.utils.ui.SingleClickButton
import info.nightscout.androidaps.utils.ui.UIRunnable
-import io.reactivex.disposables.CompositeDisposable
-import io.reactivex.rxkotlin.plusAssign
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.kotlin.plusAssign
import java.util.*
import javax.inject.Inject
@@ -81,88 +78,48 @@ class ActionsFragment : DaggerFragment() {
private val pumpCustomActions = HashMap()
private val pumpCustomButtons = ArrayList()
- private var smallWidth = false
- private var smallHeight = false
private lateinit var dm: DisplayMetrics
- private var buttonsLayout: LinearLayout? = null
- private var profileSwitch: SingleClickButton? = null
- private var tempTarget: SingleClickButton? = null
- private var extendedBolus: SingleClickButton? = null
- private var extendedBolusCancel: SingleClickButton? = null
- private var setTempBasal: SingleClickButton? = null
- private var cancelTempBasal: SingleClickButton? = null
- private var fill: SingleClickButton? = null
- private var historyBrowser: SingleClickButton? = null
- private var tddStats: SingleClickButton? = null
- private var pumpBatteryChange: SingleClickButton? = null
+ private var _binding: ActionsFragmentBinding? = null
+ // This property is only valid between onCreateView and onDestroyView.
+ private val binding get() = _binding!!
- private var cannulaAge: TextView? = null
- private var insulinAge: TextView? = null
- private var reservoirLevel: TextView? = null
- private var sensorAge: TextView? = null
- private var sensorLevel: TextView? = null
- private var pbAge: TextView? = null
- private var batteryLevel: TextView? = null
- private var sensorLevelLabel: TextView? = null
- private var insulinLevelLabel: TextView? = null
- private var pbLevelLabel: TextView? = null
- private var cannulaOrPatch: TextView? = null
-
- override fun onCreateView(
- inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
//check screen width
dm = DisplayMetrics()
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R)
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
+ @Suppress("DEPRECATION")
activity?.display?.getRealMetrics(dm)
- else
- @Suppress("DEPRECATION") activity?.windowManager?.defaultDisplay?.getMetrics(dm)
-
- val screenWidth = dm.widthPixels
- val screenHeight = dm.heightPixels
- smallWidth = screenWidth <= Constants.SMALL_WIDTH
- smallHeight = screenHeight <= Constants.SMALL_HEIGHT
- val landscape = screenHeight < screenWidth
-
- return inflater.inflate(skinProvider.activeSkin().actionsLayout(landscape, smallWidth), container, false)
+ } else {
+ @Suppress("DEPRECATION")
+ activity?.windowManager?.defaultDisplay?.getMetrics(dm)
+ }
+ _binding = ActionsFragmentBinding.inflate(inflater, container, false)
+ return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- buttonsLayout = view.findViewById(R.id.action_buttons_layout)
- profileSwitch = view.findViewById(R.id.actions_profileswitch)
- tempTarget = view.findViewById(R.id.actions_temptarget)
- extendedBolus = view.findViewById(R.id.actions_extendedbolus)
- extendedBolusCancel = view.findViewById(R.id.actions_extendedbolus_cancel)
- setTempBasal = view.findViewById(R.id.actions_settempbasal)
- cancelTempBasal = view.findViewById(R.id.actions_canceltempbasal)
- fill = view.findViewById(R.id.actions_fill)
- historyBrowser = view.findViewById(R.id.actions_historybrowser)
- tddStats = view.findViewById(R.id.actions_tddstats)
- pumpBatteryChange = view.findViewById(R.id.actions_pumpbatterychange)
+ skinProvider.activeSkin().preProcessLandscapeActionsLayout(dm, binding)
- cannulaAge = view.findViewById(R.id.cannula_age)
- insulinAge = view.findViewById(R.id.insulin_age)
- reservoirLevel = view.findViewById(R.id.reservoir_level)
- sensorAge = view.findViewById(R.id.sensor_age)
- sensorLevel = view.findViewById(R.id.sensor_level)
- pbAge = view.findViewById(R.id.pb_age)
- batteryLevel = view.findViewById(R.id.battery_level)
- sensorLevelLabel = view.findViewById(R.id.sensor_level_label)
- insulinLevelLabel = view.findViewById(R.id.insulin_level_label)
- pbLevelLabel = view.findViewById(R.id.pb_level_label)
- cannulaOrPatch = view.findViewById(R.id.cannula_or_patch)
-
- profileSwitch?.setOnClickListener {
- ProfileSwitchDialog().show(childFragmentManager, "ProfileSwitchDialog")
+ binding.profileSwitch.setOnClickListener {
+ activity?.let { activity ->
+ protectionCheck.queryProtection(
+ activity,
+ ProtectionCheck.Protection.BOLUS,
+ UIRunnable { ProfileSwitchDialog().show(childFragmentManager, "ProfileSwitchDialog")})
+ }
}
- tempTarget?.setOnClickListener {
- TempTargetDialog().show(childFragmentManager, "Actions")
+ binding.tempTarget.setOnClickListener {
+ activity?.let { activity ->
+ protectionCheck.queryProtection(
+ activity,
+ ProtectionCheck.Protection.BOLUS,
+ UIRunnable { TempTargetDialog().show(childFragmentManager, "Actions") })
+ }
}
- extendedBolus?.setOnClickListener {
+ binding.extendedBolus.setOnClickListener {
activity?.let { activity ->
protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, UIRunnable {
OKDialog.showConfirmation(
@@ -174,7 +131,7 @@ class ActionsFragment : DaggerFragment() {
})
}
}
- extendedBolusCancel?.setOnClickListener {
+ binding.extendedBolusCancel.setOnClickListener {
if (iobCobCalculator.getExtendedBolus(dateUtil.now()) != null) {
uel.log(Action.CANCEL_EXTENDED_BOLUS, Sources.Actions)
commandQueue.cancelExtended(object : Callback() {
@@ -186,10 +143,15 @@ class ActionsFragment : DaggerFragment() {
})
}
}
- setTempBasal?.setOnClickListener {
- TempBasalDialog().show(childFragmentManager, "Actions")
+ binding.setTempBasal.setOnClickListener {
+ activity?.let { activity ->
+ protectionCheck.queryProtection(
+ activity,
+ ProtectionCheck.Protection.BOLUS,
+ UIRunnable { TempBasalDialog().show(childFragmentManager, "Actions") })
+ }
}
- cancelTempBasal?.setOnClickListener {
+ binding.cancelTempBasal.setOnClickListener {
if (iobCobCalculator.getTempBasalIncludingConvertedExtended(dateUtil.now()) != null) {
uel.log(Action.CANCEL_TEMP_BASAL, Sources.Actions)
commandQueue.cancelTempBasal(true, object : Callback() {
@@ -201,32 +163,32 @@ class ActionsFragment : DaggerFragment() {
})
}
}
- fill?.setOnClickListener {
+ binding.fill.setOnClickListener {
activity?.let { activity ->
protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, UIRunnable { FillDialog().show(childFragmentManager, "FillDialog") })
}
}
- historyBrowser?.setOnClickListener { startActivity(Intent(context, HistoryBrowseActivity::class.java)) }
- tddStats?.setOnClickListener { startActivity(Intent(context, TDDStatsActivity::class.java)) }
- view.findViewById(R.id.actions_bgcheck).setOnClickListener {
+ binding.historyBrowser.setOnClickListener { startActivity(Intent(context, HistoryBrowseActivity::class.java)) }
+ binding.tddStats.setOnClickListener { startActivity(Intent(context, TDDStatsActivity::class.java)) }
+ binding.bgCheck.setOnClickListener {
CareDialog().setOptions(CareDialog.EventType.BGCHECK, R.string.careportal_bgcheck).show(childFragmentManager, "Actions")
}
- view.findViewById(R.id.actions_cgmsensorinsert).setOnClickListener {
+ binding.cgmSensorInsert.setOnClickListener {
CareDialog().setOptions(CareDialog.EventType.SENSOR_INSERT, R.string.careportal_cgmsensorinsert).show(childFragmentManager, "Actions")
}
- pumpBatteryChange?.setOnClickListener {
+ binding.pumpBatteryChange.setOnClickListener {
CareDialog().setOptions(CareDialog.EventType.BATTERY_CHANGE, R.string.careportal_pumpbatterychange).show(childFragmentManager, "Actions")
}
- view.findViewById(R.id.actions_note).setOnClickListener {
+ binding.note.setOnClickListener {
CareDialog().setOptions(CareDialog.EventType.NOTE, R.string.careportal_note).show(childFragmentManager, "Actions")
}
- view.findViewById(R.id.actions_exercise).setOnClickListener {
+ binding.exercise.setOnClickListener {
CareDialog().setOptions(CareDialog.EventType.EXERCISE, R.string.careportal_exercise).show(childFragmentManager, "Actions")
}
- view.findViewById(R.id.actions_question).setOnClickListener {
+ binding.question.setOnClickListener {
CareDialog().setOptions(CareDialog.EventType.QUESTION, R.string.careportal_question).show(childFragmentManager, "Actions")
}
- view.findViewById(R.id.actions_announcement).setOnClickListener {
+ binding.announcement.setOnClickListener {
CareDialog().setOptions(CareDialog.EventType.ANNOUNCEMENT, R.string.careportal_announcement).show(childFragmentManager, "Actions")
}
@@ -265,13 +227,18 @@ class ActionsFragment : DaggerFragment() {
disposable.clear()
}
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+
@Synchronized
fun updateGui() {
val profile = profileFunction.getProfile()
val pump = activePlugin.activePump
- profileSwitch?.visibility = (
+ binding.profileSwitch.visibility = (
activePlugin.activeProfileSource.profile != null &&
pump.pumpDescription.isSetBasalProfileCapable &&
pump.isInitialized() &&
@@ -279,60 +246,58 @@ class ActionsFragment : DaggerFragment() {
!loop.isDisconnected).toVisibility()
if (!pump.pumpDescription.isExtendedBolusCapable || !pump.isInitialized() || pump.isSuspended() || loop.isDisconnected || pump.isFakingTempsByExtendedBoluses || config.NSCLIENT) {
- extendedBolus?.visibility = View.GONE
- extendedBolusCancel?.visibility = View.GONE
+ binding.extendedBolus.visibility = View.GONE
+ binding.extendedBolusCancel.visibility = View.GONE
} else {
val activeExtendedBolus = repository.getExtendedBolusActiveAt(dateUtil.now()).blockingGet()
if (activeExtendedBolus is ValueWrapper.Existing) {
- extendedBolus?.visibility = View.GONE
- extendedBolusCancel?.visibility = View.VISIBLE
+ binding.extendedBolus.visibility = View.GONE
+ binding.extendedBolusCancel.visibility = View.VISIBLE
@Suppress("SetTextI18n")
- extendedBolusCancel?.text = rh.gs(R.string.cancel) + " " + activeExtendedBolus.value.toStringMedium(dateUtil)
+ binding.extendedBolusCancel.text = rh.gs(R.string.cancel) + " " + activeExtendedBolus.value.toStringMedium(dateUtil)
} else {
- extendedBolus?.visibility = View.VISIBLE
- extendedBolusCancel?.visibility = View.GONE
+ binding.extendedBolus.visibility = View.VISIBLE
+ binding.extendedBolusCancel.visibility = View.GONE
}
}
if (!pump.pumpDescription.isTempBasalCapable || !pump.isInitialized() || pump.isSuspended() || loop.isDisconnected || config.NSCLIENT) {
- setTempBasal?.visibility = View.GONE
- cancelTempBasal?.visibility = View.GONE
+ binding.setTempBasal.visibility = View.GONE
+ binding.cancelTempBasal.visibility = View.GONE
} else {
val activeTemp = iobCobCalculator.getTempBasalIncludingConvertedExtended(System.currentTimeMillis())
if (activeTemp != null) {
- setTempBasal?.visibility = View.GONE
- cancelTempBasal?.visibility = View.VISIBLE
+ binding.setTempBasal.visibility = View.GONE
+ binding.cancelTempBasal.visibility = View.VISIBLE
@Suppress("SetTextI18n")
- cancelTempBasal?.text = rh.gs(R.string.cancel) + " " + activeTemp.toStringShort()
+ binding.cancelTempBasal.text = rh.gs(R.string.cancel) + " " + activeTemp.toStringShort()
} else {
- setTempBasal?.visibility = View.VISIBLE
- cancelTempBasal?.visibility = View.GONE
+ binding.setTempBasal.visibility = View.VISIBLE
+ binding.cancelTempBasal.visibility = View.GONE
}
}
val activeBgSource = activePlugin.activeBgSource
- historyBrowser?.visibility = (profile != null).toVisibility()
- fill?.visibility = (pump.pumpDescription.isRefillingCapable && pump.isInitialized() && !pump.isSuspended()).toVisibility()
- if (pump is DiaconnG8Plugin) {
- pumpBatteryChange?.visibility = (pump.pumpDescription.isBatteryReplaceable && !pump.isBatteryChangeLoggingEnabled()).toVisibility()
- } else {
- pumpBatteryChange?.visibility =
- (pump.pumpDescription.isBatteryReplaceable || (pump is OmnipodErosPumpPlugin && pump.isUseRileyLinkBatteryLevel && pump.isBatteryChangeLoggingEnabled)).toVisibility()
- }
- tempTarget?.visibility = (profile != null && !loop.isDisconnected).toVisibility()
- tddStats?.visibility = pump.pumpDescription.supportsTDDs.toVisibility()
+ binding.historyBrowser.visibility = (profile != null).toVisibility()
+ binding.fill.visibility = (pump.pumpDescription.isRefillingCapable && pump.isInitialized() && !pump.isSuspended()).toVisibility()
+ binding.pumpBatteryChange.visibility = (pump.pumpDescription.isBatteryReplaceable || pump.isBatteryChangeLoggingEnabled()).toVisibility()
+ binding.tempTarget.visibility = (profile != null && !loop.isDisconnected).toVisibility()
+ binding.tddStats.visibility = pump.pumpDescription.supportsTDDs.toVisibility()
+ val isPatchPump = pump.pumpDescription.isPatchPump
+ binding.status.apply {
+ cannulaOrPatch.text = if (isPatchPump) rh.gs(R.string.patch_pump) else rh.gs(R.string.cannula)
+ val imageResource = if (isPatchPump) R.drawable.ic_patch_pump_outline else R.drawable.ic_cp_age_cannula
+ cannulaOrPatch.setCompoundDrawablesWithIntrinsicBounds(imageResource, 0, 0, 0)
+ batteryLayout.visibility = (!isPatchPump || pump.pumpDescription.useHardwareLink).toVisibility()
- cannulaOrPatch?.text = if (pump.pumpDescription.isPatchPump) rh.gs(R.string.patch_pump) else rh.gs(R.string.cannula)
- val imageResource = if (pump.pumpDescription.isPatchPump) R.drawable.ic_patch_pump_outline else R.drawable.ic_cp_age_cannula
- cannulaOrPatch?.setCompoundDrawablesWithIntrinsicBounds(imageResource, 0, 0, 0)
-
- if (!config.NSCLIENT) {
- statusLightHandler.updateStatusLights(cannulaAge, insulinAge, reservoirLevel, sensorAge, sensorLevel, pbAge, batteryLevel)
- sensorLevelLabel?.text = if (activeBgSource.sensorBatteryLevel == -1) "" else rh.gs(R.string.careportal_level_label)
- } else {
- statusLightHandler.updateStatusLights(cannulaAge, insulinAge, null, sensorAge, null, pbAge, null)
- sensorLevelLabel?.text = ""
- insulinLevelLabel?.text = ""
- pbLevelLabel?.text = ""
+ if (!config.NSCLIENT) {
+ statusLightHandler.updateStatusLights(cannulaAge, insulinAge, reservoirLevel, sensorAge, sensorLevel, pbAge, batteryLevel)
+ sensorLevelLabel.text = if (activeBgSource.sensorBatteryLevel == -1) "" else rh.gs(R.string.careportal_level_label)
+ } else {
+ statusLightHandler.updateStatusLights(cannulaAge, insulinAge, null, sensorAge, null, pbAge, null)
+ sensorLevelLabel.text = ""
+ insulinLevelLabel.text = ""
+ pbLevelLabel.text = ""
+ }
}
checkPumpCustomActions()
@@ -365,7 +330,7 @@ class ActionsFragment : DaggerFragment() {
val top = activity?.let { ContextCompat.getDrawable(it, customAction.iconResourceId) }
btn.setCompoundDrawablesWithIntrinsicBounds(null, top, null, null)
- buttonsLayout?.addView(btn)
+ binding.buttonsLayout.addView(btn)
this.pumpCustomActions[rh.gs(customAction.name)] = customAction
this.pumpCustomButtons.add(btn)
@@ -373,7 +338,7 @@ class ActionsFragment : DaggerFragment() {
}
private fun removePumpCustomActions() {
- for (customButton in pumpCustomButtons) buttonsLayout?.removeView(customButton)
+ for (customButton in pumpCustomButtons) binding.buttonsLayout.removeView(customButton)
pumpCustomButtons.clear()
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/actions/ActionsPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/actions/ActionsPlugin.kt
index e99beec86a..879c084315 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/actions/ActionsPlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/actions/ActionsPlugin.kt
@@ -7,7 +7,7 @@ import info.nightscout.androidaps.interfaces.PluginBase
import info.nightscout.androidaps.interfaces.PluginDescription
import info.nightscout.androidaps.interfaces.PluginType
import info.nightscout.shared.logging.AAPSLogger
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import javax.inject.Inject
import javax.inject.Singleton
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/AutotuneCore.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/AutotuneCore.kt
new file mode 100644
index 0000000000..d0dc4da9be
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/AutotuneCore.kt
@@ -0,0 +1,516 @@
+package info.nightscout.androidaps.plugins.general.autotune
+
+import info.nightscout.androidaps.R
+import info.nightscout.androidaps.data.LocalInsulin
+import info.nightscout.androidaps.plugins.general.autotune.data.ATProfile
+import info.nightscout.androidaps.plugins.general.autotune.data.PreppedGlucose
+import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin
+import info.nightscout.androidaps.utils.Round
+import info.nightscout.shared.sharedPreferences.SP
+import java.util.*
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class AutotuneCore @Inject constructor(
+ private val sp: SP,
+ private val autotuneFS: AutotuneFS
+) {
+
+ fun tuneAllTheThings(preppedGlucose: PreppedGlucose, previousAutotune: ATProfile, pumpProfile: ATProfile): ATProfile {
+ //var pumpBasalProfile = pumpProfile.basalprofile;
+ val pumpBasalProfile = pumpProfile.basal
+ //console.error(pumpBasalProfile);
+ var basalProfile = previousAutotune.basal
+ //console.error(basalProfile);
+ //console.error(isfProfile);
+ var isf = previousAutotune.isf
+ //console.error(isf);
+ var carbRatio = previousAutotune.ic
+ //console.error(carbRatio);
+ val csf = isf / carbRatio
+ val dia = previousAutotune.dia
+ val peak = previousAutotune.peak
+ val csfGlucose = preppedGlucose.csfGlucoseData
+ val isfGlucose = preppedGlucose.isfGlucoseData
+ val basalGlucose = preppedGlucose.basalGlucoseData
+ val crData = preppedGlucose.crData
+ val diaDeviations = preppedGlucose.diaDeviations
+ val peakDeviations = preppedGlucose.peakDeviations
+ val pumpISF = pumpProfile.isf
+ val pumpCarbRatio = pumpProfile.ic
+ val pumpCSF = pumpISF / pumpCarbRatio
+ // Autosens constraints
+ val autotuneMax = sp.getDouble(R.string.key_openapsama_autosens_max, 1.2)
+ val autotuneMin = sp.getDouble(R.string.key_openapsama_autosens_min, 0.7)
+ val min5minCarbImpact = sp.getDouble(R.string.key_openapsama_min_5m_carbimpact, 3.0)
+
+ // tune DIA
+ var newDia = dia
+ if (diaDeviations.size > 0)
+ {
+ val currentDiaMeanDev = diaDeviations[2].meanDeviation
+ val currentDiaRMSDev = diaDeviations[2].rmsDeviation
+ //Console.WriteLine(DIA,currentDIAMeanDev,currentDIARMSDev);
+ var minMeanDeviations = 1000000.0
+ var minRmsDeviations = 1000000.0
+ var meanBest = 2
+ var rmsBest = 2
+ for (i in 0..diaDeviations.size-1)
+ {
+ val meanDeviations = diaDeviations[i].meanDeviation
+ val rmsDeviations = diaDeviations[i].rmsDeviation
+ if (meanDeviations < minMeanDeviations)
+ {
+ minMeanDeviations = Round.roundTo(meanDeviations, 0.001)
+ meanBest = i
+ }
+ if (rmsDeviations < minRmsDeviations)
+ {
+ minRmsDeviations = Round.roundTo(rmsDeviations, 0.001)
+ rmsBest = i
+ }
+ }
+ log("Best insulinEndTime for meanDeviations: ${diaDeviations[meanBest].dia} hours")
+ log("Best insulinEndTime for RMSDeviations: ${diaDeviations[rmsBest].dia} hours")
+ if (meanBest < 2 && rmsBest < 2)
+ {
+ if (diaDeviations[1].meanDeviation < currentDiaMeanDev * 0.99 && diaDeviations[1].rmsDeviation < currentDiaRMSDev * 0.99)
+ newDia = diaDeviations[1].dia
+ }
+ else if (meanBest > 2 && rmsBest > 2)
+ {
+ if (diaDeviations[3].meanDeviation < currentDiaMeanDev * 0.99 && diaDeviations[3].rmsDeviation < currentDiaRMSDev * 0.99)
+ newDia = diaDeviations[3].dia
+ }
+ if (newDia > 12.0)
+ {
+ log("insulinEndTime maximum is 12h: not raising further")
+ newDia = 12.0
+ }
+ if (newDia != dia)
+ log("Adjusting insulinEndTime from $dia to $newDia hours")
+ else
+ log("Leaving insulinEndTime unchanged at $dia hours")
+ }
+
+ // tune insulinPeakTime
+ var newPeak = peak
+ if (peakDeviations.size > 2)
+ {
+ val currentPeakMeanDev = peakDeviations[2].meanDeviation
+ val currentPeakRMSDev = peakDeviations[2].rmsDeviation
+ //Console.WriteLine(currentPeakMeanDev);
+ var minMeanDeviations = 1000000.0
+ var minRmsDeviations = 1000000.0
+ var meanBest = 2
+ var rmsBest = 2
+ for (i in 0..peakDeviations.size-1)
+ {
+ val meanDeviations = peakDeviations[i].meanDeviation;
+ val rmsDeviations = peakDeviations[i].rmsDeviation;
+ if (meanDeviations < minMeanDeviations)
+ {
+ minMeanDeviations = Round.roundTo(meanDeviations, 0.001)
+ meanBest = i
+ }
+ if (rmsDeviations < minRmsDeviations)
+ {
+ minRmsDeviations = Round.roundTo(rmsDeviations, 0.001)
+ rmsBest = i
+ }
+ }
+ log("Best insulinPeakTime for meanDeviations: ${peakDeviations[meanBest].peak} minutes")
+ log("Best insulinPeakTime for RMSDeviations: ${peakDeviations[rmsBest].peak} minutes")
+ if (meanBest < 2 && rmsBest < 2)
+ {
+ if (peakDeviations[1].meanDeviation < currentPeakMeanDev * 0.99 && peakDeviations[1].rmsDeviation < currentPeakRMSDev * 0.99)
+ newPeak = peakDeviations[1].peak
+ }
+ else if (meanBest > 2 && rmsBest > 2)
+ {
+ if (peakDeviations[3].meanDeviation < currentPeakMeanDev * 0.99 && peakDeviations[3].rmsDeviation < currentPeakRMSDev * 0.99)
+ newPeak = peakDeviations[3].peak
+ }
+ if (newPeak != peak)
+ log("Adjusting insulinPeakTime from " + peak + " to " + newPeak + " minutes")
+ else
+ log("Leaving insulinPeakTime unchanged at " + peak)
+ }
+
+ // Calculate carb ratio (CR) independently of csf and isf
+ // Use the time period from meal bolus/carbs until COB is zero and IOB is < currentBasal/2
+ // For now, if another meal IOB/COB stacks on top of it, consider them together
+ // Compare beginning and ending BGs, and calculate how much more/less insulin is needed to neutralize
+ // Use entered carbs vs. starting IOB + delivered insulin + needed-at-end insulin to directly calculate CR.
+
+ //autotune-core (lib/autotune/index.js) #149-#165
+ var crTotalCarbs = 0.0
+ var crTotalInsulin = 0.0
+ for (i in crData.indices) {
+ val crDatum = crData[i]
+ val crBGChange = crDatum.crEndBG - crDatum.crInitialBG
+ val crInsulinReq = crBGChange / isf
+ //val crIOBChange = crDatum.crEndIOB - crDatum.crInitialIOB
+ crDatum.crInsulinTotal = crDatum.crInitialIOB + crDatum.crInsulin + crInsulinReq
+ //log(crDatum.crInitialIOB + " " + crDatum.crInsulin + " " + crInsulinReq + " " + crDatum.crInsulinTotal);
+ //val cr = Round.roundTo(crDatum.crCarbs / crDatum.crInsulinTotal, 0.001)
+ //log(crBGChange + " " + crInsulinReq + " " + crIOBChange + " " + crDatum.crInsulinTotal);
+ //log("CRCarbs: " + crDatum.crCarbs + " CRInsulin: " + crDatum.crInsulinTotal + " CR:" + cr);
+ if (crDatum.crInsulinTotal > 0) {
+ crTotalCarbs += crDatum.crCarbs
+ crTotalInsulin += crDatum.crInsulinTotal
+ }
+ }
+
+ //autotune-core (lib/autotune/index.js) #166-#169
+ crTotalInsulin = Round.roundTo(crTotalInsulin, 0.001)
+ var totalCR = 0.0
+ if (crTotalInsulin != 0.0)
+ totalCR = Round.roundTo(crTotalCarbs / crTotalInsulin, 0.001)
+ log("crTotalCarbs: $crTotalCarbs crTotalInsulin: $crTotalInsulin totalCR: $totalCR")
+
+ //autotune-core (lib/autotune/index.js) #170-#209 (already hourly in aaps)
+ // convert the basal profile to hourly if it isn't already
+ val hourlyBasalProfile = basalProfile
+
+ //log(hourlyPumpProfile.toString());
+ //log(hourlyBasalProfile.toString());
+ val newHourlyBasalProfile = DoubleArray(24)
+ for (i in 0..23) {
+ newHourlyBasalProfile[i] = hourlyBasalProfile[i]
+ }
+ val basalUntuned = previousAutotune.basalUntuned
+
+ //autotune-core (lib/autotune/index.js) #210-#266
+ // look at net deviations for each hour
+ for (hour in 0..23) {
+ var deviations = 0.0
+ for (i in basalGlucose.indices) {
+ val BGTime = Calendar.getInstance()
+ //var BGTime: Date? = null
+ if (basalGlucose[i].date != 0L) {
+ BGTime.setTimeInMillis(basalGlucose[i].date)
+ //BGTime = Date(basalGlucose[i].date)
+ } else {
+ log("Could not determine last BG time")
+ }
+ val myHour = BGTime.get(Calendar.HOUR_OF_DAY)
+ //val myHour = BGTime!!.hours
+ if (hour == myHour) {
+ //log.debug(basalGlucose[i].deviation);
+ deviations += basalGlucose[i].deviation
+ }
+ }
+ deviations = Round.roundTo(deviations, 0.001)
+ log("Hour $hour total deviations: $deviations mg/dL")
+ // calculate how much less or additional basal insulin would have been required to eliminate the deviations
+ // only apply 20% of the needed adjustment to keep things relatively stable
+ var basalNeeded = 0.2 * deviations / isf
+ basalNeeded = Round.roundTo(basalNeeded, 0.01)
+ // if basalNeeded is positive, adjust each of the 1-3 hour prior basals by 10% of the needed adjustment
+ log("Hour $hour basal adjustment needed: $basalNeeded U/hr")
+ if (basalNeeded > 0) {
+ for (offset in -3..-1) {
+ var offsetHour = hour + offset
+ if (offsetHour < 0) {
+ offsetHour += 24
+ }
+ //log.debug(offsetHour);
+ newHourlyBasalProfile[offsetHour] = newHourlyBasalProfile[offsetHour] + basalNeeded / 3
+ newHourlyBasalProfile[offsetHour] = Round.roundTo(newHourlyBasalProfile[offsetHour], 0.001)
+ }
+ // otherwise, figure out the percentage reduction required to the 1-3 hour prior basals
+ // and adjust all of them downward proportionally
+ } else if (basalNeeded < 0) {
+ var threeHourBasal = 0.0
+ for (offset in -3..-1) {
+ var offsetHour = hour + offset
+ if (offsetHour < 0) {
+ offsetHour += 24
+ }
+ threeHourBasal += newHourlyBasalProfile[offsetHour]
+ }
+ val adjustmentRatio = 1.0 + basalNeeded / threeHourBasal
+ //log.debug(adjustmentRatio);
+ for (offset in -3..-1) {
+ var offsetHour = hour + offset
+ if (offsetHour < 0) {
+ offsetHour += 24
+ }
+ newHourlyBasalProfile[offsetHour] = newHourlyBasalProfile[offsetHour] * adjustmentRatio
+ newHourlyBasalProfile[offsetHour] = Round.roundTo(newHourlyBasalProfile[offsetHour], 0.001)
+ }
+ }
+ }
+ //autotune-core (lib/autotune/index.js) #267-#294
+ for (hour in 0..23) {
+ //log.debug(newHourlyBasalProfile[hour],hourlyPumpProfile[hour].rate*1.2);
+ // cap adjustments at autosens_max and autosens_min
+ val maxRate = pumpBasalProfile[hour] * autotuneMax
+ val minRate = pumpBasalProfile[hour] * autotuneMin
+ if (newHourlyBasalProfile[hour] > maxRate) {
+ log("Limiting hour " + hour + " basal to " + Round.roundTo(maxRate, 0.01) + " (which is " + Round.roundTo(autotuneMax, 0.01) + " * pump basal of " + pumpBasalProfile[hour] + ")")
+ //log.debug("Limiting hour",hour,"basal to",maxRate.toFixed(2),"(which is 20% above pump basal of",hourlyPumpProfile[hour].rate,")");
+ newHourlyBasalProfile[hour] = maxRate
+ } else if (newHourlyBasalProfile[hour] < minRate) {
+ log("Limiting hour " + hour + " basal to " + Round.roundTo(minRate, 0.01) + " (which is " + autotuneMin + " * pump basal of " + newHourlyBasalProfile[hour] + ")")
+ //log.debug("Limiting hour",hour,"basal to",minRate.toFixed(2),"(which is 20% below pump basal of",hourlyPumpProfile[hour].rate,")");
+ newHourlyBasalProfile[hour] = minRate
+ }
+ newHourlyBasalProfile[hour] = Round.roundTo(newHourlyBasalProfile[hour], 0.001)
+ }
+
+ // some hours of the day rarely have data to tune basals due to meals.
+ // when no adjustments are needed to a particular hour, we should adjust it toward the average of the
+ // periods before and after it that do have data to be tuned
+ var lastAdjustedHour = 0
+ // scan through newHourlyBasalProfile and find hours where the rate is unchanged
+ //autotune-core (lib/autotune/index.js) #302-#323
+ for (hour in 0..23) {
+ if (hourlyBasalProfile[hour] == newHourlyBasalProfile[hour]) {
+ var nextAdjustedHour = 23
+ for (nextHour in hour..23) {
+ if (hourlyBasalProfile[nextHour] != newHourlyBasalProfile[nextHour]) {
+ nextAdjustedHour = nextHour
+ break
+ //} else {
+ // log("At hour: "+nextHour +" " + hourlyBasalProfile[nextHour] + " " +newHourlyBasalProfile[nextHour]);
+ }
+ }
+ //log.debug(hour, newHourlyBasalProfile);
+ newHourlyBasalProfile[hour] = Round.roundTo(0.8 * hourlyBasalProfile[hour] + 0.1 * newHourlyBasalProfile[lastAdjustedHour] + 0.1 * newHourlyBasalProfile[nextAdjustedHour], 0.001)
+ basalUntuned[hour]++
+ log("Adjusting hour " + hour + " basal from " + hourlyBasalProfile[hour] + " to " + newHourlyBasalProfile[hour] + " based on hour " + lastAdjustedHour + " = " + newHourlyBasalProfile[lastAdjustedHour] + " and hour " + nextAdjustedHour + " = " + newHourlyBasalProfile[nextAdjustedHour])
+ } else {
+ lastAdjustedHour = hour
+ }
+ }
+ //log(newHourlyBasalProfile.toString());
+ basalProfile = newHourlyBasalProfile
+
+ // Calculate carb ratio (CR) independently of csf and isf
+ // Use the time period from meal bolus/carbs until COB is zero and IOB is < currentBasal/2
+ // For now, if another meal IOB/COB stacks on top of it, consider them together
+ // Compare beginning and ending BGs, and calculate how much more/less insulin is needed to neutralize
+ // Use entered carbs vs. starting IOB + delivered insulin + needed-at-end insulin to directly calculate CR.
+
+ // calculate net deviations while carbs are absorbing
+ // measured from carb entry until COB and deviations both drop to zero
+ var deviations = 0.0
+ var mealCarbs = 0
+ var totalMealCarbs = 0
+ var totalDeviations = 0.0
+ val fullNewCSF: Double
+ //log.debug(CSFGlucose[0].mealAbsorption);
+ //log.debug(CSFGlucose[0]);
+ //autotune-core (lib/autotune/index.js) #346-#365
+ for (i in csfGlucose.indices) {
+ //log.debug(CSFGlucose[i].mealAbsorption, i);
+ if (csfGlucose[i].mealAbsorption === "start") {
+ deviations = 0.0
+ mealCarbs = csfGlucose[i].mealCarbs
+ } else if (csfGlucose[i].mealAbsorption === "end") {
+ deviations += csfGlucose[i].deviation
+ // compare the sum of deviations from start to end vs. current csf * mealCarbs
+ //log.debug(csf,mealCarbs);
+ //val csfRise = csf * mealCarbs
+ //log.debug(deviations,isf);
+ //log.debug("csfRise:",csfRise,"deviations:",deviations);
+ totalMealCarbs += mealCarbs
+ totalDeviations += deviations
+ } else {
+ //todo Philoul check 0 * min5minCarbImpact ???
+ deviations += Math.max(0 * min5minCarbImpact, csfGlucose[i].deviation)
+ mealCarbs = Math.max(mealCarbs, csfGlucose[i].mealCarbs)
+ }
+ }
+ // at midnight, write down the mealcarbs as total meal carbs (to prevent special case of when only one meal and it not finishing absorbing by midnight)
+ // TODO: figure out what to do with dinner carbs that don't finish absorbing by midnight
+ if (totalMealCarbs == 0) {
+ totalMealCarbs += mealCarbs
+ }
+ if (totalDeviations == 0.0) {
+ totalDeviations += deviations
+ }
+ //log.debug(totalDeviations, totalMealCarbs);
+ fullNewCSF = if (totalMealCarbs == 0) {
+ // if no meals today, csf is unchanged
+ csf
+ } else {
+ // how much change would be required to account for all of the deviations
+ Round.roundTo(totalDeviations / totalMealCarbs, 0.01)
+ }
+ // only adjust by 20%
+ var newCSF = 0.8 * csf + 0.2 * fullNewCSF
+ // safety cap csf
+ if (pumpCSF != 0.0) {
+ val maxCSF = pumpCSF * autotuneMax
+ val minCSF = pumpCSF * autotuneMin
+ if (newCSF > maxCSF) {
+ log("Limiting csf to " + Round.roundTo(maxCSF, 0.01) + " (which is " + autotuneMax + "* pump csf of " + pumpCSF + ")")
+ newCSF = maxCSF
+ } else if (newCSF < minCSF) {
+ log("Limiting csf to " + Round.roundTo(minCSF, 0.01) + " (which is" + autotuneMin + "* pump csf of " + pumpCSF + ")")
+ newCSF = minCSF
+ } //else { log.debug("newCSF",newCSF,"is close enough to",pumpCSF); }
+ }
+ val oldCSF = Round.roundTo(csf, 0.001)
+ newCSF = Round.roundTo(newCSF, 0.001)
+ totalDeviations = Round.roundTo(totalDeviations, 0.001)
+ log("totalMealCarbs: $totalMealCarbs totalDeviations: $totalDeviations oldCSF $oldCSF fullNewCSF: $fullNewCSF newCSF: $newCSF")
+ // this is where csf is set based on the outputs
+ //if (newCSF != 0.0) {
+ // csf = newCSF
+ //}
+ var fullNewCR: Double
+ fullNewCR = if (totalCR == 0.0) {
+ // if no meals today, CR is unchanged
+ carbRatio
+ } else {
+ // how much change would be required to account for all of the deviations
+ totalCR
+ }
+ // don't tune CR out of bounds
+ var maxCR = pumpCarbRatio * autotuneMax
+ if (maxCR > 150) {
+ maxCR = 150.0
+ }
+ var minCR = pumpCarbRatio * autotuneMin
+ if (minCR < 3) {
+ minCR = 3.0
+ }
+ // safety cap fullNewCR
+ if (pumpCarbRatio != 0.0) {
+ if (fullNewCR > maxCR) {
+ log("Limiting fullNewCR from " + fullNewCR + " to " + Round.roundTo(maxCR, 0.01) + " (which is " + autotuneMax + " * pump CR of " + pumpCarbRatio + ")")
+ fullNewCR = maxCR
+ } else if (fullNewCR < minCR) {
+ log("Limiting fullNewCR from " + fullNewCR + " to " + Round.roundTo(minCR, 0.01) + " (which is " + autotuneMin + " * pump CR of " + pumpCarbRatio + ")")
+ fullNewCR = minCR
+ } //else { log.debug("newCR",newCR,"is close enough to",pumpCarbRatio); }
+ }
+ // only adjust by 20%
+ var newCR = 0.8 * carbRatio + 0.2 * fullNewCR
+ // safety cap newCR
+ if (pumpCarbRatio != 0.0) {
+ if (newCR > maxCR) {
+ log("Limiting CR to " + Round.roundTo(maxCR, 0.01) + " (which is " + autotuneMax + " * pump CR of " + pumpCarbRatio + ")")
+ newCR = maxCR
+ } else if (newCR < minCR) {
+ log("Limiting CR to " + Round.roundTo(minCR, 0.01) + " (which is " + autotuneMin + " * pump CR of " + pumpCarbRatio + ")")
+ newCR = minCR
+ } //else { log.debug("newCR",newCR,"is close enough to",pumpCarbRatio); }
+ }
+ newCR = Round.roundTo(newCR, 0.001)
+ log("oldCR: $carbRatio fullNewCR: $fullNewCR newCR: $newCR")
+ // this is where CR is set based on the outputs
+ //var ISFFromCRAndCSF = isf;
+ if (newCR != 0.0) {
+ carbRatio = newCR
+ //ISFFromCRAndCSF = Math.round( carbRatio * csf * 1000)/1000;
+ }
+
+ // calculate median deviation and bgi in data attributable to isf
+ val isfDeviations: MutableList = ArrayList()
+ val bGIs: MutableList = ArrayList()
+ val avgDeltas: MutableList = ArrayList()
+ val ratios: MutableList = ArrayList()
+ var count = 0
+ for (i in isfGlucose.indices) {
+ val deviation = isfGlucose[i].deviation
+ isfDeviations.add(deviation)
+ val BGI = isfGlucose[i].bgi
+ bGIs.add(BGI)
+ val avgDelta = isfGlucose[i].avgDelta
+ avgDeltas.add(avgDelta)
+ val ratio = 1 + deviation / BGI
+ //log.debug("Deviation:",deviation,"BGI:",BGI,"avgDelta:",avgDelta,"ratio:",ratio);
+ ratios.add(ratio)
+ count++
+ }
+ Collections.sort(avgDeltas)
+ Collections.sort(bGIs)
+ Collections.sort(isfDeviations)
+ Collections.sort(ratios)
+ var p50deviation = IobCobCalculatorPlugin.percentile(isfDeviations.toTypedArray(), 0.50)
+ var p50BGI = IobCobCalculatorPlugin.percentile(bGIs.toTypedArray(), 0.50)
+ val p50ratios = Round.roundTo(IobCobCalculatorPlugin.percentile(ratios.toTypedArray(), 0.50), 0.001)
+ var fullNewISF = isf
+ if (count < 10) {
+ // leave isf unchanged if fewer than 5 isf data points
+ log("Only found " + isfGlucose.size + " ISF data points, leaving ISF unchanged at " + isf)
+ } else {
+ // calculate what adjustments to isf would have been necessary to bring median deviation to zero
+ fullNewISF = isf * p50ratios
+ }
+ fullNewISF = Round.roundTo(fullNewISF, 0.001)
+ // adjust the target isf to be a weighted average of fullNewISF and pumpISF
+ val adjustmentFraction: Double
+ /*
+ // TODO: philoul may be allow adjustmentFraction in settings with safety limits ?)
+ if (typeof(pumpProfile.autotune_isf_adjustmentFraction) !== 'undefined') {
+ adjustmentFraction = pumpProfile.autotune_isf_adjustmentFraction;
+ } else {*/
+ adjustmentFraction = 1.0
+ // }
+
+ // low autosens ratio = high isf
+ val maxISF = pumpISF / autotuneMin
+ // high autosens ratio = low isf
+ val minISF = pumpISF / autotuneMax
+ var adjustedISF = 0.0
+ var newISF = 0.0
+ if (pumpISF != 0.0) {
+ adjustedISF = if (fullNewISF < 0) {
+ isf
+ } else {
+ adjustmentFraction * fullNewISF + (1 - adjustmentFraction) * pumpISF
+ }
+ // cap adjustedISF before applying 10%
+ //log.debug(adjustedISF, maxISF, minISF);
+ if (adjustedISF > maxISF) {
+ log("Limiting adjusted isf of " + Round.roundTo(adjustedISF, 0.01) + " to " + Round.roundTo(maxISF, 0.01) + "(which is pump isf of " + pumpISF + "/" + autotuneMin + ")")
+ adjustedISF = maxISF
+ } else if (adjustedISF < minISF) {
+ log("Limiting adjusted isf of" + Round.roundTo(adjustedISF, 0.01) + " to " + Round.roundTo(minISF, 0.01) + "(which is pump isf of " + pumpISF + "/" + autotuneMax + ")")
+ adjustedISF = minISF
+ }
+
+ // and apply 20% of that adjustment
+ newISF = 0.8 * isf + 0.2 * adjustedISF
+ if (newISF > maxISF) {
+ log("Limiting isf of" + Round.roundTo(newISF, 0.01) + "to" + Round.roundTo(maxISF, 0.01) + "(which is pump isf of" + pumpISF + "/" + autotuneMin + ")")
+ newISF = maxISF
+ } else if (newISF < minISF) {
+ log("Limiting isf of" + Round.roundTo(newISF, 0.01) + "to" + Round.roundTo(minISF, 0.01) + "(which is pump isf of" + pumpISF + "/" + autotuneMax + ")")
+ newISF = minISF
+ }
+ }
+ newISF = Round.roundTo(newISF, 0.001)
+ //log.debug(avgRatio);
+ //log.debug(newISF);
+ p50deviation = Round.roundTo(p50deviation, 0.001)
+ p50BGI = Round.roundTo(p50BGI, 0.001)
+ adjustedISF = Round.roundTo(adjustedISF, 0.001)
+ log("p50deviation: $p50deviation p50BGI $p50BGI p50ratios: $p50ratios Old isf: $isf fullNewISF: $fullNewISF adjustedISF: $adjustedISF newISF: $newISF")
+ if (newISF != 0.0) {
+ isf = newISF
+ }
+ previousAutotune.from = preppedGlucose.from
+ previousAutotune.basal = basalProfile
+ previousAutotune.isf = isf
+ previousAutotune.ic = Round.roundTo(carbRatio, 0.001)
+ previousAutotune.basalUntuned = basalUntuned
+ previousAutotune.dia = newDia
+ previousAutotune.peak = newPeak
+ val localInsulin = LocalInsulin("Ins_$newPeak-$newDia", newPeak, newDia)
+ previousAutotune.localInsulin = localInsulin
+ previousAutotune.updateProfile()
+ return previousAutotune
+ }
+
+ private fun log(message: String) {
+ autotuneFS.atLog("[Core] $message")
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/AutotuneFS.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/AutotuneFS.kt
new file mode 100644
index 0000000000..5f1d3a9959
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/AutotuneFS.kt
@@ -0,0 +1,204 @@
+package info.nightscout.androidaps.plugins.general.autotune
+
+import info.nightscout.androidaps.interfaces.ResourceHelper
+import info.nightscout.androidaps.plugins.general.autotune.data.ATProfile
+import info.nightscout.androidaps.plugins.general.autotune.data.PreppedGlucose
+import info.nightscout.androidaps.plugins.general.maintenance.LoggerUtils
+import info.nightscout.androidaps.R
+import org.json.JSONException
+import org.slf4j.LoggerFactory
+import java.io.*
+import java.text.SimpleDateFormat
+import java.util.zip.ZipEntry
+import java.util.zip.ZipOutputStream
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class AutotuneFS @Inject constructor(
+ private val rh: ResourceHelper,
+ private val loggerUtils: LoggerUtils
+ ) {
+
+ val AUTOTUNEFOLDER = "autotune"
+ val SETTINGSFOLDER = "settings"
+ val RECOMMENDATIONS = "autotune_recommendations.log"
+ val ENTRIESPREF = "aaps-entries."
+ val TREATMENTSPREF = "aaps-treatments."
+ val AAPSBOLUSESPREF = "aaps-boluses."
+ val PREPPEDPREF = "aaps-autotune."
+ val SETTINGS = "settings.json"
+ val PROFIL = "profil"
+ val PUMPPROFILE = "pumpprofile.json"
+ val TUNEDPROFILE = "newaapsprofile."
+ val LOGPREF = "autotune."
+ val ZIPPREF = "autotune_"
+ lateinit var autotunePath: File
+ lateinit var autotuneSettings: File
+ private var logString = ""
+ val BUFFER_SIZE = 2048
+ private val log = LoggerFactory.getLogger(AutotunePlugin::class.java)
+
+ /*****************************************************************************
+ * Create autotune folder for all files created during an autotune session
+ *****************************************************************************/
+ fun createAutotuneFolder() {
+ //create autotune subfolder for autotune files if not exists
+ autotunePath = File(loggerUtils.logDirectory, AUTOTUNEFOLDER)
+ if (!(autotunePath.exists() && autotunePath.isDirectory)) {
+ autotunePath.mkdir()
+ log("Create $AUTOTUNEFOLDER subfolder in ${loggerUtils.logDirectory}")
+ }
+ autotuneSettings = File(loggerUtils.logDirectory, SETTINGSFOLDER)
+ if (!(autotuneSettings.exists() && autotuneSettings.isDirectory)) {
+ autotuneSettings.mkdir()
+ log("Create $SETTINGSFOLDER subfolder in ${loggerUtils.logDirectory}")
+ }
+ }
+
+ /*****************************************************************************
+ * between each run of autotune, clean autotune folder content
+ *****************************************************************************/
+ fun deleteAutotuneFiles() {
+ autotunePath.listFiles()?.let { listFiles ->
+ for (file in listFiles) {
+ if (file.isFile) file.delete()
+ }
+ }
+ autotuneSettings.listFiles()?.let { listFiles ->
+ for (file in listFiles) {
+ if (file.isFile) file.delete()
+ }
+ }
+ log("Delete previous Autotune files")
+ }
+
+ /*****************************************************************************
+ * Create a JSON autotune files or settings files
+ *****************************************************************************/
+ fun exportSettings(settings: String) {
+ createAutotunefile(SETTINGS, settings, true)
+ }
+
+ fun exportPumpProfile(profile: ATProfile) {
+ createAutotunefile(PUMPPROFILE, profile.profiletoOrefJSON(), true)
+ createAutotunefile(PUMPPROFILE, profile.profiletoOrefJSON())
+ }
+
+ fun exportTunedProfile(tunedProfile: ATProfile) {
+ createAutotunefile(TUNEDPROFILE + formatDate(tunedProfile.from) + ".json", tunedProfile.profiletoOrefJSON())
+ try {
+ createAutotunefile(rh.gs(R.string.autotune_tunedprofile_name) + ".json", tunedProfile.profiletoOrefJSON(), true)
+ } catch (e: JSONException) {
+ }
+ }
+
+ fun exportEntries(autotuneIob: AutotuneIob) {
+ try {
+ createAutotunefile(ENTRIESPREF + formatDate(autotuneIob.startBG) + ".json", autotuneIob.glucoseToJSON())
+ } catch (e: JSONException) {
+ }
+ }
+
+ fun exportTreatments(autotuneIob: AutotuneIob) {
+ try {
+ createAutotunefile(TREATMENTSPREF + formatDate(autotuneIob.startBG) + ".json", autotuneIob.nsHistoryToJSON())
+ createAutotunefile(AAPSBOLUSESPREF + formatDate(autotuneIob.startBG) + ".json", autotuneIob.bolusesToJSON())
+ } catch (e: JSONException) {
+ }
+ }
+
+ fun exportPreppedGlucose(preppedGlucose: PreppedGlucose) {
+ createAutotunefile(PREPPEDPREF + formatDate(preppedGlucose.from) + ".json", preppedGlucose.toString(2))
+ }
+
+ fun exportResult(result: String) {
+ createAutotunefile(RECOMMENDATIONS, result)
+ }
+
+ fun exportLog(lastRun: Long, index: Int = 0) {
+ val suffix = if (index == 0) "" else "_" + index
+ log("Create " + LOGPREF + formatDate(lastRun) + suffix + ".log" + " file in " + AUTOTUNEFOLDER + " folder")
+ createAutotunefile(LOGPREF + formatDate(lastRun) + suffix + ".log", logString)
+ logString = ""
+ }
+
+ fun exportLogAndZip(lastRun: Long) {
+ log("Create " + LOGPREF + formatDate(lastRun) + ".log" + " file in " + AUTOTUNEFOLDER + " folder")
+ createAutotunefile(LOGPREF + formatDate(lastRun) + ".log", logString)
+ zipAutotune(lastRun)
+ logString = ""
+ }
+
+ private fun createAutotunefile(fileName: String, stringFile: String, isSettingFile: Boolean = false) {
+ val autotuneFile = File(if (isSettingFile) autotuneSettings.absolutePath else autotunePath.absolutePath, fileName)
+ try {
+ val fw = FileWriter(autotuneFile)
+ val pw = PrintWriter(fw)
+ pw.println(stringFile)
+ pw.close()
+ fw.close()
+ log("Create " + fileName + " file in " + (if (isSettingFile) SETTINGSFOLDER else AUTOTUNEFOLDER) + " folder")
+ } catch (e: FileNotFoundException) {
+ //log.error("Unhandled exception", e);
+ } catch (e: IOException) {
+ //log.error("Unhandled exception", e);
+ }
+ }
+
+ /**********************************************************************************
+ * create a zip file with all autotune files and settings in autotune folder at the end of run
+ **********************************************************************************/
+ fun zipAutotune(lastRun: Long) {
+ try {
+ val zipFileName = ZIPPREF + formatDate(lastRun, true) + ".zip"
+ val zipFile = File(loggerUtils.logDirectory, zipFileName)
+ val out = ZipOutputStream(BufferedOutputStream(FileOutputStream(zipFile)))
+ zipDirectory(autotunePath, autotunePath.name, out)
+ zipDirectory(autotuneSettings, autotuneSettings.name, out)
+ out.flush()
+ out.close()
+ log("Create $zipFileName file in ${loggerUtils.logDirectory} folder")
+ } catch (e: IOException) {
+ //log.error("Unhandled exception", e);
+ }
+ }
+
+ private fun log(message: String) {
+ atLog("[FS] $message")
+ }
+
+ fun atLog(message: String) {
+ logString += "$message\n"
+ log.debug(message)
+ }
+
+ private fun zipDirectory(folder: File, parentFolder: String, out: ZipOutputStream) {
+ folder.listFiles()?.let { listFiles ->
+ for (file in listFiles) {
+ if (file.isDirectory) {
+ zipDirectory(file, parentFolder + "/" + file.name, out)
+ continue
+ }
+ try {
+ out.putNextEntry(ZipEntry(parentFolder + "/" + file.name))
+ val bis = BufferedInputStream(FileInputStream(file))
+ //long bytesRead = 0;
+ val bytesIn = ByteArray(BUFFER_SIZE)
+ var read: Int
+ while (bis.read(bytesIn).also { read = it } != -1) {
+ out.write(bytesIn, 0, read)
+ }
+ out.closeEntry()
+ } catch (e: IOException) {
+ //log.error("Unhandled exception", e);
+ }
+ }
+ }
+ }
+
+ private fun formatDate(date: Long, dateHour: Boolean = false): String {
+ val dateFormat = if (dateHour) SimpleDateFormat("yyyy-MM-dd_HH-mm-ss") else SimpleDateFormat("yyyy-MM-dd")
+ return dateFormat.format(date)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/AutotuneFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/AutotuneFragment.kt
new file mode 100644
index 0000000000..0f2834c90e
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/AutotuneFragment.kt
@@ -0,0 +1,544 @@
+package info.nightscout.androidaps.plugins.general.autotune
+
+import android.graphics.Paint
+import android.graphics.Typeface
+import android.os.Bundle
+import android.text.Editable
+import android.text.TextWatcher
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.AdapterView
+import android.widget.ArrayAdapter
+import android.widget.TableLayout
+import android.widget.TableRow
+import android.widget.TextView
+import dagger.android.HasAndroidInjector
+import dagger.android.support.DaggerFragment
+import info.nightscout.androidaps.Constants
+import info.nightscout.androidaps.R
+import info.nightscout.androidaps.data.LocalInsulin
+import info.nightscout.androidaps.data.ProfileSealed
+import info.nightscout.androidaps.database.entities.UserEntry
+import info.nightscout.androidaps.database.entities.ValueWithUnit
+import info.nightscout.androidaps.databinding.AutotuneFragmentBinding
+import info.nightscout.androidaps.dialogs.ProfileViewerDialog
+import info.nightscout.androidaps.extensions.runOnUiThread
+import info.nightscout.androidaps.extensions.toVisibility
+import info.nightscout.androidaps.interfaces.*
+import info.nightscout.androidaps.logging.UserEntryLogger
+import info.nightscout.androidaps.plugins.bus.RxBus
+import info.nightscout.androidaps.plugins.general.autotune.data.ATProfile
+import info.nightscout.androidaps.plugins.general.autotune.events.EventAutotuneUpdateGui
+import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin
+import info.nightscout.androidaps.plugins.profile.local.events.EventLocalProfileChanged
+import info.nightscout.androidaps.utils.DateUtil
+import info.nightscout.androidaps.utils.FabricPrivacy
+import info.nightscout.androidaps.utils.MidnightTime
+import info.nightscout.androidaps.utils.Round
+import info.nightscout.androidaps.utils.alertDialogs.OKDialog.showConfirmation
+import info.nightscout.androidaps.utils.rx.AapsSchedulers
+import info.nightscout.shared.SafeParse
+import info.nightscout.shared.sharedPreferences.SP
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.kotlin.plusAssign
+import org.json.JSONObject
+import java.text.DecimalFormat
+import javax.inject.Inject
+
+class AutotuneFragment : DaggerFragment() {
+
+ @Inject lateinit var profileFunction: ProfileFunction
+ @Inject lateinit var autotunePlugin: AutotunePlugin
+ @Inject lateinit var autotuneFS: AutotuneFS
+ @Inject lateinit var sp: SP
+ @Inject lateinit var dateUtil: DateUtil
+ @Inject lateinit var activePlugin: ActivePlugin
+ @Inject lateinit var localProfilePlugin: LocalProfilePlugin
+ @Inject lateinit var fabricPrivacy: FabricPrivacy
+ @Inject lateinit var uel: UserEntryLogger
+ @Inject lateinit var rh: ResourceHelper
+ @Inject lateinit var rxBus: RxBus
+ @Inject lateinit var injector: HasAndroidInjector
+ @Inject lateinit var aapsSchedulers: AapsSchedulers
+
+ private var disposable: CompositeDisposable = CompositeDisposable()
+
+ //private val log = LoggerFactory.getLogger(AutotunePlugin::class.java)
+ private var _binding: AutotuneFragmentBinding? = null
+ private lateinit var profileStore: ProfileStore
+ private var profileName = ""
+ private var profile: ATProfile? = null
+
+ // This property is only valid between onCreateView and
+ // onDestroyView.
+ private val binding get() = _binding!!
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
+ _binding = AutotuneFragmentBinding.inflate(inflater, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ sp.putBoolean(R.string.key_autotune_tune_insulin_curve, false) // put to false tune insulin curve
+ sp.putBoolean(R.string.key_autotune_additional_log, false) // put to false additional log
+ autotunePlugin.loadLastRun()
+ if (autotunePlugin.lastNbDays.isEmpty())
+ autotunePlugin.lastNbDays = sp.getInt(R.string.key_autotune_default_tune_days, 5).toString()
+ val defaultValue = sp.getInt(R.string.key_autotune_default_tune_days, 5).toDouble()
+ profileStore = activePlugin.activeProfileSource.profile ?: ProfileStore(injector, JSONObject(), dateUtil)
+ profileName = if (binding.profileList.text.toString() == rh.gs(R.string.active)) "" else binding.profileList.text.toString()
+ profileFunction.getProfile()?.let { currentProfile ->
+ profile = ATProfile(profileStore.getSpecificProfile(profileName)?.let { ProfileSealed.Pure(it) } ?: currentProfile, LocalInsulin(""), injector)
+ }
+
+ binding.tuneDays.setParams(
+ savedInstanceState?.getDouble("tunedays")
+ ?: defaultValue, 1.0, 30.0, 1.0, DecimalFormat("0"), false, null, textWatcher
+ )
+ binding.autotuneRun.setOnClickListener {
+ val daysBack = SafeParse.stringToInt(binding.tuneDays.text)
+ autotunePlugin.lastNbDays = daysBack.toString()
+ log("Run Autotune $profileName, $daysBack days")
+ Thread {
+ autotunePlugin.aapsAutotune(daysBack, false, profileName)
+ }.start()
+ updateGui()
+ }
+ binding.profileList.onItemClickListener = AdapterView.OnItemClickListener { _, _, _, _ ->
+ if (!autotunePlugin.calculationRunning) {
+ profileName = if (binding.profileList.text.toString() == rh.gs(R.string.active)) "" else binding.profileList.text.toString()
+ profileFunction.getProfile()?.let { currentProfile ->
+ profile = ATProfile(profileStore.getSpecificProfile(profileName)?.let { ProfileSealed.Pure(it) } ?: currentProfile, LocalInsulin(""), injector)
+ }
+ autotunePlugin.selectedProfile = profileName
+ resetParam()
+ }
+ updateGui()
+ }
+
+ binding.autotuneCopylocal.setOnClickListener {
+ val localName = rh.gs(R.string.autotune_tunedprofile_name) + " " + dateUtil.dateAndTimeString(autotunePlugin.lastRun)
+ val circadian = sp.getBoolean(R.string.key_autotune_circadian_ic_isf, false)
+ autotunePlugin.tunedProfile?.let { tunedProfile ->
+ showConfirmation(requireContext(),
+ rh.gs(R.string.autotune_copy_localprofile_button),
+ rh.gs(R.string.autotune_copy_local_profile_message) + "\n" + localName,
+ Runnable {
+ localProfilePlugin.addProfile(localProfilePlugin.copyFrom(tunedProfile.getProfile(circadian), localName))
+ rxBus.send(EventLocalProfileChanged())
+ uel.log(
+ UserEntry.Action.NEW_PROFILE,
+ UserEntry.Sources.Autotune,
+ ValueWithUnit.SimpleString(localName)
+ )
+ updateGui()
+ })
+ }
+ }
+
+ binding.autotuneUpdateProfile.setOnClickListener {
+ val localName = autotunePlugin.pumpProfile.profilename
+ showConfirmation(requireContext(),
+ rh.gs(R.string.autotune_update_input_profile_button),
+ rh.gs(R.string.autotune_update_local_profile_message, localName),
+ Runnable {
+ autotunePlugin.tunedProfile?.profilename = localName
+ autotunePlugin.updateProfile(autotunePlugin.tunedProfile)
+ autotunePlugin.updateButtonVisibility = View.GONE
+ autotunePlugin.saveLastRun()
+ uel.log(
+ UserEntry.Action.STORE_PROFILE,
+ UserEntry.Sources.Autotune,
+ ValueWithUnit.SimpleString(localName)
+ )
+ updateGui()
+ }
+ )
+ }
+
+ binding.autotuneRevertProfile.setOnClickListener {
+ val localName = autotunePlugin.pumpProfile.profilename
+ showConfirmation(requireContext(),
+ rh.gs(R.string.autotune_revert_input_profile_button),
+ rh.gs(R.string.autotune_revert_local_profile_message, localName),
+ Runnable {
+ autotunePlugin.tunedProfile?.profilename = ""
+ autotunePlugin.updateProfile(autotunePlugin.pumpProfile)
+ autotunePlugin.updateButtonVisibility = View.VISIBLE
+ autotunePlugin.saveLastRun()
+ uel.log(
+ UserEntry.Action.STORE_PROFILE,
+ UserEntry.Sources.Autotune,
+ ValueWithUnit.SimpleString(localName)
+ )
+ updateGui()
+ }
+ )
+ }
+
+ binding.autotuneCheckInputProfile.setOnClickListener {
+ val pumpProfile = profileFunction.getProfile()?.let { currentProfile ->
+ profileStore.getSpecificProfile(profileName)?.let { specificProfile ->
+ ATProfile(ProfileSealed.Pure(specificProfile), LocalInsulin(""), injector).also {
+ it.profilename = profileName
+ }
+ }
+ ?: ATProfile(currentProfile, LocalInsulin(""), injector).also {
+ it.profilename = profileFunction.getProfileName()
+ }
+ }
+ pumpProfile?.let {
+ ProfileViewerDialog().also { pvd ->
+ pvd.arguments = Bundle().also {
+ it.putLong("time", dateUtil.now())
+ it.putInt("mode", ProfileViewerDialog.Mode.CUSTOM_PROFILE.ordinal)
+ it.putString("customProfile", pumpProfile.profile.toPureNsJson(dateUtil).toString())
+ it.putString("customProfileUnits", profileFunction.getUnits().asText)
+ it.putString("customProfileName", pumpProfile.profilename)
+ }
+ }.show(childFragmentManager, "ProfileViewDialog")
+ }
+ }
+
+ binding.autotuneCompare.setOnClickListener {
+ val pumpProfile = autotunePlugin.pumpProfile
+ val circadian = sp.getBoolean(R.string.key_autotune_circadian_ic_isf, false)
+ val tunedProfile = if (circadian) autotunePlugin.tunedProfile?.circadianProfile else autotunePlugin.tunedProfile?.profile
+ ProfileViewerDialog().also { pvd ->
+ pvd.arguments = Bundle().also {
+ it.putLong("time", dateUtil.now())
+ it.putInt("mode", ProfileViewerDialog.Mode.PROFILE_COMPARE.ordinal)
+ it.putString("customProfile", pumpProfile.profile.toPureNsJson(dateUtil).toString())
+ it.putString("customProfile2", tunedProfile?.toPureNsJson(dateUtil).toString())
+ it.putString("customProfileUnits", profileFunction.getUnits().asText)
+ it.putString("customProfileName", pumpProfile.profilename + "\n" + rh.gs(R.string.autotune_tunedprofile_name))
+ }
+ }.show(childFragmentManager, "ProfileViewDialog")
+ }
+
+ binding.autotuneProfileswitch.setOnClickListener {
+ val tunedProfile = autotunePlugin.tunedProfile
+ autotunePlugin.updateProfile(tunedProfile)
+ val circadian = sp.getBoolean(R.string.key_autotune_circadian_ic_isf, false)
+ tunedProfile?.let { tunedP ->
+ tunedP.profileStore(circadian)?.let {
+ showConfirmation(requireContext(),
+ rh.gs(R.string.activate_profile) + ": " + tunedP.profilename + " ?",
+ Runnable {
+ uel.log(
+ UserEntry.Action.STORE_PROFILE,
+ UserEntry.Sources.Autotune,
+ ValueWithUnit.SimpleString(tunedP.profilename)
+ )
+ val now = dateUtil.now()
+ if (profileFunction.createProfileSwitch(
+ it,
+ profileName = tunedP.profilename,
+ durationInMinutes = 0,
+ percentage = 100,
+ timeShiftInHours = 0,
+ timestamp = now
+ )
+ ) {
+ uel.log(
+ UserEntry.Action.PROFILE_SWITCH,
+ UserEntry.Sources.Autotune,
+ "Autotune AutoSwitch",
+ ValueWithUnit.SimpleString(autotunePlugin.tunedProfile!!.profilename)
+ )
+ }
+ rxBus.send(EventLocalProfileChanged())
+ updateGui()
+ }
+ )
+ }
+ }
+ }
+
+ binding.tuneLastrun.setOnClickListener {
+ if (!autotunePlugin.calculationRunning) {
+ autotunePlugin.loadLastRun()
+ updateGui()
+ }
+ }
+ binding.tuneLastrun.paintFlags = binding.tuneLastrun.paintFlags or Paint.UNDERLINE_TEXT_FLAG
+ }
+
+ @Synchronized
+ override fun onResume() {
+ super.onResume()
+ disposable += rxBus
+ .toObservable(EventAutotuneUpdateGui::class.java)
+ .observeOn(aapsSchedulers.main)
+ .subscribe({ updateGui() }, fabricPrivacy::logException)
+ checkNewDay()
+ updateGui()
+ }
+
+ @Synchronized
+ override fun onPause() {
+ super.onPause()
+ disposable.clear()
+ }
+
+ @Synchronized
+ private fun updateGui() {
+ _binding ?: return
+ binding.tuneDays.value = autotunePlugin.lastNbDays.toDouble()
+ profileStore = activePlugin.activeProfileSource.profile ?: ProfileStore(injector, JSONObject(), dateUtil)
+ profileName = if (binding.profileList.text.toString() == rh.gs(R.string.active)) "" else binding.profileList.text.toString()
+ profileFunction.getProfile()?.let { currentProfile ->
+ profile = ATProfile(profileStore.getSpecificProfile(profileName)?.let { ProfileSealed.Pure(it) } ?: currentProfile, LocalInsulin(""), injector)
+ }
+ val profileList: ArrayList = profileStore.getProfileList()
+ profileList.add(0, rh.gs(R.string.active))
+ context?.let { context ->
+ binding.profileList.setAdapter(ArrayAdapter(context, R.layout.spinner_centered, profileList))
+ } ?: return
+ // set selected to actual profile
+ if (autotunePlugin.selectedProfile.isNotEmpty())
+ binding.profileList.setText(autotunePlugin.selectedProfile, false)
+ else {
+ binding.profileList.setText(profileList[0], false)
+ }
+ binding.autotuneRun.visibility = View.GONE
+ binding.autotuneCheckInputProfile.visibility = View.GONE
+ binding.autotuneCopylocal.visibility = View.GONE
+ binding.autotuneUpdateProfile.visibility = View.GONE
+ binding.autotuneRevertProfile.visibility = View.GONE
+ binding.autotuneProfileswitch.visibility = View.GONE
+ binding.autotuneCompare.visibility = View.GONE
+ when {
+ autotunePlugin.calculationRunning -> {
+ binding.tuneWarning.text = rh.gs(R.string.autotune_warning_during_run)
+ }
+
+ autotunePlugin.lastRunSuccess -> {
+ binding.autotuneCopylocal.visibility = View.VISIBLE
+ binding.autotuneUpdateProfile.visibility = autotunePlugin.updateButtonVisibility
+ binding.autotuneRevertProfile.visibility = if (autotunePlugin.updateButtonVisibility == View.VISIBLE) View.GONE else View.VISIBLE
+ binding.autotuneProfileswitch.visibility = View.VISIBLE
+ binding.tuneWarning.text = rh.gs(R.string.autotune_warning_after_run)
+ binding.autotuneCompare.visibility = View.VISIBLE
+ }
+
+ else -> {
+ binding.autotuneRun.visibility = (profile?.isValid == true).toVisibility()
+ binding.autotuneCheckInputProfile.visibility = View.VISIBLE
+ }
+ }
+ binding.tuneLastrun.text = dateUtil.dateAndTimeString(autotunePlugin.lastRun)
+ showResults()
+ }
+
+ private fun checkNewDay() {
+ val runToday = autotunePlugin.lastRun > MidnightTime.calc(dateUtil.now() - autotunePlugin.autotuneStartHour * 3600 * 1000L) + autotunePlugin.autotuneStartHour * 3600 * 1000L
+ if (runToday && autotunePlugin.result != "") {
+ binding.tuneWarning.text = rh.gs(R.string.autotune_warning_after_run)
+ } else if (!runToday || autotunePlugin.result.isEmpty()) { //if new day re-init result, default days, warning and button's visibility
+ resetParam(!runToday)
+ }
+ }
+
+ private fun addWarnings(): String {
+ var warning = ""
+ var nl = ""
+ if (profileFunction.getProfile() == null) {
+ warning = rh.gs(R.string.profileswitch_ismissing)
+ return warning
+ }
+ profileFunction.getProfile()?.let { currentProfile ->
+ profile = ATProfile(profileStore.getSpecificProfile(profileName)?.let { ProfileSealed.Pure(it) } ?: currentProfile, LocalInsulin(""), injector).also { profile ->
+ if (!profile.isValid) return rh.gs(R.string.autotune_profile_invalid)
+ if (profile.icSize > 1) {
+ warning += nl + rh.gs(R.string.autotune_ic_warning, profile.icSize, profile.ic)
+ nl = "\n"
+ }
+ if (profile.isfSize > 1) {
+ warning += nl + rh.gs(R.string.autotune_isf_warning, profile.isfSize, Profile.fromMgdlToUnits(profile.isf, profileFunction.getUnits()), profileFunction.getUnits().asText)
+ }
+ }
+ }
+ return warning
+ }
+
+ private fun resetParam(resetDay: Boolean = true) {
+ binding.tuneWarning.text = addWarnings()
+ if (resetDay)
+ autotunePlugin.lastNbDays = sp.getInt(R.string.key_autotune_default_tune_days, 5).toString()
+ autotunePlugin.result = ""
+ binding.autotuneResults.removeAllViews()
+ autotunePlugin.tunedProfile = null
+ autotunePlugin.lastRunSuccess = false
+ autotunePlugin.updateButtonVisibility = View.GONE
+ }
+
+ private val textWatcher = object : TextWatcher {
+ override fun afterTextChanged(s: Editable) {
+ updateGui()
+ }
+
+ override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
+ override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
+ if (binding.tuneDays.text.isNotEmpty()) {
+ try {
+ if (autotunePlugin.calculationRunning)
+ binding.tuneDays.value = autotunePlugin.lastNbDays.toDouble()
+ if (binding.tuneDays.value != autotunePlugin.lastNbDays.toDouble()) {
+ autotunePlugin.lastNbDays = binding.tuneDays.text
+ resetParam(false)
+ }
+ } catch (e: Exception) {
+ fabricPrivacy.logException(e)
+ }
+ }
+ }
+ }
+
+ private fun showResults() {
+ context?.let { context ->
+ runOnUiThread {
+ _binding?.let {
+ binding.autotuneResults.removeAllViews()
+ if (autotunePlugin.result.isNotBlank()) {
+ var toMgDl = 1.0
+ if (profileFunction.getUnits() == GlucoseUnit.MMOL) toMgDl = Constants.MMOLL_TO_MGDL
+ val isfFormat = if (profileFunction.getUnits() == GlucoseUnit.MMOL) "%.2f" else "%.1f"
+ binding.autotuneResults.addView(
+ TableLayout(context).also { layout ->
+ layout.addView(
+ TextView(context).apply {
+ text = autotunePlugin.result
+ setTypeface(typeface, Typeface.BOLD)
+ gravity = Gravity.CENTER_HORIZONTAL
+ setTextAppearance(android.R.style.TextAppearance_Material_Medium)
+ })
+ autotunePlugin.tunedProfile?.let { tuned ->
+ layout.addView(toTableRowHeader())
+ val tuneInsulin = sp.getBoolean(R.string.key_autotune_tune_insulin_curve, false)
+ if (tuneInsulin) {
+ layout.addView(
+ toTableRowValue(
+ rh.gs(R.string.insulin_peak),
+ autotunePlugin.pumpProfile.localInsulin.peak.toDouble(),
+ tuned.localInsulin.peak.toDouble(),
+ "%.0f"
+ )
+ )
+ layout.addView(
+ toTableRowValue(
+ rh.gs(R.string.dia),
+ Round.roundTo(autotunePlugin.pumpProfile.localInsulin.dia, 0.1),
+ Round.roundTo(tuned.localInsulin.dia, 0.1),
+ "%.1f"
+ )
+ )
+ }
+ layout.addView(
+ toTableRowValue(
+ rh.gs(R.string.isf_short),
+ Round.roundTo(autotunePlugin.pumpProfile.isf / toMgDl, 0.001),
+ Round.roundTo(tuned.isf / toMgDl, 0.001),
+ isfFormat
+ )
+ )
+ layout.addView(toTableRowValue(rh.gs(R.string.ic_short), Round.roundTo(autotunePlugin.pumpProfile.ic, 0.001), Round.roundTo(tuned.ic, 0.001), "%.2f"))
+ layout.addView(
+ TextView(context).apply {
+ text = rh.gs(R.string.basal)
+ setTypeface(typeface, Typeface.BOLD)
+ gravity = Gravity.CENTER_HORIZONTAL
+ setTextAppearance(android.R.style.TextAppearance_Material_Medium)
+ }
+ )
+ layout.addView(toTableRowHeader(true))
+ var totalPump = 0.0
+ var totalTuned = 0.0
+ for (h in 0 until tuned.basal.size) {
+ val df = DecimalFormat("00")
+ val time = df.format(h.toLong()) + ":00"
+ totalPump += autotunePlugin.pumpProfile.basal[h]
+ totalTuned += tuned.basal[h]
+ layout.addView(toTableRowValue(time, autotunePlugin.pumpProfile.basal[h], tuned.basal[h], "%.3f", tuned.basalUntuned[h].toString()))
+ }
+ layout.addView(toTableRowValue("∑", totalPump, totalTuned, "%.3f", " "))
+ }
+ }
+ )
+ }
+ binding.autotuneResultsCard.visibility = if (autotunePlugin.calculationRunning && autotunePlugin.result.isEmpty()) View.GONE else View.VISIBLE
+ }
+ }
+ }
+ }
+
+ private fun toTableRowHeader(basal: Boolean = false): TableRow =
+ TableRow(context).also { header ->
+ val lp = TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT, TableRow.LayoutParams.WRAP_CONTENT).apply { weight = 1f }
+ header.layoutParams = TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.WRAP_CONTENT).apply { gravity = Gravity.CENTER_HORIZONTAL }
+ header.addView(TextView(context).apply {
+ layoutParams = lp.apply { column = 0 }
+ textAlignment = TextView.TEXT_ALIGNMENT_CENTER
+ text = if (basal) rh.gs(R.string.time) else rh.gs(R.string.autotune_param)
+ })
+ header.addView(TextView(context).apply {
+ layoutParams = lp.apply { column = 1 }
+ textAlignment = TextView.TEXT_ALIGNMENT_CENTER
+ text = rh.gs(R.string.profile)
+ })
+ header.addView(TextView(context).apply {
+ layoutParams = lp.apply { column = 2 }
+ textAlignment = TextView.TEXT_ALIGNMENT_CENTER
+ text = rh.gs(R.string.autotune_tunedprofile_name)
+ })
+ header.addView(TextView(context).apply {
+ layoutParams = lp.apply { column = 3 }
+ textAlignment = TextView.TEXT_ALIGNMENT_CENTER
+ text = rh.gs(R.string.autotune_percent)
+ })
+ header.addView(TextView(context).apply {
+ layoutParams = lp.apply { column = 4 }
+ textAlignment = TextView.TEXT_ALIGNMENT_CENTER
+ text = if (basal) rh.gs(R.string.autotune_missing) else " "
+ })
+ }
+
+ private fun toTableRowValue(hour: String, inputValue: Double, tunedValue: Double, format: String = "%.3f", missing: String = ""): TableRow =
+ TableRow(context).also { row ->
+ val percentValue = Round.roundTo(tunedValue / inputValue * 100 - 100, 1.0).toInt().toString() + "%"
+ val lp = TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT, TableRow.LayoutParams.WRAP_CONTENT).apply { weight = 1f }
+ row.layoutParams = TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.WRAP_CONTENT).apply { gravity = Gravity.CENTER_HORIZONTAL }
+ row.addView(TextView(context).apply {
+ layoutParams = lp.apply { column = 0 }
+ textAlignment = TextView.TEXT_ALIGNMENT_CENTER
+ text = hour
+ })
+ row.addView(TextView(context).apply {
+ layoutParams = lp.apply { column = 1 }
+ textAlignment = TextView.TEXT_ALIGNMENT_CENTER
+ text = String.format(format, inputValue)
+ })
+ row.addView(TextView(context).apply {
+ layoutParams = lp.apply { column = 2 }
+ textAlignment = TextView.TEXT_ALIGNMENT_CENTER
+ text = String.format(format, tunedValue)
+ })
+ row.addView(TextView(context).apply {
+ layoutParams = lp.apply { column = 3 }
+ textAlignment = TextView.TEXT_ALIGNMENT_CENTER
+ text = percentValue
+ })
+ row.addView(TextView(context).apply {
+ layoutParams = lp.apply { column = 4 }
+ textAlignment = TextView.TEXT_ALIGNMENT_CENTER
+ text = missing
+ })
+ }
+
+ private fun log(message: String) {
+ autotuneFS.atLog("[Fragment] $message")
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/AutotuneIob.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/AutotuneIob.kt
new file mode 100644
index 0000000000..a634ee876c
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/AutotuneIob.kt
@@ -0,0 +1,398 @@
+package info.nightscout.androidaps.plugins.general.autotune
+
+import info.nightscout.androidaps.Constants
+import info.nightscout.androidaps.R
+import info.nightscout.androidaps.data.IobTotal
+import info.nightscout.androidaps.data.LocalInsulin
+import info.nightscout.androidaps.database.AppRepository
+import info.nightscout.androidaps.database.embedments.InterfaceIDs
+import info.nightscout.androidaps.database.entities.*
+import info.nightscout.androidaps.extensions.durationInMinutes
+import info.nightscout.androidaps.extensions.iobCalc
+import info.nightscout.androidaps.extensions.toJson
+import info.nightscout.androidaps.extensions.toTemporaryBasal
+import info.nightscout.androidaps.interfaces.ActivePlugin
+import info.nightscout.androidaps.interfaces.Profile
+import info.nightscout.androidaps.interfaces.ProfileFunction
+import info.nightscout.androidaps.plugins.general.autotune.data.ATProfile
+import info.nightscout.androidaps.utils.DateUtil
+import info.nightscout.androidaps.utils.Round
+import info.nightscout.androidaps.utils.T
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.shared.logging.LTag
+import info.nightscout.shared.sharedPreferences.SP
+import org.json.JSONArray
+import org.json.JSONObject
+import java.util.*
+import javax.inject.Inject
+import javax.inject.Singleton
+import kotlin.math.ceil
+
+@Singleton
+open class AutotuneIob @Inject constructor(
+ private val aapsLogger: AAPSLogger,
+ private val repository: AppRepository,
+ private val profileFunction: ProfileFunction,
+ private val sp: SP,
+ private val dateUtil: DateUtil,
+ private val activePlugin: ActivePlugin,
+ private val autotuneFS: AutotuneFS
+) {
+
+ private var nsTreatments = ArrayList()
+ private var dia: Double = Constants.defaultDIA
+ var boluses: ArrayList = ArrayList()
+ var meals = ArrayList()
+ lateinit var glucose: List // newest at index 0
+ private lateinit var tempBasals: ArrayList
+ var startBG: Long = 0
+ var endBG: Long = 0
+ private fun range(): Long = (60 * 60 * 1000L * dia + T.hours(2).msecs()).toLong()
+
+ fun initializeData(from: Long, to: Long, tunedProfile: ATProfile) {
+ dia = tunedProfile.dia
+ startBG = from
+ endBG = to
+ nsTreatments.clear()
+ tempBasals = ArrayList()
+ initializeBgreadings(from, to)
+ initializeTreatmentData(from - range(), to)
+ initializeTempBasalData(from - range(), to, tunedProfile)
+ initializeExtendedBolusData(from - range(), to, tunedProfile)
+ sortTempBasal()
+ addNeutralTempBasal(from - range(), to, tunedProfile) // Without Neutral TBR, Autotune Web will ignore iob for periods without TBR running
+ sortNsTreatments()
+ sortBoluses()
+ aapsLogger.debug(LTag.AUTOTUNE, "Nb Treatments: " + nsTreatments.size + " Nb meals: " + meals.size)
+ }
+
+ @Synchronized
+ private fun sortTempBasal() {
+ tempBasals = ArrayList(tempBasals.toList().sortedWith { o1: TemporaryBasal, o2: TemporaryBasal -> (o2.timestamp - o1.timestamp).toInt() })
+ }
+
+ @Synchronized
+ private fun sortNsTreatments() {
+ nsTreatments = ArrayList(nsTreatments.toList().sortedWith { o1: NsTreatment, o2: NsTreatment -> (o2.date - o1.date).toInt() })
+ }
+
+ @Synchronized
+ private fun sortBoluses() {
+ boluses = ArrayList(boluses.toList().sortedWith { o1: Bolus, o2: Bolus -> (o2.timestamp - o1.timestamp).toInt() })
+ }
+
+ private fun initializeBgreadings(from: Long, to: Long) {
+ glucose = repository.compatGetBgReadingsDataFromTime(from, to, false).blockingGet()
+ }
+
+ //nsTreatment is used only for export data, meals is used in AutotunePrep
+ private fun initializeTreatmentData(from: Long, to: Long) {
+ val oldestBgDate = if (glucose.isNotEmpty()) glucose[glucose.size - 1].timestamp else from
+ aapsLogger.debug(LTag.AUTOTUNE, "Check BG date: BG Size: " + glucose.size + " OldestBG: " + dateUtil.dateAndTimeAndSecondsString(oldestBgDate) + " to: " + dateUtil.dateAndTimeAndSecondsString(to))
+ val tmpCarbs = repository.getCarbsDataFromTimeToTimeExpanded(from, to, false).blockingGet()
+ aapsLogger.debug(LTag.AUTOTUNE, "Nb treatments after query: " + tmpCarbs.size)
+ meals.clear()
+ boluses.clear()
+ var nbCarbs = 0
+ for (i in tmpCarbs.indices) {
+ val tp = tmpCarbs[i]
+ if (tp.isValid) {
+ nsTreatments.add(NsTreatment(tp))
+ //only carbs after first BGReadings are taken into account in calculation of Autotune
+ if (tp.amount > 0.0 && tp.timestamp >= oldestBgDate) meals.add(tmpCarbs[i])
+ if (tp.timestamp < to && tp.amount > 0.0)
+ nbCarbs++
+ }
+ }
+ val tmpBolus = repository.getBolusesDataFromTimeToTime(from, to, false).blockingGet()
+ var nbSMB = 0
+ var nbBolus = 0
+ for (i in tmpBolus.indices) {
+ val tp = tmpBolus[i]
+ if (tp.isValid && tp.type != Bolus.Type.PRIMING) {
+ boluses.add(tp)
+ nsTreatments.add(NsTreatment(tp))
+ //only carbs after first BGReadings are taken into account in calculation of Autotune
+ if (tp.timestamp < to) {
+ if (tp.type == Bolus.Type.SMB)
+ nbSMB++
+ else if (tp.amount > 0.0)
+ nbBolus++
+ }
+ }
+ }
+ //log.debug("AutotunePlugin Nb Meals: $nbCarbs Nb Bolus: $nbBolus Nb SMB: $nbSMB")
+ }
+
+ //nsTreatment is used only for export data
+ private fun initializeTempBasalData(from: Long, to: Long, tunedProfile: ATProfile) {
+ val tBRs = repository.getTemporaryBasalsDataFromTimeToTime(from, to, false).blockingGet()
+ //log.debug("D/AutotunePlugin tempBasal size before cleaning:" + tBRs.size);
+ for (i in tBRs.indices) {
+ if (tBRs[i].isValid)
+ toSplittedTimestampTB(tBRs[i], tunedProfile)
+ }
+ //log.debug("D/AutotunePlugin: tempBasal size: " + tempBasals.size)
+ }
+
+ //nsTreatment is used only for export data
+ private fun initializeExtendedBolusData(from: Long, to: Long, tunedProfile: ATProfile) {
+ val extendedBoluses = repository.getExtendedBolusDataFromTimeToTime(from, to, false).blockingGet()
+ val pumpInterface = activePlugin.activePump
+ if (pumpInterface.isFakingTempsByExtendedBoluses) {
+ for (i in extendedBoluses.indices) {
+ val eb = extendedBoluses[i]
+ if (eb.isValid)
+ profileFunction.getProfile(eb.timestamp)?.let {
+ toSplittedTimestampTB(eb.toTemporaryBasal(it), tunedProfile)
+ }
+ }
+ } else {
+ for (i in extendedBoluses.indices) {
+ val eb = extendedBoluses[i]
+ if (eb.isValid) {
+ nsTreatments.add(NsTreatment(eb))
+ boluses.addAll(convertToBoluses(eb))
+ }
+ }
+ }
+ }
+
+ // addNeutralTempBasal will add a fake neutral TBR (100%) to have correct basal rate in exported file for periods without TBR running
+ // to be able to compare results between oref0 algo and aaps
+ @Synchronized
+ private fun addNeutralTempBasal(from: Long, to: Long, tunedProfile: ATProfile) {
+ var previousStart = to
+ for (i in tempBasals.indices) {
+ val newStart = tempBasals[i].timestamp + tempBasals[i].duration
+ if (previousStart - newStart > T.mins(1).msecs()) { // fill neutral only if more than 1 min
+ val neutralTbr = TemporaryBasal(
+ isValid = true,
+ isAbsolute = false,
+ timestamp = newStart,
+ rate = 100.0,
+ duration = previousStart - newStart,
+ interfaceIDs_backing = InterfaceIDs(nightscoutId = "neutral_" + newStart.toString()),
+ type = TemporaryBasal.Type.NORMAL
+ )
+ toSplittedTimestampTB(neutralTbr, tunedProfile)
+ }
+ previousStart = tempBasals[i].timestamp
+ }
+ if (previousStart - from > T.mins(1).msecs()) { // fill neutral only if more than 1 min
+ val neutralTbr = TemporaryBasal(
+ isValid = true,
+ isAbsolute = false,
+ timestamp = from,
+ rate = 100.0,
+ duration = previousStart - from,
+ interfaceIDs_backing = InterfaceIDs(nightscoutId = "neutral_" + from.toString()),
+ type = TemporaryBasal.Type.NORMAL
+ )
+ toSplittedTimestampTB(neutralTbr, tunedProfile)
+ }
+ }
+
+ // toSplittedTimestampTB will split all TBR across hours in different TBR with correct absolute value to be sure to have correct basal rate
+ // even if profile rate is not the same
+ @Synchronized
+ private fun toSplittedTimestampTB(tb: TemporaryBasal, tunedProfile: ATProfile) {
+ var splittedTimestamp = tb.timestamp
+ val cutInMilliSec = T.mins(60).msecs() //30 min to compare with oref0, 60 min to improve accuracy
+ var splittedDuration = tb.duration
+ if (tb.isValid && tb.durationInMinutes > 0) {
+ val endTimestamp = splittedTimestamp + splittedDuration
+ while (splittedDuration > 0) {
+ if (Profile.milliSecFromMidnight(splittedTimestamp) / cutInMilliSec == Profile.milliSecFromMidnight(endTimestamp) / cutInMilliSec) {
+ val newtb = TemporaryBasal(
+ isValid = true,
+ isAbsolute = tb.isAbsolute,
+ timestamp = splittedTimestamp,
+ rate = tb.rate,
+ duration = splittedDuration,
+ interfaceIDs_backing = tb.interfaceIDs_backing,
+ type = tb.type
+ )
+ tempBasals.add(newtb)
+ nsTreatments.add(NsTreatment(newtb))
+ splittedDuration = 0
+ val profile = profileFunction.getProfile(newtb.timestamp) ?:continue
+ boluses.addAll(convertToBoluses(newtb, profile, tunedProfile.profile)) //
+ // required for correct iob calculation with oref0 algo
+ } else {
+ val durationFilled = (cutInMilliSec - Profile.milliSecFromMidnight(splittedTimestamp) % cutInMilliSec)
+ val newtb = TemporaryBasal(
+ isValid = true,
+ isAbsolute = tb.isAbsolute,
+ timestamp = splittedTimestamp,
+ rate = tb.rate,
+ duration = durationFilled,
+ interfaceIDs_backing = tb.interfaceIDs_backing,
+ type = tb.type
+ )
+ tempBasals.add(newtb)
+ nsTreatments.add(NsTreatment(newtb))
+ splittedTimestamp += durationFilled
+ splittedDuration -= durationFilled
+ val profile = profileFunction.getProfile(newtb.timestamp) ?:continue
+ boluses.addAll(convertToBoluses(newtb, profile, tunedProfile.profile)) // required for correct iob calculation with oref0 algo
+ }
+ }
+ }
+ }
+
+ open fun getIOB(time: Long, localInsulin: LocalInsulin): IobTotal {
+ val bolusIob = getCalculationToTimeTreatments(time, localInsulin).round()
+ return bolusIob
+ }
+
+ fun getCalculationToTimeTreatments(time: Long, localInsulin: LocalInsulin): IobTotal {
+ val total = IobTotal(time)
+ val detailedLog = sp.getBoolean(R.string.key_autotune_additional_log, false)
+ for (pos in boluses.indices) {
+ val t = boluses[pos]
+ if (!t.isValid) continue
+ if (t.timestamp > time || t.timestamp < time - localInsulin.duration) continue
+ val tIOB = t.iobCalc(time, localInsulin)
+ if (detailedLog)
+ log("iobCalc;${t.interfaceIDs.nightscoutId};$time;${t.timestamp};${tIOB.iobContrib};${tIOB.activityContrib};${dateUtil.dateAndTimeAndSecondsString(time)};${dateUtil.dateAndTimeAndSecondsString(t.timestamp)}")
+ total.iob += tIOB.iobContrib
+ total.activity += tIOB.activityContrib
+ }
+ return total
+ }
+
+
+ fun convertToBoluses(eb: ExtendedBolus): MutableList {
+ val result: MutableList = ArrayList()
+ val aboutFiveMinIntervals = ceil(eb.duration / 5.0).toInt()
+ val spacing = eb.duration / aboutFiveMinIntervals.toDouble()
+ for (j in 0L until aboutFiveMinIntervals) {
+ // find middle of the interval
+ val calcDate = (eb.timestamp + j * spacing * 60 * 1000 + 0.5 * spacing * 60 * 1000).toLong()
+ val tempBolusSize: Double = eb.amount / aboutFiveMinIntervals
+ val bolusInterfaceIDs = InterfaceIDs().also { it.nightscoutId = eb.interfaceIDs.nightscoutId + "_eb_$j" }
+ val tempBolusPart = Bolus(
+ interfaceIDs_backing = bolusInterfaceIDs,
+ timestamp = calcDate,
+ amount = tempBolusSize,
+ type = Bolus.Type.NORMAL
+ )
+ result.add(tempBolusPart)
+ }
+ return result
+ }
+
+ fun convertToBoluses(tbr: TemporaryBasal, profile: Profile, tunedProfile: Profile): MutableList {
+ val result: MutableList = ArrayList()
+ val realDuration = tbr.durationInMinutes
+ val basalRate = profile.getBasal(tbr.timestamp)
+ val tunedRate = tunedProfile.getBasal(tbr.timestamp)
+ val netBasalRate = Round.roundTo(if (tbr.isAbsolute) {
+ tbr.rate - tunedRate
+ } else {
+ tbr.rate / 100.0 * basalRate - tunedRate
+ }, 0.001)
+ val aboutFiveMinIntervals = ceil(realDuration / 5.0).toInt()
+ val tempBolusSpacing = realDuration / aboutFiveMinIntervals.toDouble()
+ for (j in 0L until aboutFiveMinIntervals) {
+ // find middle of the interval
+ val calcDate = (tbr.timestamp + j * tempBolusSpacing * 60 * 1000 + 0.5 * tempBolusSpacing * 60 * 1000).toLong()
+ val tempBolusSize = netBasalRate * tempBolusSpacing / 60.0
+ val bolusInterfaceIDs = InterfaceIDs().also { it.nightscoutId = tbr.interfaceIDs.nightscoutId + "_tbr_$j" }
+ val tempBolusPart = Bolus(
+ interfaceIDs_backing = bolusInterfaceIDs,
+ timestamp = calcDate,
+ amount = tempBolusSize,
+ type = Bolus.Type.NORMAL
+ )
+ result.add(tempBolusPart)
+ }
+ return result
+ }
+
+ @Synchronized
+ fun glucoseToJSON(): String {
+ val glucoseJson = JSONArray()
+ for (bgreading in glucose)
+ glucoseJson.put(bgreading.toJson(true, dateUtil))
+ return glucoseJson.toString(2)
+ }
+
+ @Synchronized
+ fun bolusesToJSON(): String {
+ val bolusesJson = JSONArray()
+ for (bolus in boluses)
+ bolusesJson.put(bolus.toJson(true, dateUtil))
+ return bolusesJson.toString(2)
+ }
+
+ @Synchronized
+ fun nsHistoryToJSON(): String {
+ val json = JSONArray()
+ for (t in nsTreatments) {
+ json.put(t.toJson())
+ }
+ return json.toString(2).replace("\\/", "/")
+ }
+
+ //I add this internal class to be able to export easily ns-treatment files with same contain and format than NS query used by oref0-autotune
+ private inner class NsTreatment {
+
+ var date: Long = 0
+ var eventType: TherapyEvent.Type? = null
+ var carbsTreatment: Carbs? = null
+ var bolusTreatment: Bolus? = null
+ var temporaryBasal: TemporaryBasal? = null
+ var extendedBolus: ExtendedBolus? = null
+
+ constructor(t: Carbs) {
+ carbsTreatment = t
+ date = t.timestamp
+ eventType = TherapyEvent.Type.CARBS_CORRECTION
+ }
+
+ constructor(t: Bolus) {
+ bolusTreatment = t
+ date = t.timestamp
+ eventType = TherapyEvent.Type.CORRECTION_BOLUS
+ }
+
+ constructor(t: TemporaryBasal) {
+ temporaryBasal = t
+ date = t.timestamp
+ eventType = TherapyEvent.Type.TEMPORARY_BASAL
+ }
+
+ constructor(t: ExtendedBolus) {
+ extendedBolus = t
+ date = t.timestamp
+ eventType = TherapyEvent.Type.COMBO_BOLUS
+ }
+
+ fun toJson(): JSONObject? {
+ val cPjson = JSONObject()
+ return when (eventType) {
+ TherapyEvent.Type.TEMPORARY_BASAL ->
+ temporaryBasal?.let { tbr ->
+ val profile = profileFunction.getProfile(tbr.timestamp)
+ profile?.let {
+ tbr.toJson(true, it, dateUtil)
+ }
+ }
+ TherapyEvent.Type.COMBO_BOLUS ->
+ extendedBolus?.let {
+ val profile = profileFunction.getProfile(it.timestamp)
+ it.toJson(true, profile!!, dateUtil)
+ }
+ TherapyEvent.Type.CORRECTION_BOLUS -> bolusTreatment?.toJson(true, dateUtil)
+ TherapyEvent.Type.CARBS_CORRECTION -> carbsTreatment?.toJson(true, dateUtil)
+ else -> cPjson
+ }
+ }
+ }
+
+ private fun log(message: String) {
+ autotuneFS.atLog("[iob] $message")
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/AutotunePlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/AutotunePlugin.kt
new file mode 100644
index 0000000000..6a4f3d6a94
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/AutotunePlugin.kt
@@ -0,0 +1,390 @@
+package info.nightscout.androidaps.plugins.general.autotune
+
+import android.view.View
+import dagger.android.HasAndroidInjector
+import info.nightscout.androidaps.R
+import info.nightscout.androidaps.data.LocalInsulin
+import info.nightscout.androidaps.data.ProfileSealed
+import info.nightscout.androidaps.database.entities.UserEntry
+import info.nightscout.androidaps.database.entities.ValueWithUnit
+import info.nightscout.androidaps.extensions.pureProfileFromJson
+import info.nightscout.androidaps.interfaces.*
+import info.nightscout.androidaps.logging.UserEntryLogger
+import info.nightscout.androidaps.plugins.bus.RxBus
+import info.nightscout.androidaps.plugins.general.autotune.data.ATProfile
+import info.nightscout.androidaps.plugins.general.autotune.data.PreppedGlucose
+import info.nightscout.androidaps.plugins.general.autotune.events.EventAutotuneUpdateGui
+import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin
+import info.nightscout.androidaps.plugins.profile.local.events.EventLocalProfileChanged
+import info.nightscout.androidaps.utils.DateUtil
+import info.nightscout.androidaps.utils.JsonHelper
+import info.nightscout.androidaps.utils.MidnightTime
+import info.nightscout.androidaps.utils.T
+import info.nightscout.androidaps.interfaces.BuildHelper
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.shared.logging.LTag
+import info.nightscout.shared.sharedPreferences.SP
+import org.json.JSONException
+import org.json.JSONObject
+import java.util.*
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * adaptation from oref0 autotune started by philoul on 2020 (complete refactoring of AutotunePlugin initialised by Rumen Georgiev on 1/29/2018.)
+ *
+ * TODO: replace Thread by Worker
+ * TODO: future version: Allow day of the week selection to tune specifics days (training days, working days, WE days)
+ */
+
+@Singleton
+class AutotunePlugin @Inject constructor(
+ injector: HasAndroidInjector,
+ resourceHelper: ResourceHelper,
+ private val sp: SP,
+ private val rxBus: RxBus,
+ private val profileFunction: ProfileFunction,
+ private val dateUtil: DateUtil,
+ private val activePlugin: ActivePlugin,
+ private val localProfilePlugin: LocalProfilePlugin,
+ private val autotuneFS: AutotuneFS,
+ private val autotuneIob: AutotuneIob,
+ private val autotunePrep: AutotunePrep,
+ private val autotuneCore: AutotuneCore,
+ private val buildHelper: BuildHelper,
+ private val uel: UserEntryLogger,
+ aapsLogger: AAPSLogger
+) : PluginBase(PluginDescription()
+ .mainType(PluginType.GENERAL)
+ .fragmentClass(AutotuneFragment::class.qualifiedName)
+ .pluginIcon(R.drawable.ic_autotune)
+ .pluginName(R.string.autotune)
+ .shortName(R.string.autotune_shortname)
+ .preferencesId(R.xml.pref_autotune)
+ .description(R.string.autotune_description),
+ aapsLogger, resourceHelper, injector
+), Autotune {
+ @Volatile override var lastRunSuccess: Boolean = false
+ @Volatile var result: String = ""
+ @Volatile override var calculationRunning: Boolean = false
+ @Volatile var lastRun: Long = 0
+ @Volatile var selectedProfile = ""
+ @Volatile var lastNbDays: String = ""
+ @Volatile var updateButtonVisibility: Int = 0
+ @Volatile lateinit var pumpProfile: ATProfile
+ @Volatile var tunedProfile: ATProfile? = null
+ private var preppedGlucose: PreppedGlucose? = null
+ private lateinit var profile: Profile
+ val autotuneStartHour: Int = 4
+
+ override fun aapsAutotune(daysBack: Int, autoSwitch: Boolean, profileToTune: String) {
+ lastRunSuccess = false
+ if (calculationRunning) {
+ aapsLogger.debug(LTag.AUTOMATION, "Autotune run detected, Autotune Run Cancelled")
+ return
+ }
+ calculationRunning = true
+ tunedProfile = null
+ updateButtonVisibility = View.GONE
+ var logResult = ""
+ result = ""
+ if (profileFunction.getProfile() == null) {
+ result = rh.gs(R.string.profileswitch_ismissing)
+ rxBus.send(EventAutotuneUpdateGui())
+ calculationRunning = false
+ return
+ }
+ val detailedLog = sp.getBoolean(R.string.key_autotune_additional_log, false)
+ calculationRunning = true
+ lastNbDays = "" + daysBack
+ lastRun = dateUtil.now()
+ val profileStore = activePlugin.activeProfileSource.profile
+ if (profileStore == null) {
+ result = rh.gs(R.string.profileswitch_ismissing)
+ rxBus.send(EventAutotuneUpdateGui())
+ calculationRunning = false
+ return
+ }
+ selectedProfile = if (profileToTune.isEmpty()) profileFunction.getProfileName() else profileToTune
+ profileFunction.getProfile()?.let { currentProfile ->
+ profile = profileStore.getSpecificProfile(profileToTune)?.let { ProfileSealed.Pure(it) } ?: currentProfile
+ }
+ val localInsulin = LocalInsulin("PumpInsulin", activePlugin.activeInsulin.peak, profile.dia) // var because localInsulin could be updated later with Tune Insulin peak/dia
+
+ log("Start Autotune with $daysBack days back")
+ autotuneFS.createAutotuneFolder() //create autotune subfolder for autotune files if not exists
+ autotuneFS.deleteAutotuneFiles() //clean autotune folder before run
+ // Today at 4 AM
+ var endTime = MidnightTime.calc(lastRun) + autotuneStartHour * 60 * 60 * 1000L
+ if (endTime > lastRun) endTime -= 24 * 60 * 60 * 1000L // Check if 4 AM is before now
+ val starttime = endTime - daysBack * 24 * 60 * 60 * 1000L
+ autotuneFS.exportSettings(settings(lastRun, daysBack, starttime, endTime))
+ tunedProfile = ATProfile(profile, localInsulin, injector).also {
+ it.profilename = rh.gs(R.string.autotune_tunedprofile_name)
+ }
+ pumpProfile = ATProfile(profile, localInsulin, injector).also {
+ it.profilename = selectedProfile
+ }
+ autotuneFS.exportPumpProfile(pumpProfile)
+
+ for (i in 0 until daysBack) {
+ val from = starttime + i * 24 * 60 * 60 * 1000L // get 24 hours BG values from 4 AM to 4 AM next day
+ val to = from + 24 * 60 * 60 * 1000L
+ log("Tune day " + (i + 1) + " of " + daysBack)
+ tunedProfile?.let { it ->
+ autotuneIob.initializeData(from, to, it) //autotuneIob contains BG and Treatments data from history (<=> query for ns-treatments and ns-entries)
+ autotuneFS.exportEntries(autotuneIob) //<=> ns-entries.yyyymmdd.json files exported for results compare with oref0 autotune on virtual machine
+ autotuneFS.exportTreatments(autotuneIob) //<=> ns-treatments.yyyymmdd.json files exported for results compare with oref0 autotune on virtual machine (include treatments ,tempBasal and extended
+ preppedGlucose = autotunePrep.categorize(it) //<=> autotune.yyyymmdd.json files exported for results compare with oref0 autotune on virtual machine
+ preppedGlucose?.let { preppedGlucose ->
+ autotuneFS.exportPreppedGlucose(preppedGlucose)
+ tunedProfile = autotuneCore.tuneAllTheThings(preppedGlucose, it, pumpProfile).also { tunedProfile ->
+ autotuneFS.exportTunedProfile(tunedProfile) //<=> newprofile.yyyymmdd.json files exported for results compare with oref0 autotune on virtual machine
+ if (i < daysBack - 1) {
+ log("Partial result for day ${i + 1}".trimIndent())
+ result = rh.gs(R.string.autotune_partial_result, i + 1, daysBack)
+ rxBus.send(EventAutotuneUpdateGui())
+ }
+ logResult = showResults(tunedProfile, pumpProfile)
+ if (detailedLog)
+ autotuneFS.exportLog(lastRun, i + 1)
+ }
+ }
+ ?: {
+ log("preppedGlucose is null on day ${i + 1}")
+ tunedProfile = null
+ }
+ }
+ if (tunedProfile == null) {
+ result = rh.gs(R.string.autotune_error)
+ log("TunedProfile is null on day ${i + 1}")
+ autotuneFS.exportResult(result)
+ autotuneFS.exportLogAndZip(lastRun)
+ rxBus.send(EventAutotuneUpdateGui())
+ calculationRunning = false
+ return
+ }
+ }
+ result = rh.gs(R.string.autotune_result, dateUtil.dateAndTimeString(lastRun))
+ if (!detailedLog)
+ autotuneFS.exportLog(lastRun)
+ autotuneFS.exportResult(logResult)
+ autotuneFS.zipAutotune(lastRun)
+ updateButtonVisibility = View.VISIBLE
+
+ if (autoSwitch) {
+ val circadian = sp.getBoolean(R.string.key_autotune_circadian_ic_isf, false)
+ tunedProfile?.let { tunedP ->
+ tunedP.profilename = pumpProfile.profilename
+ updateProfile(tunedP)
+ uel.log(
+ UserEntry.Action.STORE_PROFILE,
+ UserEntry.Sources.Automation,
+ rh.gs(R.string.autotune),
+ ValueWithUnit.SimpleString(tunedP.profilename)
+ )
+ updateButtonVisibility = View.GONE
+ tunedP.profileStore(circadian)?.let { profilestore ->
+ if (profileFunction.createProfileSwitch(
+ profilestore,
+ profileName = tunedP.profilename,
+ durationInMinutes = 0,
+ percentage = 100,
+ timeShiftInHours = 0,
+ timestamp = dateUtil.now()
+ )
+ ) {
+ log("Profile Switch succeed ${tunedP.profilename}")
+ uel.log(
+ UserEntry.Action.PROFILE_SWITCH,
+ UserEntry.Sources.Automation,
+ rh.gs(R.string.autotune),
+ ValueWithUnit.SimpleString(tunedP.profilename))
+ }
+ rxBus.send(EventLocalProfileChanged())
+ }
+ }
+ }
+
+ tunedProfile?.let {
+ saveLastRun()
+ lastRunSuccess = true
+ rxBus.send(EventAutotuneUpdateGui())
+ calculationRunning = false
+ return
+ }
+ result = rh.gs(R.string.autotune_error)
+ rxBus.send(EventAutotuneUpdateGui())
+ calculationRunning = false
+ return
+ }
+
+ private fun showResults(tunedProfile: ATProfile?, pumpProfile: ATProfile): String {
+ if (tunedProfile == null)
+ return "No Result" // should never occurs
+ val line = rh.gs(R.string.autotune_log_separator)
+ var strResult = line
+ strResult += rh.gs(R.string.autotune_log_title)
+ strResult += line
+ val tuneInsulin = sp.getBoolean(R.string.key_autotune_tune_insulin_curve, false)
+ if (tuneInsulin) {
+ strResult += rh.gs(R.string.autotune_log_peak, rh.gs(R.string.insulin_peak), pumpProfile.localInsulin.peak, tunedProfile.localInsulin.peak)
+ strResult += rh.gs(R.string.autotune_log_dia, rh.gs(R.string.ic_short), pumpProfile.localInsulin.dia, tunedProfile.localInsulin.dia)
+ }
+ // show ISF and CR
+ strResult += rh.gs(R.string.autotune_log_ic_isf, rh.gs(R.string.isf_short), pumpProfile.isf, tunedProfile.isf)
+ strResult += rh.gs(R.string.autotune_log_ic_isf, rh.gs(R.string.ic_short), pumpProfile.ic, tunedProfile.ic)
+ strResult += line
+ var totalBasal = 0.0
+ var totalTuned = 0.0
+ for (i in 0..23) {
+ totalBasal += pumpProfile.basal[i]
+ totalTuned += tunedProfile.basal[i]
+ val percentageChangeValue = tunedProfile.basal[i] / pumpProfile.basal[i] * 100 - 100
+ strResult += rh.gs(R.string.autotune_log_basal, i.toDouble(), pumpProfile.basal[i], tunedProfile.basal[i], tunedProfile.basalUntuned[i], percentageChangeValue)
+ }
+ strResult += line
+ strResult += rh.gs(R.string.autotune_log_sum_basal, totalBasal, totalTuned)
+ strResult += line
+ log(strResult)
+ return strResult
+ }
+
+ private fun settings(runDate: Long, nbDays: Int, firstloopstart: Long, lastloopend: Long): String {
+ var jsonString = ""
+ val jsonSettings = JSONObject()
+ val insulinInterface = activePlugin.activeInsulin
+ val utcOffset = T.msecs(TimeZone.getDefault().getOffset(dateUtil.now()).toLong()).hours()
+ val startDateString = dateUtil.toISOString(firstloopstart).substring(0,10)
+ val endDateString = dateUtil.toISOString(lastloopend - 24 * 60 * 60 * 1000L).substring(0,10)
+ val nsUrl = sp.getString(R.string.key_nsclientinternal_url, "")
+ val optCategorizeUam = if (sp.getBoolean(R.string.key_autotune_categorize_uam_as_basal, false)) "-c=true" else ""
+ val optInsulinCurve = if (sp.getBoolean(R.string.key_autotune_tune_insulin_curve, false)) "-i=true" else ""
+ try {
+ jsonSettings.put("datestring", dateUtil.toISOString(runDate))
+ jsonSettings.put("dateutc", dateUtil.toISOAsUTC(runDate))
+ jsonSettings.put("utcOffset", utcOffset)
+ jsonSettings.put("units", profileFunction.getUnits().asText)
+ jsonSettings.put("timezone", TimeZone.getDefault().id)
+ jsonSettings.put("url_nightscout", sp.getString(R.string.key_nsclientinternal_url, ""))
+ jsonSettings.put("nbdays", nbDays)
+ jsonSettings.put("startdate", startDateString)
+ jsonSettings.put("enddate", endDateString)
+ // command to change timezone
+ jsonSettings.put("timezone_command", "sudo ln -sf /usr/share/zoneinfo/" + TimeZone.getDefault().id + " /etc/localtime")
+ // oref0_command is for running oref0-autotune on a virtual machine in a dedicated ~/aaps subfolder
+ jsonSettings.put("oref0_command", "oref0-autotune -d=~/aaps -n=$nsUrl -s=$startDateString -e=$endDateString $optCategorizeUam $optInsulinCurve")
+ // aaps_command is for running modified oref0-autotune with exported data from aaps (ns-entries and ns-treatment json files copied in ~/aaps/autotune folder and pumpprofile.json copied in ~/aaps/settings/
+ jsonSettings.put("aaps_command", "aaps-autotune -d=~/aaps -s=$startDateString -e=$endDateString $optCategorizeUam $optInsulinCurve")
+ jsonSettings.put("categorize_uam_as_basal", sp.getBoolean(R.string.key_autotune_categorize_uam_as_basal, false))
+ jsonSettings.put("tune_insulin_curve", false)
+
+ val peaktime: Int = insulinInterface.peak
+ if (insulinInterface.id === Insulin.InsulinType.OREF_ULTRA_RAPID_ACTING)
+ jsonSettings.put("curve","ultra-rapid")
+ else if (insulinInterface.id === Insulin.InsulinType.OREF_RAPID_ACTING)
+ jsonSettings.put("curve", "rapid-acting")
+ else if (insulinInterface.id === Insulin.InsulinType.OREF_LYUMJEV) {
+ jsonSettings.put("curve", "ultra-rapid")
+ jsonSettings.put("useCustomPeakTime", true)
+ jsonSettings.put("insulinPeakTime", peaktime)
+ } else if (insulinInterface.id === Insulin.InsulinType.OREF_FREE_PEAK) {
+ jsonSettings.put("curve", if (peaktime > 55) "rapid-acting" else "ultra-rapid")
+ jsonSettings.put("useCustomPeakTime", true)
+ jsonSettings.put("insulinPeakTime", peaktime)
+ }
+ jsonString = jsonSettings.toString(4).replace("\\/", "/")
+ } catch (e: JSONException) { }
+ return jsonString
+ }
+
+ fun updateProfile(newProfile: ATProfile?) {
+ if (newProfile == null) return
+ val circadian = sp.getBoolean(R.string.key_autotune_circadian_ic_isf, false)
+ val profileStore = activePlugin.activeProfileSource.profile ?: ProfileStore(injector, JSONObject(), dateUtil)
+ val profileList: ArrayList = profileStore.getProfileList()
+ var indexLocalProfile = -1
+ for (p in profileList.indices)
+ if (profileList[p] == newProfile.profilename)
+ indexLocalProfile = p
+ if (indexLocalProfile == -1) {
+ localProfilePlugin.addProfile(localProfilePlugin.copyFrom(newProfile.getProfile(circadian), newProfile.profilename))
+ return
+ }
+ localProfilePlugin.currentProfileIndex = indexLocalProfile
+ localProfilePlugin.currentProfile()?.dia = newProfile.dia
+ localProfilePlugin.currentProfile()?.basal = newProfile.basal()
+ localProfilePlugin.currentProfile()?.ic = newProfile.ic(circadian)
+ localProfilePlugin.currentProfile()?.isf = newProfile.isf(circadian)
+ localProfilePlugin.storeSettings()
+ }
+
+ fun saveLastRun() {
+ val json = JSONObject()
+ json.put("lastNbDays", lastNbDays)
+ json.put("lastRun",lastRun)
+ json.put("pumpProfile", pumpProfile.profile.toPureNsJson(dateUtil))
+ json.put("pumpProfileName", pumpProfile.profilename)
+ json.put("pumpPeak", pumpProfile.peak)
+ json.put("pumpDia", pumpProfile.dia)
+ tunedProfile?.let { atProfile ->
+ json.put("tunedProfile", atProfile.profile.toPureNsJson(dateUtil))
+ json.put("tunedCircadianProfile", atProfile.circadianProfile.toPureNsJson(dateUtil))
+ json.put("tunedProfileName", atProfile.profilename)
+ json.put("tunedPeak", atProfile.peak)
+ json.put("tunedDia", atProfile.dia)
+ for (i in 0..23) {
+ json.put("missingDays_$i", atProfile.basalUntuned[i])
+ }
+ }
+ json.put("result", result)
+ json.put("updateButtonVisibility", updateButtonVisibility)
+ sp.putString(R.string.key_autotune_last_run, json.toString())
+ }
+
+ fun loadLastRun() {
+ result = ""
+ lastRunSuccess = false
+ try {
+ val json = JSONObject(sp.getString(R.string.key_autotune_last_run, ""))
+ lastNbDays = JsonHelper.safeGetString(json, "lastNbDays", "")
+ lastRun = JsonHelper.safeGetLong(json, "lastRun")
+ val pumpPeak = JsonHelper.safeGetInt(json, "pumpPeak")
+ val pumpDia = JsonHelper.safeGetDouble(json, "pumpDia")
+ var localInsulin = LocalInsulin("PumpInsulin", pumpPeak, pumpDia)
+ selectedProfile = JsonHelper.safeGetString(json, "pumpProfileName", "")
+ val profile = JsonHelper.safeGetJSONObject(json, "pumpProfile", null)?.let { pureProfileFromJson(it, dateUtil) }
+ ?: return
+ pumpProfile = ATProfile(ProfileSealed.Pure(profile), localInsulin, injector).also { it.profilename = selectedProfile }
+ val tunedPeak = JsonHelper.safeGetInt(json, "tunedPeak")
+ val tunedDia = JsonHelper.safeGetDouble(json, "tunedDia")
+ localInsulin = LocalInsulin("PumpInsulin", tunedPeak, tunedDia)
+ val tunedProfileName = JsonHelper.safeGetString(json, "tunedProfileName", "")
+ val tuned = JsonHelper.safeGetJSONObject(json, "tunedProfile", null)?.let { pureProfileFromJson(it, dateUtil) }
+ ?: return
+ val circadianTuned = JsonHelper.safeGetJSONObject(json, "tunedCircadianProfile", null)?.let { pureProfileFromJson(it, dateUtil) }
+ ?: return
+ tunedProfile = ATProfile(ProfileSealed.Pure(tuned), localInsulin, injector).also { atProfile ->
+ atProfile.profilename = tunedProfileName
+ atProfile.circadianProfile = ProfileSealed.Pure(circadianTuned)
+ for (i in 0..23) {
+ atProfile.basalUntuned[i] = JsonHelper.safeGetInt(json,"missingDays_$i")
+ }
+ }
+ result = JsonHelper.safeGetString(json, "result", "")
+ updateButtonVisibility = JsonHelper.safeGetInt(json, "updateButtonVisibility")
+ lastRunSuccess = true
+ } catch (e: Exception) {
+ }
+ }
+
+ private fun log(message: String) {
+ atLog("[Plugin] $message")
+ }
+
+ override fun specialEnableCondition(): Boolean = buildHelper.isEngineeringMode() && buildHelper.isDev()
+
+ override fun atLog(message: String) {
+ autotuneFS.atLog(message)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/AutotunePrep.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/AutotunePrep.kt
new file mode 100644
index 0000000000..63617e908e
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/AutotunePrep.kt
@@ -0,0 +1,563 @@
+package info.nightscout.androidaps.plugins.general.autotune
+
+import info.nightscout.androidaps.R
+import info.nightscout.androidaps.data.LocalInsulin
+import info.nightscout.androidaps.database.entities.GlucoseValue
+import info.nightscout.androidaps.plugins.general.autotune.data.*
+import info.nightscout.androidaps.database.entities.Bolus
+import info.nightscout.androidaps.database.entities.Carbs
+import info.nightscout.androidaps.utils.DateUtil
+import info.nightscout.androidaps.utils.MidnightTime
+import info.nightscout.androidaps.utils.Round
+import info.nightscout.androidaps.utils.T
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.shared.logging.LTag
+import info.nightscout.shared.sharedPreferences.SP
+import java.util.*
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class AutotunePrep @Inject constructor(
+ private val aapsLogger: AAPSLogger,
+ private val sp: SP,
+ private val dateUtil: DateUtil,
+ private val autotuneFS: AutotuneFS,
+ private val autotuneIob: AutotuneIob
+) {
+ fun categorize(tunedprofile: ATProfile): PreppedGlucose? {
+ val preppedGlucose = categorizeBGDatums(tunedprofile, tunedprofile.localInsulin)
+ val tuneInsulin = sp.getBoolean(R.string.key_autotune_tune_insulin_curve, false)
+ if (tuneInsulin) {
+ var minDeviations = 1000000.0
+ val diaDeviations: MutableList = ArrayList()
+ val peakDeviations: MutableList = ArrayList()
+ val currentDIA = tunedprofile.localInsulin.dia
+ val currentPeak = tunedprofile.localInsulin.peak
+
+ var dia = currentDIA - 2
+ val endDIA = currentDIA + 2
+ while (dia <= endDIA)
+ {
+ var sqrtDeviations = 0.0
+ var deviations = 0.0
+ var deviationsSq = 0.0
+ val localInsulin = LocalInsulin("Ins_$currentPeak-$dia", currentPeak, dia)
+ val curve_output = categorizeBGDatums(tunedprofile, localInsulin, false)
+ val basalGlucose = curve_output?.basalGlucoseData
+
+ basalGlucose?.let {
+ for (hour in 0..23) {
+ for (i in 0..(basalGlucose.size-1)) {
+ val myHour = ((basalGlucose[i].date - MidnightTime.calc(basalGlucose[i].date)) / T.hours(1).msecs()).toInt()
+ if (hour == myHour) {
+ sqrtDeviations += Math.pow(Math.abs(basalGlucose[i].deviation), 0.5)
+ deviations += Math.abs(basalGlucose[i].deviation)
+ deviationsSq += Math.pow(basalGlucose[i].deviation, 2.0)
+ }
+ }
+ }
+
+ val meanDeviation = Round.roundTo(Math.abs(deviations / basalGlucose.size), 0.001)
+ val smrDeviation = Round.roundTo(Math.pow(sqrtDeviations / basalGlucose.size, 2.0), 0.001)
+ val rmsDeviation = Round.roundTo(Math.pow(deviationsSq / basalGlucose.size, 0.5), 0.001)
+ log("insulinEndTime $dia meanDeviation: $meanDeviation SMRDeviation: $smrDeviation RMSDeviation: $rmsDeviation (mg/dL)")
+ diaDeviations.add(
+ DiaDeviation(
+ dia = dia,
+ meanDeviation = meanDeviation,
+ smrDeviation = smrDeviation,
+ rmsDeviation = rmsDeviation
+ )
+ )
+ }
+ preppedGlucose?.diaDeviations = diaDeviations
+
+ deviations = Round.roundTo(deviations, 0.001)
+ if (deviations < minDeviations)
+ minDeviations = Round.roundTo(deviations, 0.001)
+ dia += 1.0
+ }
+
+ // consoleError('Optimum insulinEndTime', newDIA, 'mean deviation:', JSMath.Round(minDeviations/basalGlucose.length*1000)/1000, '(mg/dL)');
+ //consoleError(diaDeviations);
+
+ minDeviations = 1000000.0
+ var peak = currentPeak - 10
+ val endPeak = currentPeak + 10
+ while (peak <= endPeak)
+ {
+ var sqrtDeviations = 0.0
+ var deviations = 0.0
+ var deviationsSq = 0.0
+ val localInsulin = LocalInsulin("Ins_$peak-$currentDIA", peak, currentDIA)
+ val curve_output = categorizeBGDatums(tunedprofile, localInsulin, false)
+ val basalGlucose = curve_output?.basalGlucoseData
+
+ basalGlucose?.let {
+ for (hour in 0..23) {
+ for (i in 0..(basalGlucose.size - 1)) {
+ val myHour = ((basalGlucose[i].date - MidnightTime.calc(basalGlucose[i].date)) / T.hours(1).msecs()).toInt()
+ if (hour == myHour) {
+ //console.error(basalGlucose[i].deviation);
+ sqrtDeviations += Math.pow(Math.abs(basalGlucose[i].deviation), 0.5)
+ deviations += Math.abs(basalGlucose[i].deviation)
+ deviationsSq += Math.pow(basalGlucose[i].deviation, 2.0)
+ }
+ }
+ }
+
+ val meanDeviation = Round.roundTo(deviations / basalGlucose.size, 0.001)
+ val smrDeviation = Round.roundTo(Math.pow(sqrtDeviations / basalGlucose.size, 2.0), 0.001)
+ val rmsDeviation = Round.roundTo(Math.pow(deviationsSq / basalGlucose.size, 0.5), 0.001)
+ log("insulinPeakTime $peak meanDeviation: $meanDeviation SMRDeviation: $smrDeviation RMSDeviation: $rmsDeviation (mg/dL)")
+ peakDeviations.add(
+ PeakDeviation
+ (
+ peak = peak,
+ meanDeviation = meanDeviation,
+ smrDeviation = smrDeviation,
+ rmsDeviation = rmsDeviation,
+ )
+ )
+ }
+
+ deviations = Round.roundTo(deviations, 0.001);
+ if (deviations < minDeviations)
+ minDeviations = Round.roundTo(deviations, 0.001)
+ peak += 5
+ }
+ //consoleError($"Optimum insulinPeakTime {newPeak} mean deviation: {JSMath.Round(minDeviations/basalGlucose.Count, 3)} (mg/dL)");
+ //consoleError(peakDeviations);
+ preppedGlucose?.peakDeviations = peakDeviations
+ }
+
+ return preppedGlucose
+ }
+
+ // private static Logger log = LoggerFactory.getLogger(AutotunePlugin.class);
+ fun categorizeBGDatums(tunedprofile: ATProfile, localInsulin: LocalInsulin, verbose: Boolean = true): PreppedGlucose? {
+ //lib/meals is called before to get only meals data (in AAPS it's done in AutotuneIob)
+ val treatments: MutableList = autotuneIob.meals
+ val boluses: MutableList = autotuneIob.boluses
+ // Bloc between #21 and # 54 replaced by bloc below (just remove BG value below 39, Collections.sort probably not necessary because BG values already sorted...)
+ val glucose = autotuneIob.glucose
+ val glucoseData: MutableList = ArrayList()
+ for (i in glucose.indices) {
+ if (glucose[i].value > 39) {
+ glucoseData.add(glucose[i])
+ }
+ }
+ if (glucose.size == 0 || glucoseData.size == 0 ) {
+ //aapsLogger.debug(LTag.AUTOTUNE, "No BG value received")
+ if (verbose)
+ log("No BG value received")
+ return null
+ }
+
+ glucoseData.sortWith(object: Comparator{ override fun compare(o1: GlucoseValue, o2: GlucoseValue): Int = (o2.timestamp - o1.timestamp).toInt() })
+
+ // Bloc below replace bloc between #55 and #71
+ // boluses and maxCarbs not used here ?,
+ // IOBInputs are for iob calculation (done here in AutotuneIob Class)
+ //val boluses = 0
+ //val maxCarbs = 0
+ if (treatments.size == 0) {
+ //aapsLogger.debug(LTag.AUTOTUNE, "No Carbs entries")
+ if (verbose)
+ log("No Carbs entries")
+ //return null
+ }
+ if (autotuneIob.boluses.size == 0) {
+ //aapsLogger.debug(LTag.AUTOTUNE, "No treatment received")
+ if (verbose)
+ log("No treatment received")
+ return null
+ }
+
+ var csfGlucoseData: MutableList = ArrayList()
+ var isfGlucoseData: MutableList = ArrayList()
+ var basalGlucoseData: MutableList = ArrayList()
+ val uamGlucoseData: MutableList = ArrayList()
+ val crData: MutableList = ArrayList()
+
+ //Bloc below replace bloc between #72 and #93
+ // I keep it because BG lines in log are consistent between AAPS and Oref0
+ val bucketedData: MutableList = ArrayList()
+ bucketedData.add(BGDatum(glucoseData[0], dateUtil))
+ //int j=0;
+ var k = 0 // index of first value used by bucket
+ //for loop to validate and bucket the data
+ for (i in 1 until glucoseData.size) {
+ val BGTime = glucoseData[i].timestamp
+ val lastBGTime = glucoseData[k].timestamp
+ val elapsedMinutes = (BGTime - lastBGTime) / (60 * 1000)
+ if (Math.abs(elapsedMinutes) >= 2) {
+ //j++; // move to next bucket
+ k = i // store index of first value used by bucket
+ bucketedData.add(BGDatum(glucoseData[i], dateUtil))
+ } else {
+ // average all readings within time deadband
+ val average = glucoseData[k]
+ for (l in k + 1 until i + 1) {
+ average.value += glucoseData[l].value
+ }
+ average.value = average.value / (i - k + 1)
+ bucketedData.add(BGDatum(average, dateUtil))
+ }
+ }
+
+ // Here treatments contains only meals data
+ // bloc between #94 and #114 remove meals before first BG value
+
+ // Bloc below replace bloc between #115 and #122 (initialize data before main loop)
+ // crInitialxx are declaration to be able to use these data in whole loop
+ var calculatingCR = false
+ var absorbing = false
+ var uam = false // unannounced meal
+ var mealCOB = 0.0
+ var mealCarbs = 0.0
+ var crCarbs = 0.0
+ var type = ""
+ var crInitialIOB = 0.0
+ var crInitialBG = 0.0
+ var crInitialCarbTime = 0L
+
+ //categorize.js#123 (Note: don't need fullHistory because data are managed in AutotuneIob Class)
+ //Here is main loop between #125 and #366
+ // main for loop
+ for (i in bucketedData.size - 5 downTo 1) {
+ val glucoseDatum = bucketedData[i]
+ //log.debug(glucoseDatum);
+ val BGTime = glucoseDatum.date
+
+ // As we're processing each data point, go through the treatment.carbs and see if any of them are older than
+ // the current BG data point. If so, add those carbs to COB.
+ val treatment = if (treatments.size > 0) treatments[treatments.size - 1] else null
+ var myCarbs = 0.0
+ if (treatment != null) {
+ if (treatment.timestamp < BGTime) {
+ if (treatment.amount > 0.0) {
+ mealCOB += treatment.amount
+ mealCarbs += treatment.amount
+ myCarbs = treatment.amount
+ }
+ treatments.removeAt(treatments.size - 1)
+ }
+ }
+ var bg = 0.0
+ var avgDelta = 0.0
+
+ // TODO: re-implement interpolation to avoid issues here with gaps
+ // calculate avgDelta as last 4 datapoints to better catch more rises after COB hits zero
+ if (bucketedData[i].value != 0.0 && bucketedData[i + 4].value != 0.0) {
+ //log.debug(bucketedData[i]);
+ bg = bucketedData[i].value
+ if (bg < 40 || bucketedData[i + 4].value < 40) {
+ //process.stderr.write("!");
+ continue
+ }
+ avgDelta = (bg - bucketedData[i + 4].value) / 4
+ } else {
+ //aapsLogger.debug(LTag.AUTOTUNE, "Could not find glucose data")
+ if (verbose)
+ log("Could not find glucose data")
+ }
+ avgDelta = Round.roundTo(avgDelta, 0.01)
+ glucoseDatum.avgDelta = avgDelta
+
+ //sens = ISF
+ val sens = tunedprofile.isf
+
+ // for IOB calculations, use the average of the last 4 hours' basals to help convergence;
+ // this helps since the basal this hour could be different from previous, especially if with autotune they start to diverge.
+ // use the pumpbasalprofile to properly calculate IOB during periods where no temp basal is set
+ /* Note Philoul currentPumpBasal never used in oref0 Autotune code
+ var currentPumpBasal = pumpprofile.profile.getBasal(BGTime)
+ currentPumpBasal += pumpprofile.profile.getBasal(BGTime - 1 * 60 * 60 * 1000)
+ currentPumpBasal += pumpprofile.profile.getBasal(BGTime - 2 * 60 * 60 * 1000)
+ currentPumpBasal += pumpprofile.profile.getBasal(BGTime - 3 * 60 * 60 * 1000)
+ currentPumpBasal = Round.roundTo(currentPumpBasal / 4, 0.001) //CurrentPumpBasal for iob calculation is average of 4 last pumpProfile Basal rate
+ */
+ // this is the current autotuned basal, used for everything else besides IOB calculations
+ val currentBasal = tunedprofile.getBasal(BGTime)
+
+ // basalBGI is BGI of basal insulin activity.
+ val basalBGI = Round.roundTo(currentBasal * sens / 60 * 5, 0.01) // U/hr * mg/dL/U * 1 hr / 60 minutes * 5 = mg/dL/5m
+ //console.log(JSON.stringify(IOBInputs.profile));
+ // call iob since calculated elsewhere
+ //var iob = getIOB(IOBInputs)[0];
+ // in autotune iob is calculated with 6 hours of history data, tunedProfile and average pumpProfile basal rate...
+ //log("currentBasal: " + currentBasal + " BGTime: " + BGTime + " / " + dateUtil!!.timeStringWithSeconds(BGTime) + "******************************************************************************************")
+ val iob = autotuneIob.getIOB(BGTime, localInsulin) // add localInsulin to be independent to InsulinPlugin
+
+ // activity times ISF times 5 minutes is BGI
+ val BGI = Round.roundTo(-iob.activity * sens * 5, 0.01)
+ // datum = one glucose data point (being prepped to store in output)
+ glucoseDatum.bgi = BGI
+ // calculating deviation
+ var deviation = avgDelta - BGI
+
+ // set positive deviations to zero if BG is below 80
+ if (bg < 80 && deviation > 0) {
+ deviation = 0.0
+ }
+
+ // rounding and storing deviation
+ deviation = Round.roundTo(deviation, 0.01)
+ glucoseDatum.deviation = deviation
+
+ // Then, calculate carb absorption for that 5m interval using the deviation.
+ if (mealCOB > 0) {
+ val ci = Math.max(deviation, sp.getDouble(R.string.key_openapsama_min_5m_carbimpact, 3.0))
+ val absorbed = ci * tunedprofile.ic / sens
+ // Store the COB, and use it as the starting point for the next data point.
+ mealCOB = Math.max(0.0, mealCOB - absorbed)
+ }
+
+ // Calculate carb ratio (CR) independently of CSF and ISF
+ // Use the time period from meal bolus/carbs until COB is zero and IOB is < currentBasal/2
+ // For now, if another meal IOB/COB stacks on top of it, consider them together
+ // Compare beginning and ending BGs, and calculate how much more/less insulin is needed to neutralize
+ // Use entered carbs vs. starting IOB + delivered insulin + needed-at-end insulin to directly calculate CR.
+ if (mealCOB > 0 || calculatingCR) {
+ // set initial values when we first see COB
+ crCarbs += myCarbs
+ if (calculatingCR == false) {
+ crInitialIOB = iob.iob
+ crInitialBG = glucoseDatum.value
+ crInitialCarbTime = glucoseDatum.date
+ //aapsLogger.debug(LTag.AUTOTUNE, "CRInitialIOB: $crInitialIOB CRInitialBG: $crInitialBG CRInitialCarbTime: ${dateUtil.toISOString(crInitialCarbTime)}")
+ if (verbose)
+ log("CRInitialIOB: $crInitialIOB CRInitialBG: $crInitialBG CRInitialCarbTime: ${dateUtil.toISOString(crInitialCarbTime)}")
+ }
+ // keep calculatingCR as long as we have COB or enough IOB
+ if (mealCOB > 0 && i > 1) {
+ calculatingCR = true
+ } else if (iob.iob > currentBasal / 2 && i > 1) {
+ calculatingCR = true
+ // when COB=0 and IOB drops low enough, record end values and be done calculatingCR
+ } else {
+ val crEndIOB = iob.iob
+ val crEndBG = glucoseDatum.value
+ val crEndTime = glucoseDatum.date
+ //aapsLogger.debug(LTag.AUTOTUNE, "CREndIOB: $crEndIOB CREndBG: $crEndBG CREndTime: ${dateUtil.toISOString(crEndTime)}")
+ if (verbose)
+ log("CREndIOB: $crEndIOB CREndBG: $crEndBG CREndTime: ${dateUtil.toISOString(crEndTime)}")
+ val crDatum = CRDatum(dateUtil)
+ crDatum.crInitialBG = crInitialBG
+ crDatum.crInitialIOB = crInitialIOB
+ crDatum.crInitialCarbTime = crInitialCarbTime
+ crDatum.crEndBG = crEndBG
+ crDatum.crEndIOB = crEndIOB
+ crDatum.crEndTime = crEndTime
+ crDatum.crCarbs = crCarbs
+ //log.debug(CRDatum);
+ //String crDataString = "{\"CRInitialIOB\": " + CRInitialIOB + ", \"CRInitialBG\": " + CRInitialBG + ", \"CRInitialCarbTime\": " + CRInitialCarbTime + ", \"CREndIOB\": " + CREndIOB + ", \"CREndBG\": " + CREndBG + ", \"CREndTime\": " + CREndTime + ", \"CRCarbs\": " + CRCarbs + "}";
+ val CRElapsedMinutes = Math.round((crEndTime - crInitialCarbTime) / (1000 * 60).toFloat())
+
+ //log.debug(CREndTime - CRInitialCarbTime, CRElapsedMinutes);
+ if (CRElapsedMinutes < 60 || i == 1 && mealCOB > 0) {
+ //aapsLogger.debug(LTag.AUTOTUNE, "Ignoring $CRElapsedMinutes m CR period.")
+ if (verbose)
+ log("Ignoring $CRElapsedMinutes m CR period.")
+ } else {
+ crData.add(crDatum)
+ }
+ crCarbs = 0.0
+ calculatingCR = false
+ }
+ }
+
+ // If mealCOB is zero but all deviations since hitting COB=0 are positive, assign those data points to CSFGlucoseData
+ // Once deviations go negative for at least one data point after COB=0, we can use the rest of the data to tune ISF or basals
+ if (mealCOB > 0 || absorbing || mealCarbs > 0) {
+ // if meal IOB has decayed, then end absorption after this data point unless COB > 0
+ absorbing = if (iob.iob < currentBasal / 2) {
+ false
+ // otherwise, as long as deviations are positive, keep tracking carb deviations
+ } else if (deviation > 0) {
+ true
+ } else {
+ false
+ }
+ if (!absorbing && mealCOB == 0.0) {
+ mealCarbs = 0.0
+ }
+ // check previous "type" value, and if it wasn't csf, set a mealAbsorption start flag
+ //log.debug(type);
+ if (type != "csf") {
+ glucoseDatum.mealAbsorption = "start"
+ //aapsLogger.debug(LTag.AUTOTUNE, "${glucoseDatum.mealAbsorption} carb absorption")
+ if (verbose)
+ log("${glucoseDatum.mealAbsorption} carb absorption")
+ }
+ type = "csf"
+ glucoseDatum.mealCarbs = mealCarbs.toInt()
+ //if (i == 0) { glucoseDatum.mealAbsorption = "end"; }
+ csfGlucoseData.add(glucoseDatum)
+ } else {
+ // check previous "type" value, and if it was csf, set a mealAbsorption end flag
+ if (type == "csf") {
+ csfGlucoseData[csfGlucoseData.size - 1].mealAbsorption = "end"
+ //aapsLogger.debug(LTag.AUTOTUNE, "${csfGlucoseData[csfGlucoseData.size - 1].mealAbsorption} carb absorption")
+ if (verbose)
+ log("${csfGlucoseData[csfGlucoseData.size - 1].mealAbsorption} carb absorption")
+ }
+ if (iob.iob > 2 * currentBasal || deviation > 6 || uam) {
+ uam = if (deviation > 0) {
+ true
+ } else {
+ false
+ }
+ if (type != "uam") {
+ glucoseDatum.uamAbsorption = "start"
+ //aapsLogger.debug(LTag.AUTOTUNE, "${glucoseDatum.uamAbsorption} unannnounced meal absorption")
+ if (verbose)
+ log(glucoseDatum.uamAbsorption + " unannnounced meal absorption")
+ }
+ type = "uam"
+ uamGlucoseData.add(glucoseDatum)
+ } else {
+ if (type == "uam") {
+ //aapsLogger.debug(LTag.AUTOTUNE, "end unannounced meal absorption")
+ if (verbose)
+ log("end unannounced meal absorption")
+ }
+
+ // Go through the remaining time periods and divide them into periods where scheduled basal insulin activity dominates. This would be determined by calculating the BG impact of scheduled basal insulin
+ // (for example 1U/hr * 48 mg/dL/U ISF = 48 mg/dL/hr = 5 mg/dL/5m), and comparing that to BGI from bolus and net basal insulin activity.
+ // When BGI is positive (insulin activity is negative), we want to use that data to tune basals
+ // When BGI is smaller than about 1/4 of basalBGI, we want to use that data to tune basals
+ // When BGI is negative and more than about 1/4 of basalBGI, we can use that data to tune ISF,
+ // unless avgDelta is positive: then that's some sort of unexplained rise we don't want to use for ISF, so that means basals
+ if (basalBGI > -4 * BGI) {
+ type = "basal"
+ basalGlucoseData.add(glucoseDatum)
+ } else {
+ if (avgDelta > 0 && avgDelta > -2 * BGI) {
+ //type="unknown"
+ type = "basal"
+ basalGlucoseData.add(glucoseDatum)
+ } else {
+ type = "ISF"
+ isfGlucoseData.add(glucoseDatum)
+ }
+ }
+ }
+ }
+ // debug line to print out all the things
+ //aapsLogger.debug(LTag.AUTOTUNE, "${(if (absorbing) 1 else 0)} mealCOB: ${Round.roundTo(mealCOB, 0.1)} mealCarbs: ${Math.round(mealCarbs)} basalBGI: ${Round.roundTo(basalBGI, 0.1)} BGI: ${Round.roundTo(BGI, 0.1)} IOB: ${iob.iob} Activity: ${iob.activity} at ${dateUtil.timeStringWithSeconds(BGTime)} dev: $deviation avgDelta: $avgDelta $type")
+ if (verbose)
+ log("${(if (absorbing) 1 else 0)} mealCOB: ${Round.roundTo(mealCOB, 0.1)} mealCarbs: ${Math.round(mealCarbs)} basalBGI: ${Round.roundTo(basalBGI, 0.1)} BGI: ${Round
+ .roundTo(BGI, 0.1)} IOB: ${iob.iob} Activity: ${iob.activity} at ${dateUtil.timeStringWithSeconds(BGTime)} dev: $deviation avgDelta: $avgDelta $type")
+ }
+
+//****************************************************************************************************************************************
+
+// categorize.js Lines 372-383
+ for (crDatum in crData) {
+ crDatum.crInsulin = dosed(crDatum.crInitialCarbTime, crDatum.crEndTime, boluses)
+ }
+ // categorize.js Lines 384-436
+ val CSFLength = csfGlucoseData.size
+ var ISFLength = isfGlucoseData.size
+ val UAMLength = uamGlucoseData.size
+ var basalLength = basalGlucoseData.size
+ if (sp.getBoolean(R.string.key_autotune_categorize_uam_as_basal, false)) {
+ //aapsLogger.debug(LTag.AUTOTUNE, "Categorizing all UAM data as basal.")
+ if (verbose)
+ log("Categorizing all UAM data as basal.")
+ basalGlucoseData.addAll(uamGlucoseData)
+ } else if (CSFLength > 12) {
+ //aapsLogger.debug(LTag.AUTOTUNE, "Found at least 1h of carb: assuming meals were announced, and categorizing UAM data as basal.")
+ if (verbose)
+ log("Found at least 1h of carb: assuming meals were announced, and categorizing UAM data as basal.")
+ basalGlucoseData.addAll(uamGlucoseData)
+ } else {
+ if (2 * basalLength < UAMLength) {
+ //log.debug(basalGlucoseData, UAMGlucoseData);
+ //aapsLogger.debug(LTag.AUTOTUNE, "Warning: too many deviations categorized as UnAnnounced Meals")
+ //aapsLogger.debug(LTag.AUTOTUNE, "Adding $UAMLength UAM deviations to $basalLength basal ones")
+ if (verbose) {
+ log("Warning: too many deviations categorized as UnAnnounced Meals")
+ log("Adding $UAMLength UAM deviations to $basalLength basal ones")
+ }
+ basalGlucoseData.addAll(uamGlucoseData)
+ //log.debug(basalGlucoseData);
+ // if too much data is excluded as UAM, add in the UAM deviations, but then discard the highest 50%
+ basalGlucoseData.sortWith(object: Comparator{ override fun compare(o1: BGDatum, o2: BGDatum): Int = (100 * o1.deviation - 100 * o2.deviation).toInt() }) //deviation rouded to 0.01, so *100 to avoid crash during sort
+ val newBasalGlucose: MutableList = ArrayList()
+ for (i in 0 until basalGlucoseData.size / 2) {
+ newBasalGlucose.add(basalGlucoseData[i])
+ }
+ //log.debug(newBasalGlucose);
+ basalGlucoseData = newBasalGlucose
+ //aapsLogger.debug(LTag.AUTOTUNE, "and selecting the lowest 50%, leaving ${basalGlucoseData.size} basal+UAM ones")
+ if (verbose)
+ log("and selecting the lowest 50%, leaving ${basalGlucoseData.size} basal+UAM ones")
+ }
+ if (2 * ISFLength < UAMLength) {
+ //aapsLogger.debug(LTag.AUTOTUNE, "Adding $UAMLength UAM deviations to $ISFLength ISF ones")
+ if (verbose)
+ log("Adding $UAMLength UAM deviations to $ISFLength ISF ones")
+ isfGlucoseData.addAll(uamGlucoseData)
+ // if too much data is excluded as UAM, add in the UAM deviations to ISF, but then discard the highest 50%
+ isfGlucoseData.sortWith(object: Comparator{ override fun compare(o1: BGDatum, o2: BGDatum): Int = (100 * o1.deviation - 100 * o2.deviation).toInt() }) //deviation rouded to 0.01, so *100 to avoid crash during sort
+ val newISFGlucose: MutableList = ArrayList()
+ for (i in 0 until isfGlucoseData.size / 2) {
+ newISFGlucose.add(isfGlucoseData[i])
+ }
+ //console.error(newISFGlucose);
+ isfGlucoseData = newISFGlucose
+ //aapsLogger.debug(LTag.AUTOTUNE, "and selecting the lowest 50%, leaving ${isfGlucoseData.size} ISF+UAM ones")
+ if (verbose)
+ log("and selecting the lowest 50%, leaving ${isfGlucoseData.size} ISF+UAM ones")
+ //log.error(ISFGlucoseData.length, UAMLength);
+ }
+ }
+ basalLength = basalGlucoseData.size
+ ISFLength = isfGlucoseData.size
+ if (4 * basalLength + ISFLength < CSFLength && ISFLength < 10) {
+ //aapsLogger.debug(LTag.AUTOTUNE, "Warning: too many deviations categorized as meals")
+ //aapsLogger.debug(LTag.AUTOTUNE, "Adding $CSFLength CSF deviations to $ISFLength ISF ones")
+ if (verbose) {
+ log("Warning: too many deviations categorized as meals")
+ //log.debug("Adding",CSFLength,"CSF deviations to",basalLength,"basal ones");
+ //var basalGlucoseData = basalGlucoseData.concat(CSFGlucoseData);
+ log("Adding $CSFLength CSF deviations to $ISFLength ISF ones")
+ }
+ isfGlucoseData.addAll(csfGlucoseData)
+ csfGlucoseData = ArrayList()
+ }
+
+// categorize.js Lines 437-444
+ //aapsLogger.debug(LTag.AUTOTUNE, "CRData: ${crData.size} CSFGlucoseData: ${csfGlucoseData.size} ISFGlucoseData: ${isfGlucoseData.size} BasalGlucoseData: ${basalGlucoseData.size}")
+ if (verbose)
+ log("CRData: ${crData.size} CSFGlucoseData: ${csfGlucoseData.size} ISFGlucoseData: ${isfGlucoseData.size} BasalGlucoseData: ${basalGlucoseData.size}")
+
+ return PreppedGlucose(autotuneIob.startBG, crData, csfGlucoseData, isfGlucoseData, basalGlucoseData, dateUtil)
+ }
+
+ //dosed.js full
+ private fun dosed(start: Long, end: Long, treatments: List): Double {
+ var insulinDosed = 0.0
+ //aapsLogger.debug(LTag.AUTOTUNE, "No treatments to process.")
+ if (treatments.size == 0) {
+ log("No treatments to process.")
+ return 0.0
+ }
+ for (treatment in treatments) {
+ if (treatment.amount != 0.0 && treatment.timestamp > start && treatment.timestamp <= end) {
+ insulinDosed += treatment.amount
+ //log("CRDATA;${dateUtil.toISOString(start)};${dateUtil.toISOString(end)};${treatment.timestamp};${treatment.amount};$insulinDosed")
+ }
+ }
+ //log("insulin dosed: " + insulinDosed);
+ return Round.roundTo(insulinDosed, 0.001)
+ }
+
+ private fun log(message: String) {
+ autotuneFS.atLog("[Prep] $message")
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/data/ATProfile.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/data/ATProfile.kt
new file mode 100644
index 0000000000..d3d9d947dd
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/data/ATProfile.kt
@@ -0,0 +1,254 @@
+package info.nightscout.androidaps.plugins.general.autotune.data
+
+import dagger.android.HasAndroidInjector
+import info.nightscout.androidaps.core.R
+import info.nightscout.androidaps.data.LocalInsulin
+import info.nightscout.androidaps.data.ProfileSealed
+import info.nightscout.androidaps.data.PureProfile
+import info.nightscout.androidaps.database.data.Block
+import info.nightscout.androidaps.extensions.blockValueBySeconds
+import info.nightscout.androidaps.extensions.pureProfileFromJson
+import info.nightscout.androidaps.interfaces.*
+import info.nightscout.androidaps.plugins.bus.RxBus
+import info.nightscout.androidaps.utils.DateUtil
+import info.nightscout.androidaps.utils.Round
+import info.nightscout.androidaps.utils.T
+import info.nightscout.shared.SafeParse
+import info.nightscout.shared.sharedPreferences.SP
+import org.json.JSONArray
+import org.json.JSONException
+import org.json.JSONObject
+import java.text.DecimalFormat
+import java.util.*
+import javax.inject.Inject
+
+class ATProfile(profile: Profile, var localInsulin: LocalInsulin, val injector: HasAndroidInjector) {
+
+ @Inject lateinit var activePlugin: ActivePlugin
+ @Inject lateinit var sp: SP
+ @Inject lateinit var profileFunction: ProfileFunction
+ @Inject lateinit var dateUtil: DateUtil
+ @Inject lateinit var config: Config
+ @Inject lateinit var rxBus: RxBus
+ @Inject lateinit var rh: ResourceHelper
+
+ var profile: ProfileSealed
+ var circadianProfile: ProfileSealed
+ lateinit var pumpProfile: ProfileSealed
+ var profilename: String = ""
+ var basal = DoubleArray(24)
+ var basalUntuned = IntArray(24)
+ var ic = 0.0
+ var isf = 0.0
+ var dia = 0.0
+ var peak = 0
+ var isValid: Boolean = false
+ var from: Long = 0
+ var pumpProfileAvgISF = 0.0
+ var pumpProfileAvgIC = 0.0
+
+ val icSize: Int
+ get() = profile.getIcsValues().size
+ val isfSize: Int
+ get() = profile.getIsfsMgdlValues().size
+ val avgISF: Double
+ get() = if (profile.getIsfsMgdlValues().size == 1) profile.getIsfsMgdlValues().get(0).value else Round.roundTo(averageProfileValue(profile.getIsfsMgdlValues()), 0.01)
+ val avgIC: Double
+ get() = if (profile.getIcsValues().size == 1) profile.getIcsValues().get(0).value else Round.roundTo(averageProfileValue(profile.getIcsValues()), 0.01)
+
+ fun getBasal(timestamp: Long): Double = basal[Profile.secondsFromMidnight(timestamp)/3600]
+
+ // for localProfilePlugin Synchronisation
+ fun basal() = jsonArray(basal)
+ fun ic(circadian: Boolean = false): JSONArray {
+ if(circadian)
+ return jsonArray(pumpProfile.icBlocks, avgIC/pumpProfileAvgIC)
+ return jsonArray(ic)
+ }
+ fun isf(circadian: Boolean = false): JSONArray {
+ if(circadian)
+ return jsonArray(pumpProfile.isfBlocks, avgISF/pumpProfileAvgISF)
+ return jsonArray(Profile.fromMgdlToUnits(isf, profile.units))
+ }
+
+ fun getProfile(circadian: Boolean = false): PureProfile {
+ return if (circadian)
+ circadianProfile.convertToNonCustomizedProfile(dateUtil)
+ else
+ profile.convertToNonCustomizedProfile(dateUtil)
+ }
+
+ fun updateProfile() {
+ data()?.let { profile = ProfileSealed.Pure(it) }
+ data(true)?.let { circadianProfile = ProfileSealed.Pure(it) }
+ }
+
+ //Export json string with oref0 format used for autotune
+ // Include min_5m_carbimpact, insulin type, single value for carb_ratio and isf
+ fun profiletoOrefJSON(): String {
+ var jsonString = ""
+ val json = JSONObject()
+ val insulinInterface: Insulin = activePlugin.activeInsulin
+ try {
+ json.put("name", profilename)
+ json.put("min_5m_carbimpact", sp.getDouble("openapsama_min_5m_carbimpact", 3.0))
+ json.put("dia", dia)
+ if (insulinInterface.id === Insulin.InsulinType.OREF_ULTRA_RAPID_ACTING) json.put(
+ "curve",
+ "ultra-rapid"
+ ) else if (insulinInterface.id === Insulin.InsulinType.OREF_RAPID_ACTING) json.put("curve", "rapid-acting") else if (insulinInterface.id === Insulin.InsulinType.OREF_LYUMJEV) {
+ json.put("curve", "ultra-rapid")
+ json.put("useCustomPeakTime", true)
+ json.put("insulinPeakTime", 45)
+ } else if (insulinInterface.id === Insulin.InsulinType.OREF_FREE_PEAK) {
+ val peaktime: Int = sp.getInt(rh.gs(R.string.key_insulin_oref_peak), 75)
+ json.put("curve", if (peaktime > 50) "rapid-acting" else "ultra-rapid")
+ json.put("useCustomPeakTime", true)
+ json.put("insulinPeakTime", peaktime)
+ }
+ val basals = JSONArray()
+ for (h in 0..23) {
+ val secondfrommidnight = h * 60 * 60
+ var time: String
+ time = DecimalFormat("00").format(h) + ":00:00"
+ basals.put(
+ JSONObject()
+ .put("start", time)
+ .put("minutes", h * 60)
+ .put("rate", profile.getBasalTimeFromMidnight(secondfrommidnight)
+ )
+ )
+ }
+ json.put("basalprofile", basals)
+ val isfvalue = Round.roundTo(avgISF, 0.001)
+ json.put(
+ "isfProfile",
+ JSONObject().put(
+ "sensitivities",
+ JSONArray().put(JSONObject().put("i", 0).put("start", "00:00:00").put("sensitivity", isfvalue).put("offset", 0).put("x", 0).put("endoffset", 1440))
+ )
+ )
+ json.put("carb_ratio", avgIC)
+ json.put("autosens_max", SafeParse.stringToDouble(sp.getString(R.string.key_openapsama_autosens_max, "1.2")))
+ json.put("autosens_min", SafeParse.stringToDouble(sp.getString(R.string.key_openapsama_autosens_min, "0.7")))
+ json.put("units", GlucoseUnit.MGDL.asText)
+ json.put("timezone", TimeZone.getDefault().id)
+ jsonString = json.toString(2).replace("\\/", "/")
+ } catch (e: JSONException) {}
+
+ return jsonString
+ }
+
+ fun data(circadian: Boolean = false): PureProfile? {
+ val json: JSONObject = profile.toPureNsJson(dateUtil)
+ try {
+ json.put("dia", dia)
+ if (circadian) {
+ json.put("sens", jsonArray(pumpProfile.isfBlocks, avgISF/pumpProfileAvgISF))
+ json.put("carbratio", jsonArray(pumpProfile.icBlocks, avgIC/pumpProfileAvgIC))
+ } else {
+ json.put("sens", jsonArray(Profile.fromMgdlToUnits(isf, profile.units)))
+ json.put("carbratio", jsonArray(ic))
+ }
+ json.put("basal", jsonArray(basal))
+ } catch (e: JSONException) {
+ }
+ return pureProfileFromJson(json, dateUtil, profile.units.asText)
+ }
+
+ fun profileStore(circadian: Boolean = false): ProfileStore?
+ {
+ var profileStore: ProfileStore? = null
+ val json = JSONObject()
+ val store = JSONObject()
+ val tunedProfile = if (circadian) circadianProfile else profile
+ if (profilename.isEmpty())
+ profilename = rh.gs(R.string.autotune_tunedprofile_name)
+ try {
+ store.put(profilename, tunedProfile.toPureNsJson(dateUtil))
+ json.put("defaultProfile", profilename)
+ json.put("store", store)
+ json.put("startDate", dateUtil.toISOAsUTC(dateUtil.now()))
+ profileStore = ProfileStore(injector, json, dateUtil)
+ } catch (e: JSONException) { }
+ return profileStore
+ }
+
+ fun jsonArray(values: DoubleArray): JSONArray {
+ val json = JSONArray()
+ for (h in 0..23) {
+ val secondfrommidnight = h * 60 * 60
+ val df = DecimalFormat("00")
+ val time = df.format(h.toLong()) + ":00"
+ json.put(
+ JSONObject()
+ .put("time", time)
+ .put("timeAsSeconds", secondfrommidnight)
+ .put("value", values[h])
+ )
+ }
+ return json
+ }
+
+ fun jsonArray(value: Double) =
+ JSONArray().put(
+ JSONObject()
+ .put("time", "00:00")
+ .put("timeAsSeconds", 0)
+ .put("value", value)
+ )
+
+ fun jsonArray(values: List, multiplier: Double = 1.0): JSONArray {
+ val json = JSONArray()
+ var elapsedHours = 0L
+ values.forEach {
+ val value = values.blockValueBySeconds(T.hours(elapsedHours).secs().toInt(), multiplier, 0)
+ json.put(
+ JSONObject()
+ .put("time", DecimalFormat("00").format(elapsedHours) + ":00")
+ .put("timeAsSeconds", T.hours(elapsedHours).secs())
+ .put("value", value)
+ )
+ elapsedHours += T.msecs(it.duration).hours()
+ }
+ return json
+ }
+
+ companion object {
+
+ fun averageProfileValue(pf: Array?): Double {
+ var avgValue = 0.0
+ val secondPerDay = 24 * 60 * 60
+ if (pf == null) return avgValue
+ for (i in pf.indices) {
+ avgValue += pf[i].value * ((if (i == pf.size - 1) secondPerDay else pf[i + 1].timeAsSeconds) - pf[i].timeAsSeconds)
+ }
+ avgValue /= secondPerDay.toDouble()
+ return avgValue
+ }
+ }
+
+ init {
+ injector.androidInjector().inject(this)
+ this.profile = profile as ProfileSealed
+ circadianProfile = profile
+ isValid = profile.isValid
+ if (isValid) {
+ //initialize tuned value with current profile values
+ var minBasal = 1.0
+ for (h in 0..23) {
+ basal[h] = Round.roundTo(profile.basalBlocks.blockValueBySeconds(T.hours(h.toLong()).secs().toInt(), 1.0, 0), 0.001)
+ minBasal = Math.min(minBasal, basal[h])
+ }
+ ic = avgIC
+ isf = avgISF
+ if (ic * isf * minBasal == 0.0) // Additional validity check to avoid error later in AutotunePrep
+ isValid = false
+ pumpProfile = profile
+ pumpProfileAvgIC = avgIC
+ pumpProfileAvgISF = avgISF
+ }
+ dia = localInsulin.dia
+ peak = localInsulin.peak
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/data/BGDatum.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/data/BGDatum.kt
new file mode 100644
index 0000000000..7d2f7b6f8c
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/data/BGDatum.kt
@@ -0,0 +1,91 @@
+package info.nightscout.androidaps.plugins.general.autotune.data
+
+import info.nightscout.androidaps.database.entities.GlucoseValue.TrendArrow
+import info.nightscout.androidaps.database.entities.GlucoseValue
+import info.nightscout.androidaps.utils.DateUtil
+import info.nightscout.androidaps.utils.T
+import org.json.JSONException
+import org.json.JSONObject
+import java.util.*
+
+/**
+ * Created by Rumen Georgiev on 2/24/2018.
+ */
+class BGDatum {
+ //Added by Rumen for autotune
+ var id: Long = 0
+ var date = 0L
+ var value = 0.0
+ var direction: TrendArrow? = null
+ var deviation = 0.0
+ var bgi = 0.0
+ var mealAbsorption = ""
+ var mealCarbs = 0
+ var uamAbsorption = ""
+ var avgDelta = 0.0
+ var bgReading: GlucoseValue? = null
+ private set
+ var dateUtil: DateUtil
+
+ constructor(dateUtil: DateUtil) { this.dateUtil = dateUtil}
+ constructor(json: JSONObject, dateUtil: DateUtil) {
+ this.dateUtil = dateUtil
+ try {
+ //if (json.has("_id")) id = json.getLong("_id")
+ if (json.has("date")) date = json.getLong("date")
+ if (json.has("sgv")) value = json.getDouble("sgv")
+ if (json.has("direction")) direction = TrendArrow.fromString(json.getString("direction"))
+ if (json.has("deviation")) deviation = json.getDouble("deviation")
+ if (json.has("BGI")) bgi = json.getDouble("BGI")
+ if (json.has("avgDelta")) avgDelta = json.getDouble("avgDelta")
+ if (json.has("mealAbsorption")) mealAbsorption = json.getString("mealAbsorption")
+ if (json.has("mealCarbs")) mealCarbs = json.getInt("mealCarbs")
+ } catch (e: JSONException) {
+ }
+ }
+
+ constructor(glucoseValue: GlucoseValue, dateUtil: DateUtil) {
+ this.dateUtil = dateUtil
+ date = glucoseValue.timestamp
+ value = glucoseValue.value
+ direction = glucoseValue.trendArrow
+ id = glucoseValue.id
+ this.bgReading = glucoseValue
+ }
+
+ fun toJSON(mealData: Boolean): JSONObject {
+ val bgjson = JSONObject()
+ val utcOffset = T.msecs(TimeZone.getDefault().getOffset(dateUtil.now()).toLong()).hours()
+ try {
+ bgjson.put("_id", id)
+ bgjson.put("date", date)
+ bgjson.put("dateString", dateUtil.toISOAsUTC(date))
+ bgjson.put("sgv", value)
+ bgjson.put("direction", direction)
+ bgjson.put("type", "sgv")
+ bgjson.put("sysTime", dateUtil.toISOAsUTC(date))
+ bgjson.put("utcOffset", utcOffset)
+ bgjson.put("glucose", value)
+ bgjson.put("avgDelta", avgDelta)
+ bgjson.put("BGI", bgi)
+ bgjson.put("deviation", deviation)
+ if (mealData) {
+ bgjson.put("mealAbsorption", mealAbsorption)
+ bgjson.put("mealCarbs", mealCarbs)
+ }
+ } catch (e: JSONException) {
+ }
+ return bgjson
+ }
+
+ fun equals(obj: BGDatum): Boolean {
+ var isEqual = true
+ if (date / 1000 != obj.date / 1000) isEqual = false
+ if (deviation != obj.deviation) isEqual = false
+ if (avgDelta != obj.avgDelta) isEqual = false
+ if (bgi != obj.bgi) isEqual = false
+ if (mealAbsorption != obj.mealAbsorption) isEqual = false
+ if (mealCarbs != obj.mealCarbs) isEqual = false
+ return isEqual
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/data/CRDatum.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/data/CRDatum.kt
new file mode 100644
index 0000000000..5e4c83dc99
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/data/CRDatum.kt
@@ -0,0 +1,67 @@
+package info.nightscout.androidaps.plugins.general.autotune.data
+
+import info.nightscout.androidaps.utils.DateUtil
+import org.json.JSONException
+import org.json.JSONObject
+
+/**
+ * Created by Rumen Georgiev on 2/26/2018.
+ */
+class CRDatum {
+
+ var crInitialIOB = 0.0
+ var crInitialBG = 0.0
+ var crInitialCarbTime = 0L
+ var crEndIOB = 0.0
+ var crEndBG = 0.0
+ var crEndTime = 0L
+ var crCarbs = 0.0
+ var crInsulin = 0.0
+ var crInsulinTotal = 0.0
+ var dateUtil: DateUtil
+
+ constructor(dateUtil: DateUtil) { this.dateUtil = dateUtil}
+ constructor(json: JSONObject, dateUtil: DateUtil) {
+ this.dateUtil = dateUtil
+ try {
+ if (json.has("CRInitialIOB")) crInitialIOB = json.getDouble("CRInitialIOB")
+ if (json.has("CRInitialBG")) crInitialBG = json.getDouble("CRInitialBG")
+ if (json.has("CRInitialCarbTime")) crInitialCarbTime = dateUtil.fromISODateString(json.getString("CRInitialCarbTime"))
+ if (json.has("CREndIOB")) crEndIOB = json.getDouble("CREndIOB")
+ if (json.has("CREndBG")) crEndBG = json.getDouble("CREndBG")
+ if (json.has("CREndTime")) crEndTime = dateUtil.fromISODateString(json.getString("CREndTime"))
+ if (json.has("CRCarbs")) crCarbs = json.getDouble("CRCarbs")
+ if (json.has("CRInsulin")) crInsulin = json.getDouble("CRInsulin")
+ } catch (e: JSONException) {
+ }
+ }
+
+ fun toJSON(): JSONObject {
+ val crjson = JSONObject()
+ try {
+ crjson.put("CRInitialIOB", crInitialIOB)
+ crjson.put("CRInitialBG", crInitialBG.toInt())
+ crjson.put("CRInitialCarbTime", dateUtil.toISOString(crInitialCarbTime))
+ crjson.put("CREndIOB", crEndIOB)
+ crjson.put("CREndBG", crEndBG.toInt())
+ crjson.put("CREndTime", dateUtil.toISOString(crEndTime))
+ crjson.put("CRCarbs", crCarbs.toInt())
+ crjson.put("CRInsulin", crInsulin)
+ } catch (e: JSONException) {
+ }
+ return crjson
+ }
+
+ fun equals(obj: CRDatum): Boolean {
+ var isEqual = true
+ if (crInitialIOB != obj.crInitialIOB) isEqual = false
+ if (crInitialBG != obj.crInitialBG) isEqual = false
+ if (crInitialCarbTime / 1000 != obj.crInitialCarbTime / 1000) isEqual = false
+ if (crEndIOB != obj.crEndIOB) isEqual = false
+ if (crEndBG != obj.crEndBG) isEqual = false
+ if (crEndTime / 1000 != obj.crEndTime / 1000) isEqual = false
+ if (crCarbs != obj.crCarbs) isEqual = false
+ if (crInsulin != obj.crInsulin) isEqual = false
+ return isEqual
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/data/DiaDeviation.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/data/DiaDeviation.kt
new file mode 100644
index 0000000000..9be016d39c
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/data/DiaDeviation.kt
@@ -0,0 +1,29 @@
+package info.nightscout.androidaps.plugins.general.autotune.data
+
+import org.json.JSONException
+import org.json.JSONObject
+
+class DiaDeviation(var dia: Double = 0.0, var meanDeviation: Double = 0.0, var smrDeviation: Double = 0.0, var rmsDeviation: Double = 0.0) {
+
+ constructor(json: JSONObject) : this() {
+ try {
+ if (json.has("dia")) dia = json.getDouble("dia")
+ if (json.has("meanDeviation")) meanDeviation = json.getDouble("meanDeviation")
+ if (json.has("SMRDeviation")) smrDeviation = json.getDouble("SMRDeviation")
+ if (json.has("RMSDeviation")) rmsDeviation = json.getDouble("RMSDeviation")
+ } catch (e: JSONException) {
+ }
+ }
+
+ fun toJSON(): JSONObject {
+ val crjson = JSONObject()
+ try {
+ crjson.put("dia", dia)
+ crjson.put("meanDeviation", meanDeviation.toInt())
+ crjson.put("SMRDeviation", smrDeviation)
+ crjson.put("RMSDeviation", rmsDeviation.toInt())
+ } catch (e: JSONException) {
+ }
+ return crjson
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/data/PeakDeviation.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/data/PeakDeviation.kt
new file mode 100644
index 0000000000..c9e3f66581
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/data/PeakDeviation.kt
@@ -0,0 +1,29 @@
+package info.nightscout.androidaps.plugins.general.autotune.data
+
+import org.json.JSONException
+import org.json.JSONObject
+
+class PeakDeviation(var peak: Int = 0, var meanDeviation: Double = 0.0, var smrDeviation: Double = 0.0, var rmsDeviation: Double = 0.0) {
+
+ constructor(json: JSONObject) : this() {
+ try {
+ if (json.has("peak")) peak = json.getInt("peak")
+ if (json.has("meanDeviation")) meanDeviation = json.getDouble("meanDeviation")
+ if (json.has("SMRDeviation")) smrDeviation = json.getDouble("SMRDeviation")
+ if (json.has("RMSDeviation")) rmsDeviation = json.getDouble("RMSDeviation")
+ } catch (e: JSONException) {
+ }
+ }
+
+ fun toJSON(): JSONObject {
+ val crjson = JSONObject()
+ try {
+ crjson.put("peak", peak)
+ crjson.put("meanDeviation", meanDeviation.toInt())
+ crjson.put("SMRDeviation", smrDeviation)
+ crjson.put("RMSDeviation", rmsDeviation.toInt())
+ } catch (e: JSONException) {
+ }
+ return crjson
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/data/PreppedGlucose.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/data/PreppedGlucose.kt
new file mode 100644
index 0000000000..3015dd6122
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/data/PreppedGlucose.kt
@@ -0,0 +1,117 @@
+package info.nightscout.androidaps.plugins.general.autotune.data
+
+import info.nightscout.androidaps.utils.DateUtil
+import org.json.JSONArray
+import org.json.JSONException
+import org.json.JSONObject
+import java.util.*
+
+class PreppedGlucose {
+
+ var crData: List = ArrayList()
+ var csfGlucoseData: List = ArrayList()
+ var isfGlucoseData: List = ArrayList()
+ var basalGlucoseData: List = ArrayList()
+ var diaDeviations: List = ArrayList()
+ var peakDeviations: List = ArrayList()
+ var from: Long = 0
+ lateinit var dateUtil: DateUtil
+
+ // to generate same king of json string than oref0-autotune-prep
+ override fun toString(): String {
+ return toString(0)
+ }
+
+ constructor(from: Long, crData: List, csfGlucoseData: List, isfGlucoseData: List, basalGlucoseData: List, dateUtil: DateUtil) {
+ this.from = from
+ this.crData = crData
+ this.csfGlucoseData = csfGlucoseData
+ this.isfGlucoseData = isfGlucoseData
+ this.basalGlucoseData = basalGlucoseData
+ this.dateUtil = dateUtil
+ }
+
+ constructor(json: JSONObject?, dateUtil: DateUtil) {
+ if (json == null) return
+ this.dateUtil = dateUtil
+ crData = ArrayList()
+ csfGlucoseData = ArrayList()
+ isfGlucoseData = ArrayList()
+ basalGlucoseData = ArrayList()
+ try {
+ crData = JsonCRDataToList(json.getJSONArray("CRData"))
+ csfGlucoseData = JsonGlucoseDataToList(json.getJSONArray("CSFGlucoseData"))
+ isfGlucoseData = JsonGlucoseDataToList(json.getJSONArray("ISFGlucoseData"))
+ basalGlucoseData = JsonGlucoseDataToList(json.getJSONArray("basalGlucoseData"))
+ } catch (e: JSONException) {
+ }
+ }
+
+ private fun JsonGlucoseDataToList(array: JSONArray): List {
+ val bgData: MutableList = ArrayList()
+ for (index in 0 until array.length()) {
+ try {
+ val o = array.getJSONObject(index)
+ bgData.add(BGDatum(o, dateUtil))
+ } catch (e: Exception) {
+ }
+ }
+ return bgData
+ }
+
+ private fun JsonCRDataToList(array: JSONArray): List {
+ val crData: MutableList = ArrayList()
+ for (index in 0 until array.length()) {
+ try {
+ val o = array.getJSONObject(index)
+ crData.add(CRDatum(o, dateUtil))
+ } catch (e: Exception) {
+ }
+ }
+ return crData
+ }
+
+ fun toString(indent: Int): String {
+ var jsonString = ""
+ val json = JSONObject()
+ try {
+ val crjson = JSONArray()
+ for (crd in crData) {
+ crjson.put(crd.toJSON())
+ }
+ val csfjson = JSONArray()
+ for (bgd in csfGlucoseData) {
+ csfjson.put(bgd.toJSON(true))
+ }
+ val isfjson = JSONArray()
+ for (bgd in isfGlucoseData) {
+ isfjson.put(bgd.toJSON(false))
+ }
+ val basaljson = JSONArray()
+ for (bgd in basalGlucoseData) {
+ basaljson.put(bgd.toJSON(false))
+ }
+ val diajson = JSONArray()
+ val peakjson = JSONArray()
+ if (diaDeviations.size > 0 || peakDeviations.size > 0) {
+ for (diad in diaDeviations) {
+ diajson.put(diad.toJSON())
+ }
+ for (peakd in peakDeviations) {
+ peakjson.put(peakd.toJSON())
+ }
+ }
+ json.put("CRData", crjson)
+ json.put("CSFGlucoseData", csfjson)
+ json.put("ISFGlucoseData", isfjson)
+ json.put("basalGlucoseData", basaljson)
+ if (diaDeviations.size > 0 || peakDeviations.size > 0) {
+ json.put("diaDeviations", diajson)
+ json.put("peakDeviations", peakjson)
+ }
+ jsonString = if (indent != 0) json.toString(indent) else json.toString()
+ } catch (e: JSONException) {
+ }
+ return jsonString
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/events/EventAutotuneUpdateGui.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/events/EventAutotuneUpdateGui.kt
new file mode 100644
index 0000000000..2a607d475e
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/autotune/events/EventAutotuneUpdateGui.kt
@@ -0,0 +1,5 @@
+package info.nightscout.androidaps.plugins.general.autotune.events
+
+import info.nightscout.androidaps.events.Event
+
+class EventAutotuneUpdateGui : Event()
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/dataBroadcaster/DataBroadcastPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/dataBroadcaster/DataBroadcastPlugin.kt
index 5b10d02649..26dd91eb91 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/dataBroadcaster/DataBroadcastPlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/dataBroadcaster/DataBroadcastPlugin.kt
@@ -6,12 +6,11 @@ import android.content.pm.ResolveInfo
import android.os.Bundle
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R
-import info.nightscout.androidaps.events.*
+import info.nightscout.androidaps.events.Event
+import info.nightscout.androidaps.events.EventAutosensCalculationFinished
import info.nightscout.androidaps.extensions.durationInMinutes
import info.nightscout.androidaps.extensions.toStringFull
import info.nightscout.androidaps.interfaces.*
-import info.nightscout.shared.logging.AAPSLogger
-import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.plugins.aps.events.EventOpenAPSUpdateGui
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.nsclient.data.DeviceStatusData
@@ -19,13 +18,15 @@ import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus
import info.nightscout.androidaps.plugins.general.overview.events.EventOverviewBolusProgress
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProvider
import info.nightscout.androidaps.receivers.ReceiverStatusStore
-import info.nightscout.androidaps.services.Intents
+import info.nightscout.androidaps.receivers.Intents
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.DefaultValueHelper
import info.nightscout.androidaps.utils.FabricPrivacy
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
-import io.reactivex.disposables.CompositeDisposable
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.shared.logging.LTag
+import io.reactivex.rxjava3.disposables.CompositeDisposable
import javax.inject.Inject
import javax.inject.Singleton
@@ -68,26 +69,6 @@ class DataBroadcastPlugin @Inject constructor(
.observeOn(aapsSchedulers.io)
.subscribe({ sendData(it) }, fabricPrivacy::logException)
)
- disposable.add(rxBus
- .toObservable(EventExtendedBolusChange::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({ sendData(it) }, fabricPrivacy::logException)
- )
- disposable.add(rxBus
- .toObservable(EventTempBasalChange::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({ sendData(it) }, fabricPrivacy::logException)
- )
- disposable.add(rxBus
- .toObservable(EventTreatmentChange::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({ sendData(it) }, fabricPrivacy::logException)
- )
- disposable.add(rxBus
- .toObservable(EventEffectiveProfileSwitchChanged::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({ sendData(it) }, fabricPrivacy::logException)
- )
disposable.add(rxBus
.toObservable(EventAutosensCalculationFinished::class.java)
.observeOn(aapsSchedulers.io)
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/food/FoodFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/food/FoodFragment.kt
index 4507d85ab3..4ca21b858d 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/food/FoodFragment.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/food/FoodFragment.kt
@@ -1,7 +1,6 @@
package info.nightscout.androidaps.plugins.general.food
import android.annotation.SuppressLint
-import android.graphics.Paint
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
@@ -18,32 +17,27 @@ import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.entities.Food
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.InvalidateFoodTransaction
import info.nightscout.androidaps.databinding.FoodFragmentBinding
import info.nightscout.androidaps.databinding.FoodItemBinding
import info.nightscout.androidaps.dialogs.WizardDialog
import info.nightscout.androidaps.events.EventFoodDatabaseChanged
-import info.nightscout.shared.logging.AAPSLogger
-import info.nightscout.shared.logging.LTag
+import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBus
-import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
-import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.utils.protection.ProtectionCheck
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.androidaps.utils.ui.UIRunnable
-import io.reactivex.Completable
-import io.reactivex.disposables.CompositeDisposable
-import io.reactivex.rxkotlin.plusAssign
-import io.reactivex.rxkotlin.subscribeBy
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.shared.logging.LTag
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.kotlin.plusAssign
import java.util.*
import java.util.concurrent.TimeUnit
import javax.inject.Inject
-import kotlin.collections.ArrayList
class FoodFragment : DaggerFragment() {
@@ -66,10 +60,8 @@ class FoodFragment : DaggerFragment() {
// onDestroyView.
private val binding get() = _binding!!
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
- _binding = FoodFragmentBinding.inflate(inflater, container, false)
- return binding.root
- }
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
+ FoodFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@@ -77,50 +69,14 @@ class FoodFragment : DaggerFragment() {
binding.recyclerview.setHasFixedSize(true)
binding.recyclerview.layoutManager = LinearLayoutManager(view.context)
- binding.refreshFromNightscout.setOnClickListener {
- context?.let { context ->
- OKDialog.showConfirmation(context, rh.gs(R.string.refresheventsfromnightscout) + " ?", {
- uel.log(Action.FOOD, Sources.Food, rh.gs(R.string.refresheventsfromnightscout),
- ValueWithUnit.SimpleString(rh.gsNotLocalised(R.string.refresheventsfromnightscout)))
- disposable += Completable.fromAction { repository.deleteAllFoods() }
- .subscribeOn(aapsSchedulers.io)
- .observeOn(aapsSchedulers.main)
- .subscribeBy(
- onError = { aapsLogger.error("Error removing foods", it) },
- onComplete = { rxBus.send(EventFoodDatabaseChanged()) }
- )
-
- rxBus.send(EventNSClientRestart())
- })
- }
- }
-
- binding.clearfilter.setOnClickListener {
+ binding.filterInputLayout.setEndIconOnClickListener {
binding.filter.setText("")
- binding.category.setSelection(0)
- binding.subcategory.setSelection(0)
+ binding.categoryList.setText(rh.gs(R.string.none), false)
+ binding.subcategoryList.setText(rh.gs(R.string.none), false)
filterData()
}
- binding.category.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
- override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
- fillSubcategories()
- filterData()
- }
-
- override fun onNothingSelected(parent: AdapterView<*>?) {
- fillSubcategories()
- filterData()
- }
- }
- binding.subcategory.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
- override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
- filterData()
- }
-
- override fun onNothingSelected(parent: AdapterView<*>?) {
- filterData()
- }
- }
+ binding.categoryList.onItemClickListener = AdapterView.OnItemClickListener { _, _, _, _ -> fillSubcategories(); filterData() }
+ binding.subcategoryList.onItemClickListener = AdapterView.OnItemClickListener { _, _, _, _ -> filterData() }
binding.filter.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
@@ -134,12 +90,11 @@ class FoodFragment : DaggerFragment() {
@Synchronized
override fun onResume() {
super.onResume()
- disposable.add(rxBus
+ disposable += rxBus
.toObservable(EventFoodDatabaseChanged::class.java)
.observeOn(aapsSchedulers.main)
.debounce(1L, TimeUnit.SECONDS)
.subscribe({ swapAdapter() }, fabricPrivacy::logException)
- )
swapAdapter()
}
@@ -178,13 +133,13 @@ class FoodFragment : DaggerFragment() {
val categories = ArrayList(catSet)
categories.add(0, rh.gs(R.string.none))
context?.let { context ->
- val adapterCategories = ArrayAdapter(context, R.layout.spinner_centered, categories)
- binding.category.adapter = adapterCategories
+ binding.categoryList.setAdapter(ArrayAdapter(context, R.layout.spinner_centered, categories))
+ binding.categoryList.setText(rh.gs(R.string.none), false)
}
}
private fun fillSubcategories() {
- val categoryFilter = binding.category.selectedItem.toString()
+ val categoryFilter = binding.categoryList.text.toString()
val subCatSet: MutableSet = HashSet()
if (categoryFilter != rh.gs(R.string.none)) {
for (f in unfiltered) {
@@ -198,17 +153,15 @@ class FoodFragment : DaggerFragment() {
val subcategories = ArrayList(subCatSet)
subcategories.add(0, rh.gs(R.string.none))
context?.let { context ->
- val adapterSubcategories = ArrayAdapter(context, R.layout.spinner_centered, subcategories)
- binding.subcategory.adapter = adapterSubcategories
+ binding.subcategoryList.setAdapter(ArrayAdapter(context, R.layout.spinner_centered, subcategories))
+ binding.subcategoryList.setText(rh.gs(R.string.none), false)
}
}
private fun filterData() {
val textFilter = binding.filter.text.toString()
- val categoryFilter = binding.category.selectedItem?.toString()
- ?: rh.gs(R.string.none)
- val subcategoryFilter = binding.subcategory.selectedItem?.toString()
- ?: rh.gs(R.string.none)
+ val categoryFilter = binding.categoryList.text.toString()
+ val subcategoryFilter = binding.subcategoryList.text.toString()
val newFiltered = ArrayList()
for (f in unfiltered) {
if (f.category == null || f.subCategory == null) continue
@@ -243,7 +196,6 @@ class FoodFragment : DaggerFragment() {
holder.binding.energy.text = rh.gs(R.string.shortenergy) + ": " + food.energy + rh.gs(R.string.shortkilojoul)
holder.binding.energy.visibility = food.energy.isNotZero().toVisibility()
holder.binding.icRemove.tag = food
- holder.binding.foodItem.tag = food
holder.binding.icCalculator.tag = food
}
@@ -267,18 +219,17 @@ class FoodFragment : DaggerFragment() {
}, null)
}
}
- binding.icCalculator.setOnClickListener { v:View ->
+ binding.icCalculator.setOnClickListener { v: View ->
val food = v.tag as Food
activity?.let { activity ->
protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, UIRunnable {
- if (isAdded) {
- val wizardDialog = WizardDialog()
- val bundle = Bundle()
- bundle.putInt("carbs_input", food.carbs)
- bundle.putString("notes_input", " ${food.name} - ${food.carbs}g")
- wizardDialog.setArguments(bundle)
- wizardDialog.show(childFragmentManager, "Food Item")
- }
+ if (isAdded)
+ WizardDialog().also { dialog ->
+ dialog.arguments = Bundle().also { bundle ->
+ bundle.putDouble("carbs_input", food.carbs.toDouble())
+ bundle.putString("notes_input", " ${food.name} - ${food.carbs}g")
+ }
+ }.show(childFragmentManager, "Food Item")
})
}
}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/food/FoodPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/food/FoodPlugin.kt
index 233d9ba78b..9bc829632c 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/food/FoodPlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/food/FoodPlugin.kt
@@ -17,7 +17,7 @@ import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.receivers.DataWorker
import info.nightscout.androidaps.utils.JsonHelper
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONObject
import javax.inject.Inject
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/ImportExportPrefsImpl.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/ImportExportPrefsImpl.kt
index 397f71248a..6e6e0ab684 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/ImportExportPrefsImpl.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/ImportExportPrefsImpl.kt
@@ -8,6 +8,7 @@ import android.content.pm.PackageManager
import android.provider.Settings
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
+import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
@@ -21,9 +22,11 @@ import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.entities.UserEntry
import info.nightscout.androidaps.database.entities.UserEntry.Action
import info.nightscout.androidaps.database.entities.UserEntry.Sources
+import info.nightscout.androidaps.diaconn.events.EventDiaconnG8PumpLogReset
import info.nightscout.androidaps.events.EventAppExit
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.interfaces.ImportExportPrefs
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.maintenance.formats.*
@@ -36,9 +39,8 @@ import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.alertDialogs.PrefImportSummaryDialog
import info.nightscout.androidaps.utils.alertDialogs.TwoMessagesAlertDialog
import info.nightscout.androidaps.utils.alertDialogs.WarningDialog
-import info.nightscout.androidaps.utils.buildHelper.BuildHelper
+import info.nightscout.androidaps.interfaces.BuildHelper
import info.nightscout.androidaps.utils.protection.PasswordCheck
-import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.storage.Storage
import info.nightscout.androidaps.utils.userEntry.UserEntryPresentationHelper
import info.nightscout.shared.logging.AAPSLogger
@@ -111,7 +113,9 @@ class ImportExportPrefsImpl @Inject constructor(
val n1 = Settings.System.getString(context.contentResolver, "bluetooth_name")
val n2 = Settings.Secure.getString(context.contentResolver, "bluetooth_name")
val n3 = try {
- (context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager?)?.adapter?.name
+ if (ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED) {
+ (context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager?)?.adapter?.name
+ } else null
} catch (e: Exception){
null
}
@@ -339,6 +343,7 @@ class ImportExportPrefsImpl @Inject constructor(
}
private fun restartAppAfterImport(context: Context) {
+ rxBus.send(EventDiaconnG8PumpLogReset())
sp.putBoolean(R.string.key_setupwizard_processed, true)
OKDialog.show(context, rh.gs(R.string.setting_imported), rh.gs(R.string.restartingapp)) {
uel.log(Action.IMPORT_SETTINGS, Sources.Maintenance)
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenanceFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenanceFragment.kt
index b39883538b..d2085c0194 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenanceFragment.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenanceFragment.kt
@@ -7,6 +7,7 @@ import android.view.View
import android.view.ViewGroup
import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.R
+import info.nightscout.androidaps.activities.SingleFragmentActivity
import info.nightscout.androidaps.dana.database.DanaHistoryDatabase
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.entities.UserEntry.Action
@@ -14,6 +15,7 @@ import info.nightscout.androidaps.database.entities.UserEntry.Sources
import info.nightscout.androidaps.databinding.MaintenanceFragmentBinding
import info.nightscout.androidaps.diaconn.database.DiaconnHistoryDatabase
import info.nightscout.androidaps.events.EventPreferenceChange
+import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.insight.database.InsightDatabase
import info.nightscout.androidaps.interfaces.DataSyncSelector
import info.nightscout.androidaps.interfaces.ImportExportPrefs
@@ -27,11 +29,13 @@ import info.nightscout.androidaps.plugins.general.overview.OverviewData
import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.database.DashHistoryDatabase
import info.nightscout.androidaps.plugins.pump.omnipod.eros.history.database.ErosHistoryDatabase
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.utils.protection.ProtectionCheck
+import info.nightscout.androidaps.utils.protection.ProtectionCheck.Protection.PREFERENCES
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
-import io.reactivex.Completable.fromAction
-import io.reactivex.disposables.CompositeDisposable
-import io.reactivex.rxkotlin.subscribeBy
+import io.reactivex.rxjava3.core.Completable.fromAction
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.kotlin.subscribeBy
import javax.inject.Inject
class MaintenanceFragment : DaggerFragment() {
@@ -48,6 +52,7 @@ class MaintenanceFragment : DaggerFragment() {
@Inject lateinit var diaconnDatabase: DiaconnHistoryDatabase
@Inject lateinit var erosDatabase: ErosHistoryDatabase
@Inject lateinit var dashDatabase: DashHistoryDatabase
+ @Inject lateinit var protectionCheck: ProtectionCheck
@Inject lateinit var uel: UserEntryLogger
@Inject lateinit var dataSyncSelector: DataSyncSelector
@Inject lateinit var pumpSync: PumpSync
@@ -55,11 +60,11 @@ class MaintenanceFragment : DaggerFragment() {
@Inject lateinit var overviewData: OverviewData
private val compositeDisposable = CompositeDisposable()
-
+ private var inMenu = false
+ private var queryingProtection = false
private var _binding: MaintenanceFragmentBinding? = null
- // This property is only valid between onCreateView and
- // onDestroyView.
+ // This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
@@ -69,6 +74,9 @@ class MaintenanceFragment : DaggerFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
+ val parentClass = this.activity?.let { it::class.java }
+ inMenu = parentClass == SingleFragmentActivity::class.java
+ updateProtectedUi()
binding.logSend.setOnClickListener { maintenancePlugin.sendLogs() }
binding.logDelete.setOnClickListener {
uel.log(Action.DELETE_LOGS, Sources.Maintenance)
@@ -128,6 +136,13 @@ class MaintenanceFragment : DaggerFragment() {
}
}
}
+
+ binding.unlock.setOnClickListener { queryProtection() }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ if (inMenu) queryProtection() else updateProtectedUi()
}
@Synchronized
@@ -136,4 +151,21 @@ class MaintenanceFragment : DaggerFragment() {
compositeDisposable.clear()
_binding = null
}
-}
\ No newline at end of file
+
+ private fun updateProtectedUi() {
+ val isLocked = protectionCheck.isLocked(PREFERENCES)
+ binding.mainLayout.visibility = isLocked.not().toVisibility()
+ binding.unlock.visibility = isLocked.toVisibility()
+ }
+
+ private fun queryProtection() {
+ val isLocked = protectionCheck.isLocked(PREFERENCES)
+ if (isLocked && !queryingProtection) {
+ activity?.let { activity ->
+ queryingProtection = true
+ val doUpdate = { activity.runOnUiThread { queryingProtection = false; updateProtectedUi() } }
+ protectionCheck.queryProtection(activity, PREFERENCES, doUpdate, doUpdate, doUpdate)
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenancePlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenancePlugin.kt
index 377a39d7b8..e02c373bde 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenancePlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenancePlugin.kt
@@ -13,8 +13,8 @@ import info.nightscout.androidaps.interfaces.PluginDescription
import info.nightscout.androidaps.interfaces.PluginType
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.androidaps.plugins.general.nsclient.data.NSSettingsStatus
-import info.nightscout.androidaps.utils.buildHelper.BuildHelper
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.BuildHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import java.io.*
import java.util.*
@@ -64,18 +64,29 @@ class MaintenancePlugin @Inject constructor(
context.startActivity(emailIntent)
}
- //todo replace this with a call on startup of the application, specifically to remove
- // unnecessary garbage from the log exports
fun deleteLogs(keep: Int) {
val logDir = File(loggerUtils.logDirectory)
val files = logDir.listFiles { _: File?, name: String ->
(name.startsWith("AndroidAPS") && name.endsWith(".zip"))
}
+ val autotunefiles = logDir.listFiles { _: File?, name: String ->
+ (name.startsWith("autotune") && name.endsWith(".zip"))
+ }
+ val amount = sp.getInt(R.string.key_logshipper_amount, keep)
+ val keepIndex = amount - 1
+ if (autotunefiles != null && autotunefiles.isNotEmpty()) {
+ Arrays.sort(autotunefiles) { f1: File, f2: File -> f2.name.compareTo(f1.name) }
+ var delAutotuneFiles = listOf(*autotunefiles)
+ if (keepIndex < delAutotuneFiles.size) {
+ delAutotuneFiles = delAutotuneFiles.subList(keepIndex, delAutotuneFiles.size)
+ for (file in delAutotuneFiles) {
+ file.delete()
+ }
+ }
+ }
if (files == null || files.isEmpty()) return
Arrays.sort(files) { f1: File, f2: File -> f2.name.compareTo(f1.name) }
var delFiles = listOf(*files)
- val amount = sp.getInt(R.string.key_logshipper_amount, keep)
- val keepIndex = amount - 1
if (keepIndex < delFiles.size) {
delFiles = delFiles.subList(keepIndex, delFiles.size)
for (file in delFiles) {
@@ -137,7 +148,7 @@ class MaintenancePlugin @Inject constructor(
* @return
*/
private fun constructName(): String {
- return "AndroidAPS_LOG_" + Date().time + loggerUtils.suffix
+ return "AndroidAPS_LOG_" + System.currentTimeMillis() + loggerUtils.suffix
}
private fun zip(zipFile: File?, files: List) {
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/DataSyncSelectorImplementation.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/DataSyncSelectorImplementation.kt
index f3ab04a81f..ff88b7e38f 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/DataSyncSelectorImplementation.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/DataSyncSelectorImplementation.kt
@@ -286,14 +286,14 @@ class DataSyncSelectorImplementation @Inject constructor(
bolusCalculatorResult.first.interfaceIDs.nightscoutId == null ->
nsClientPlugin.nsClientService?.dbAdd(
"treatments",
- bolusCalculatorResult.first.toJson(true, dateUtil),
+ bolusCalculatorResult.first.toJson(true, dateUtil, profileFunction),
DataSyncSelector.PairBolusCalculatorResult(bolusCalculatorResult.first, bolusCalculatorResult.second.id),
"$startId/$lastDbId"
)
// with nsId = update
bolusCalculatorResult.first.interfaceIDs.nightscoutId != null ->
nsClientPlugin.nsClientService?.dbUpdate(
- "treatments", bolusCalculatorResult.first.interfaceIDs.nightscoutId, bolusCalculatorResult.first.toJson(false, dateUtil),
+ "treatments", bolusCalculatorResult.first.interfaceIDs.nightscoutId, bolusCalculatorResult.first.toJson(false, dateUtil, profileFunction),
DataSyncSelector.PairBolusCalculatorResult(bolusCalculatorResult.first, bolusCalculatorResult.second.id), "$startId/$lastDbId"
)
}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientAddUpdateWorker.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientAddUpdateWorker.kt
index 78f69cf5de..c0b1210cb9 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientAddUpdateWorker.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientAddUpdateWorker.kt
@@ -27,7 +27,8 @@ import info.nightscout.androidaps.receivers.DataWorker
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.JsonHelper
import info.nightscout.androidaps.utils.JsonHelper.safeGetLong
-import info.nightscout.androidaps.utils.buildHelper.BuildHelper
+import info.nightscout.androidaps.interfaces.BuildHelper
+import info.nightscout.androidaps.utils.XDripBroadcast
import info.nightscout.shared.sharedPreferences.SP
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@@ -49,6 +50,7 @@ class NSClientAddUpdateWorker(
@Inject lateinit var rxBus: RxBus
@Inject lateinit var uel: UserEntryLogger
@Inject lateinit var virtualPumpPlugin: VirtualPumpPlugin
+ @Inject lateinit var xDripBroadcast: XDripBroadcast
override fun doWork(): Result {
val treatments = dataWorker.pickupJSONArray(inputData.getLong(DataWorker.STORE_KEY, -1))
@@ -85,14 +87,16 @@ class NSClientAddUpdateWorker(
.blockingGet()
.also { result ->
result.inserted.forEach {
- uel.log(Action.BOLUS, Sources.NSClient,
+ uel.log(
+ Action.BOLUS, Sources.NSClient,
ValueWithUnit.Timestamp(it.timestamp),
ValueWithUnit.Insulin(it.amount)
)
aapsLogger.debug(LTag.DATABASE, "Inserted bolus $it")
}
result.invalidated.forEach {
- uel.log(Action.BOLUS_REMOVED, Sources.NSClient,
+ uel.log(
+ Action.BOLUS_REMOVED, Sources.NSClient,
ValueWithUnit.Timestamp(it.timestamp),
ValueWithUnit.Insulin(it.amount)
)
@@ -119,21 +123,24 @@ class NSClientAddUpdateWorker(
.blockingGet()
.also { result ->
result.inserted.forEach {
- uel.log(Action.CARBS, Sources.NSClient,
+ uel.log(
+ Action.CARBS, Sources.NSClient,
ValueWithUnit.Timestamp(it.timestamp),
ValueWithUnit.Gram(it.amount.toInt())
)
aapsLogger.debug(LTag.DATABASE, "Inserted carbs $it")
}
result.invalidated.forEach {
- uel.log(Action.CARBS_REMOVED, Sources.NSClient,
+ uel.log(
+ Action.CARBS_REMOVED, Sources.NSClient,
ValueWithUnit.Timestamp(it.timestamp),
ValueWithUnit.Gram(it.amount.toInt())
)
aapsLogger.debug(LTag.DATABASE, "Invalidated carbs $it")
}
result.updated.forEach {
- uel.log(Action.CARBS, Sources.NSClient,
+ uel.log(
+ Action.CARBS, Sources.NSClient,
ValueWithUnit.Timestamp(it.timestamp),
ValueWithUnit.Gram(it.amount.toInt())
)
@@ -157,8 +164,8 @@ class NSClientAddUpdateWorker(
virtualPumpPlugin.fakeDataDetected = true
}
when {
- insulin > 0 || carbs > 0 -> Any()
- eventType == TherapyEvent.Type.TEMPORARY_TARGET.text ->
+ insulin > 0 || carbs > 0 -> Any()
+ eventType == TherapyEvent.Type.TEMPORARY_TARGET.text ->
if (sp.getBoolean(R.string.key_ns_receive_temp_target, false) || config.NSCLIENT) {
temporaryTargetFromJson(json)?.let { temporaryTarget ->
repository.runTransactionForResult(SyncNsTemporaryTargetTransaction(temporaryTarget))
@@ -169,7 +176,8 @@ class NSClientAddUpdateWorker(
.blockingGet()
.also { result ->
result.inserted.forEach { tt ->
- uel.log(Action.TT, Sources.NSClient,
+ uel.log(
+ Action.TT, Sources.NSClient,
ValueWithUnit.TherapyEventTTReason(tt.reason),
ValueWithUnit.fromGlucoseUnit(tt.lowTarget, Constants.MGDL),
ValueWithUnit.fromGlucoseUnit(tt.highTarget, Constants.MGDL).takeIf { tt.lowTarget != tt.highTarget },
@@ -178,7 +186,8 @@ class NSClientAddUpdateWorker(
aapsLogger.debug(LTag.DATABASE, "Inserted TemporaryTarget $tt")
}
result.invalidated.forEach { tt ->
- uel.log(Action.TT_REMOVED, Sources.NSClient,
+ uel.log(
+ Action.TT_REMOVED, Sources.NSClient,
ValueWithUnit.TherapyEventTTReason(tt.reason),
ValueWithUnit.Mgdl(tt.lowTarget),
ValueWithUnit.Mgdl(tt.highTarget).takeIf { tt.lowTarget != tt.highTarget },
@@ -187,7 +196,8 @@ class NSClientAddUpdateWorker(
aapsLogger.debug(LTag.DATABASE, "Invalidated TemporaryTarget $tt")
}
result.ended.forEach { tt ->
- uel.log(Action.CANCEL_TT, Sources.NSClient,
+ uel.log(
+ Action.CANCEL_TT, Sources.NSClient,
ValueWithUnit.TherapyEventTTReason(tt.reason),
ValueWithUnit.Mgdl(tt.lowTarget),
ValueWithUnit.Mgdl(tt.highTarget).takeIf { tt.lowTarget != tt.highTarget },
@@ -204,7 +214,7 @@ class NSClientAddUpdateWorker(
}
} ?: aapsLogger.error("Error parsing TT json $json")
}
- eventType == TherapyEvent.Type.NOTE.text && json.isEffectiveProfileSwitch() -> // replace this by new Type when available in NS
+ eventType == TherapyEvent.Type.NOTE.text && json.isEffectiveProfileSwitch() -> // replace this by new Type when available in NS
if (sp.getBoolean(R.string.key_ns_receive_profile_switch, false) || config.NSCLIENT) {
effectiveProfileSwitchFromJson(json, dateUtil)?.let { effectiveProfileSwitch ->
repository.runTransactionForResult(SyncNsEffectiveProfileSwitchTransaction(effectiveProfileSwitch))
@@ -215,13 +225,17 @@ class NSClientAddUpdateWorker(
.blockingGet()
.also { result ->
result.inserted.forEach {
- uel.log(Action.PROFILE_SWITCH, Sources.NSClient,
- ValueWithUnit.Timestamp(it.timestamp))
+ uel.log(
+ Action.PROFILE_SWITCH, Sources.NSClient,
+ ValueWithUnit.Timestamp(it.timestamp)
+ )
aapsLogger.debug(LTag.DATABASE, "Inserted EffectiveProfileSwitch $it")
}
result.invalidated.forEach {
- uel.log(Action.PROFILE_SWITCH_REMOVED, Sources.NSClient,
- ValueWithUnit.Timestamp(it.timestamp))
+ uel.log(
+ Action.PROFILE_SWITCH_REMOVED, Sources.NSClient,
+ ValueWithUnit.Timestamp(it.timestamp)
+ )
aapsLogger.debug(LTag.DATABASE, "Invalidated EffectiveProfileSwitch $it")
}
result.updatedNsId.forEach {
@@ -230,34 +244,34 @@ class NSClientAddUpdateWorker(
}
} ?: aapsLogger.error("Error parsing EffectiveProfileSwitch json $json")
}
- eventType == TherapyEvent.Type.BOLUS_WIZARD.text ->
- if (config.NSCLIENT) {
- bolusCalculatorResultFromJson(json)?.let { bolusCalculatorResult ->
- repository.runTransactionForResult(SyncNsBolusCalculatorResultTransaction(bolusCalculatorResult))
- .doOnError {
- aapsLogger.error(LTag.DATABASE, "Error while saving BolusCalculatorResult", it)
- ret = Result.failure(workDataOf("Error" to it.toString()))
+ eventType == TherapyEvent.Type.BOLUS_WIZARD.text ->
+ bolusCalculatorResultFromJson(json)?.let { bolusCalculatorResult ->
+ repository.runTransactionForResult(SyncNsBolusCalculatorResultTransaction(bolusCalculatorResult))
+ .doOnError {
+ aapsLogger.error(LTag.DATABASE, "Error while saving BolusCalculatorResult", it)
+ ret = Result.failure(workDataOf("Error" to it.toString()))
+ }
+ .blockingGet()
+ .also { result ->
+ result.inserted.forEach {
+ uel.log(
+ Action.BOLUS_CALCULATOR_RESULT, Sources.NSClient,
+ ValueWithUnit.Timestamp(it.timestamp),
+ )
+ aapsLogger.debug(LTag.DATABASE, "Inserted BolusCalculatorResult $it")
}
- .blockingGet()
- .also { result ->
- result.inserted.forEach {
- uel.log(Action.BOLUS_CALCULATOR_RESULT, Sources.NSClient,
- ValueWithUnit.Timestamp(it.timestamp),
- )
- aapsLogger.debug(LTag.DATABASE, "Inserted BolusCalculatorResult $it")
- }
- result.invalidated.forEach {
- uel.log(Action.BOLUS_CALCULATOR_RESULT_REMOVED, Sources.NSClient,
- ValueWithUnit.Timestamp(it.timestamp),
- )
- aapsLogger.debug(LTag.DATABASE, "Invalidated BolusCalculatorResult $it")
- }
- result.updatedNsId.forEach {
- aapsLogger.debug(LTag.DATABASE, "Updated nsId BolusCalculatorResult $it")
- }
+ result.invalidated.forEach {
+ uel.log(
+ Action.BOLUS_CALCULATOR_RESULT_REMOVED, Sources.NSClient,
+ ValueWithUnit.Timestamp(it.timestamp),
+ )
+ aapsLogger.debug(LTag.DATABASE, "Invalidated BolusCalculatorResult $it")
}
- } ?: aapsLogger.error("Error parsing BolusCalculatorResult json $json")
- }
+ result.updatedNsId.forEach {
+ aapsLogger.debug(LTag.DATABASE, "Updated nsId BolusCalculatorResult $it")
+ }
+ }
+ } ?: aapsLogger.error("Error parsing BolusCalculatorResult json $json")
eventType == TherapyEvent.Type.CANNULA_CHANGE.text ||
eventType == TherapyEvent.Type.INSULIN_CHANGE.text ||
eventType == TherapyEvent.Type.SENSOR_CHANGE.text ||
@@ -267,7 +281,7 @@ class NSClientAddUpdateWorker(
eventType == TherapyEvent.Type.QUESTION.text ||
eventType == TherapyEvent.Type.EXERCISE.text ||
eventType == TherapyEvent.Type.NOTE.text ||
- eventType == TherapyEvent.Type.PUMP_BATTERY_CHANGE.text ->
+ eventType == TherapyEvent.Type.PUMP_BATTERY_CHANGE.text ->
if (sp.getBoolean(R.string.key_ns_receive_therapy_events, false) || config.NSCLIENT) {
therapyEventFromJson(json)?.let { therapyEvent ->
repository.runTransactionForResult(SyncNsTherapyEventTransaction(therapyEvent))
@@ -285,18 +299,18 @@ class NSClientAddUpdateWorker(
result.inserted.forEach { therapyEvent ->
uel.log(action, Sources.NSClient,
therapyEvent.note ?: "",
- ValueWithUnit.Timestamp(therapyEvent.timestamp),
- ValueWithUnit.TherapyEventType(therapyEvent.type),
- ValueWithUnit.fromGlucoseUnit(therapyEvent.glucose ?:0.0,therapyEvent.glucoseUnit.toString).takeIf { therapyEvent.glucose != null }
+ ValueWithUnit.Timestamp(therapyEvent.timestamp),
+ ValueWithUnit.TherapyEventType(therapyEvent.type),
+ ValueWithUnit.fromGlucoseUnit(therapyEvent.glucose ?: 0.0, therapyEvent.glucoseUnit.toString).takeIf { therapyEvent.glucose != null }
)
aapsLogger.debug(LTag.DATABASE, "Inserted TherapyEvent $therapyEvent")
}
result.invalidated.forEach { therapyEvent ->
uel.log(Action.CAREPORTAL_REMOVED, Sources.NSClient,
therapyEvent.note ?: "",
- ValueWithUnit.Timestamp(therapyEvent.timestamp),
- ValueWithUnit.TherapyEventType(therapyEvent.type),
- ValueWithUnit.fromGlucoseUnit(therapyEvent.glucose ?:0.0, therapyEvent.glucoseUnit.toString).takeIf { therapyEvent.glucose != null }
+ ValueWithUnit.Timestamp(therapyEvent.timestamp),
+ ValueWithUnit.TherapyEventType(therapyEvent.type),
+ ValueWithUnit.fromGlucoseUnit(therapyEvent.glucose ?: 0.0, therapyEvent.glucoseUnit.toString).takeIf { therapyEvent.glucose != null }
)
aapsLogger.debug(LTag.DATABASE, "Invalidated TherapyEvent $therapyEvent")
}
@@ -309,8 +323,8 @@ class NSClientAddUpdateWorker(
}
} ?: aapsLogger.error("Error parsing TherapyEvent json $json")
}
- eventType == TherapyEvent.Type.COMBO_BOLUS.text ->
- if (config.NSCLIENT) {
+ eventType == TherapyEvent.Type.COMBO_BOLUS.text ->
+ if (buildHelper.isEngineeringMode() && sp.getBoolean(R.string.key_ns_receive_tbr_eb, false) || config.NSCLIENT) {
extendedBolusFromJson(json)?.let { extendedBolus ->
repository.runTransactionForResult(SyncNsExtendedBolusTransaction(extendedBolus))
.doOnError {
@@ -320,7 +334,8 @@ class NSClientAddUpdateWorker(
.blockingGet()
.also { result ->
result.inserted.forEach {
- uel.log(Action.EXTENDED_BOLUS, Sources.NSClient,
+ uel.log(
+ Action.EXTENDED_BOLUS, Sources.NSClient,
ValueWithUnit.Timestamp(it.timestamp),
ValueWithUnit.Insulin(it.amount),
ValueWithUnit.UnitPerHour(it.rate),
@@ -329,7 +344,8 @@ class NSClientAddUpdateWorker(
aapsLogger.debug(LTag.DATABASE, "Inserted ExtendedBolus $it")
}
result.invalidated.forEach {
- uel.log(Action.EXTENDED_BOLUS_REMOVED, Sources.NSClient,
+ uel.log(
+ Action.EXTENDED_BOLUS_REMOVED, Sources.NSClient,
ValueWithUnit.Timestamp(it.timestamp),
ValueWithUnit.Insulin(it.amount),
ValueWithUnit.UnitPerHour(it.rate),
@@ -338,7 +354,8 @@ class NSClientAddUpdateWorker(
aapsLogger.debug(LTag.DATABASE, "Invalidated ExtendedBolus $it")
}
result.ended.forEach {
- uel.log(Action.CANCEL_EXTENDED_BOLUS, Sources.NSClient,
+ uel.log(
+ Action.CANCEL_EXTENDED_BOLUS, Sources.NSClient,
ValueWithUnit.Timestamp(it.timestamp),
ValueWithUnit.Insulin(it.amount),
ValueWithUnit.UnitPerHour(it.rate),
@@ -355,8 +372,8 @@ class NSClientAddUpdateWorker(
}
} ?: aapsLogger.error("Error parsing ExtendedBolus json $json")
}
- eventType == TherapyEvent.Type.TEMPORARY_BASAL.text ->
- if (config.NSCLIENT) {
+ eventType == TherapyEvent.Type.TEMPORARY_BASAL.text ->
+ if (buildHelper.isEngineeringMode() && sp.getBoolean(R.string.key_ns_receive_tbr_eb, false) || config.NSCLIENT) {
temporaryBasalFromJson(json)?.let { temporaryBasal ->
repository.runTransactionForResult(SyncNsTemporaryBasalTransaction(temporaryBasal))
.doOnError {
@@ -366,7 +383,8 @@ class NSClientAddUpdateWorker(
.blockingGet()
.also { result ->
result.inserted.forEach {
- uel.log(Action.TEMP_BASAL, Sources.NSClient,
+ uel.log(
+ Action.TEMP_BASAL, Sources.NSClient,
ValueWithUnit.Timestamp(it.timestamp),
if (it.isAbsolute) ValueWithUnit.UnitPerHour(it.rate) else ValueWithUnit.Percent(it.rate.toInt()),
ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(it.duration).toInt())
@@ -374,7 +392,8 @@ class NSClientAddUpdateWorker(
aapsLogger.debug(LTag.DATABASE, "Inserted TemporaryBasal $it")
}
result.invalidated.forEach {
- uel.log(Action.TEMP_BASAL_REMOVED, Sources.NSClient,
+ uel.log(
+ Action.TEMP_BASAL_REMOVED, Sources.NSClient,
ValueWithUnit.Timestamp(it.timestamp),
if (it.isAbsolute) ValueWithUnit.UnitPerHour(it.rate) else ValueWithUnit.Percent(it.rate.toInt()),
ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(it.duration).toInt())
@@ -382,7 +401,8 @@ class NSClientAddUpdateWorker(
aapsLogger.debug(LTag.DATABASE, "Invalidated TemporaryBasal $it")
}
result.ended.forEach {
- uel.log(Action.CANCEL_TEMP_BASAL, Sources.NSClient,
+ uel.log(
+ Action.CANCEL_TEMP_BASAL, Sources.NSClient,
ValueWithUnit.Timestamp(it.timestamp),
if (it.isAbsolute) ValueWithUnit.UnitPerHour(it.rate) else ValueWithUnit.Percent(it.rate.toInt()),
ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(it.duration).toInt())
@@ -398,7 +418,7 @@ class NSClientAddUpdateWorker(
}
} ?: aapsLogger.error("Error parsing TemporaryBasal json $json")
}
- eventType == TherapyEvent.Type.PROFILE_SWITCH.text ->
+ eventType == TherapyEvent.Type.PROFILE_SWITCH.text ->
if (sp.getBoolean(R.string.key_ns_receive_profile_switch, false) || config.NSCLIENT) {
profileSwitchFromJson(json, dateUtil, activePlugin)?.let { profileSwitch ->
repository.runTransactionForResult(SyncNsProfileSwitchTransaction(profileSwitch))
@@ -409,13 +429,17 @@ class NSClientAddUpdateWorker(
.blockingGet()
.also { result ->
result.inserted.forEach {
- uel.log(Action.PROFILE_SWITCH, Sources.NSClient,
- ValueWithUnit.Timestamp(it.timestamp))
+ uel.log(
+ Action.PROFILE_SWITCH, Sources.NSClient,
+ ValueWithUnit.Timestamp(it.timestamp)
+ )
aapsLogger.debug(LTag.DATABASE, "Inserted ProfileSwitch $it")
}
result.invalidated.forEach {
- uel.log(Action.PROFILE_SWITCH_REMOVED, Sources.NSClient,
- ValueWithUnit.Timestamp(it.timestamp))
+ uel.log(
+ Action.PROFILE_SWITCH_REMOVED, Sources.NSClient,
+ ValueWithUnit.Timestamp(it.timestamp)
+ )
aapsLogger.debug(LTag.DATABASE, "Invalidated ProfileSwitch $it")
}
result.updatedNsId.forEach {
@@ -424,7 +448,7 @@ class NSClientAddUpdateWorker(
}
} ?: aapsLogger.error("Error parsing ProfileSwitch json $json")
}
- eventType == TherapyEvent.Type.APS_OFFLINE.text ->
+ eventType == TherapyEvent.Type.APS_OFFLINE.text ->
if (sp.getBoolean(R.string.key_ns_receive_offline_event, false) && buildHelper.isEngineeringMode() || config.NSCLIENT) {
offlineEventFromJson(json)?.let { offlineEvent ->
repository.runTransactionForResult(SyncNsOfflineEventTransaction(offlineEvent))
@@ -435,21 +459,24 @@ class NSClientAddUpdateWorker(
.blockingGet()
.also { result ->
result.inserted.forEach { oe ->
- uel.log(Action.LOOP_CHANGE, Sources.NSClient,
+ uel.log(
+ Action.LOOP_CHANGE, Sources.NSClient,
ValueWithUnit.OfflineEventReason(oe.reason),
ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(oe.duration).toInt())
)
aapsLogger.debug(LTag.DATABASE, "Inserted OfflineEvent $oe")
}
result.invalidated.forEach { oe ->
- uel.log(Action.LOOP_REMOVED, Sources.NSClient,
+ uel.log(
+ Action.LOOP_REMOVED, Sources.NSClient,
ValueWithUnit.OfflineEventReason(oe.reason),
ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(oe.duration).toInt())
)
aapsLogger.debug(LTag.DATABASE, "Invalidated OfflineEvent $oe")
}
result.ended.forEach { oe ->
- uel.log(Action.LOOP_CHANGE, Sources.NSClient,
+ uel.log(
+ Action.LOOP_CHANGE, Sources.NSClient,
ValueWithUnit.OfflineEventReason(oe.reason),
ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(oe.duration).toInt())
)
@@ -472,7 +499,8 @@ class NSClientAddUpdateWorker(
val enteredBy = JsonHelper.safeGetString(json, "enteredBy", "")
val notes = JsonHelper.safeGetString(json, "notes", "")
if (date > now - 15 * 60 * 1000L && notes.isNotEmpty()
- && enteredBy != sp.getString("careportal_enteredby", "AndroidAPS")) {
+ && enteredBy != sp.getString("careportal_enteredby", "AndroidAPS")
+ ) {
val defaultVal = config.NSCLIENT
if (sp.getBoolean(R.string.key_ns_announcements, defaultVal)) {
val announcement = Notification(Notification.NS_ANNOUNCEMENT, notes, Notification.ANNOUNCEMENT, 60)
@@ -482,6 +510,7 @@ class NSClientAddUpdateWorker(
}
}
nsClientPlugin.updateLatestDateReceivedIfNewer(latestDateInReceivedData)
+ xDripBroadcast.sendTreatments(treatments)
return ret
}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientFragment.kt
index 13b5469d01..43061ed25f 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientFragment.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientFragment.kt
@@ -1,10 +1,7 @@
package info.nightscout.androidaps.plugins.general.nsclient
-import android.graphics.Paint
import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
+import android.view.*
import android.widget.ScrollView
import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.R
@@ -12,17 +9,17 @@ import info.nightscout.androidaps.database.entities.UserEntry.Action
import info.nightscout.androidaps.database.entities.UserEntry.Sources
import info.nightscout.androidaps.databinding.NsClientFragmentBinding
import info.nightscout.androidaps.interfaces.DataSyncSelector
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientUpdateGUI
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
-import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.shared.sharedPreferences.SP
-import io.reactivex.disposables.CompositeDisposable
-import io.reactivex.rxkotlin.plusAssign
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.kotlin.plusAssign
import javax.inject.Inject
class NSClientFragment : DaggerFragment() {
@@ -36,6 +33,14 @@ class NSClientFragment : DaggerFragment() {
@Inject lateinit var dataSyncSelector: DataSyncSelector
@Inject lateinit var uel: UserEntryLogger
+ companion object {
+
+ const val ID_MENU_CLEAR_LOG = 6
+ const val ID_MENU_RESTART = 7
+ const val ID_MENU_SEND_NOW = 8
+ const val ID_MENU_FULL_SYNC = 9
+ }
+
private val disposable = CompositeDisposable()
private var _binding: NsClientFragmentBinding? = null
@@ -45,7 +50,10 @@ class NSClientFragment : DaggerFragment() {
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
- NsClientFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root
+ NsClientFragmentBinding.inflate(inflater, container, false).also {
+ _binding = it
+ setHasOptionsMenu(true)
+ }.root
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@@ -63,23 +71,49 @@ class NSClientFragment : DaggerFragment() {
nsClientPlugin.pause(isChecked)
updateGui()
}
- binding.clearLog.setOnClickListener { nsClientPlugin.clearLog() }
- binding.clearLog.paintFlags = binding.clearLog.paintFlags or Paint.UNDERLINE_TEXT_FLAG
- binding.restart.setOnClickListener { rxBus.send(EventNSClientRestart()) }
- binding.restart.paintFlags = binding.restart.paintFlags or Paint.UNDERLINE_TEXT_FLAG
- binding.deliverNow.setOnClickListener { nsClientPlugin.resend("GUI") }
- binding.deliverNow.paintFlags = binding.deliverNow.paintFlags or Paint.UNDERLINE_TEXT_FLAG
- binding.fullSync.setOnClickListener {
- context?.let { context ->
- OKDialog.showConfirmation(context, rh.gs(R.string.nsclientinternal),
- rh.gs(R.string.full_sync_comment), Runnable {
- dataSyncSelector.resetToNextFullSync()
- })
- }
- }
- binding.fullSync.paintFlags = binding.fullSync.paintFlags or Paint.UNDERLINE_TEXT_FLAG
}
+ override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+ super.onCreateOptionsMenu(menu, inflater)
+ if (isResumed) {
+ menu.add(Menu.FIRST, ID_MENU_CLEAR_LOG, 0, rh.gs(R.string.clearlog)).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
+ menu.add(Menu.FIRST, ID_MENU_RESTART, 0, rh.gs(R.string.restart)).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
+ menu.add(Menu.FIRST, ID_MENU_SEND_NOW, 0, rh.gs(R.string.deliver_now)).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
+ menu.add(Menu.FIRST, ID_MENU_FULL_SYNC, 0, rh.gs(R.string.full_sync)).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
+ menu.setGroupDividerEnabled(true)
+ }
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean =
+ when (item.itemId) {
+ ID_MENU_CLEAR_LOG -> {
+ nsClientPlugin.clearLog()
+ true
+ }
+
+ ID_MENU_RESTART -> {
+ rxBus.send(EventNSClientRestart())
+ true
+ }
+
+ ID_MENU_SEND_NOW -> {
+ nsClientPlugin.resend("GUI")
+ true
+ }
+
+ ID_MENU_FULL_SYNC -> {
+ context?.let { context ->
+ OKDialog.showConfirmation(
+ context, rh.gs(R.string.nsclientinternal), rh.gs(R.string.full_sync_comment),
+ Runnable { dataSyncSelector.resetToNextFullSync() }
+ )
+ }
+ true
+ }
+
+ else -> false
+ }
+
@Synchronized override fun onResume() {
super.onResume()
disposable += rxBus
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientMbgWorker.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientMbgWorker.kt
index a4c6386c17..a64d04ab0d 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientMbgWorker.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientMbgWorker.kt
@@ -14,7 +14,7 @@ import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.plugins.general.nsclient.data.NSMbg
import info.nightscout.androidaps.receivers.DataWorker
-import info.nightscout.androidaps.utils.buildHelper.BuildHelper
+import info.nightscout.androidaps.interfaces.BuildHelper
import info.nightscout.shared.sharedPreferences.SP
import javax.inject.Inject
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientPlugin.kt
index 540ce3e628..2a8954c583 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientPlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientPlugin.kt
@@ -21,8 +21,7 @@ import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.interfaces.PluginBase
import info.nightscout.androidaps.interfaces.PluginDescription
import info.nightscout.androidaps.interfaces.PluginType
-import info.nightscout.shared.logging.AAPSLogger
-import info.nightscout.shared.logging.LTag
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.nsclient.data.AlarmAck
import info.nightscout.androidaps.plugins.general.nsclient.data.NSAlarm
@@ -34,12 +33,13 @@ import info.nightscout.androidaps.plugins.general.nsclient.services.NSClientServ
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.HtmlHelper.fromHtml
import info.nightscout.androidaps.utils.ToastUtils
-import info.nightscout.androidaps.utils.buildHelper.BuildHelper
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.BuildHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
-import io.reactivex.disposables.CompositeDisposable
-import java.util.*
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.kotlin.plusAssign
import javax.inject.Inject
import javax.inject.Singleton
@@ -80,62 +80,49 @@ class NSClientPlugin @Inject constructor(
var nsClientService: NSClientService? = null
val isAllowed: Boolean
get() = nsClientReceiverDelegate.allowed
+ val blockingReason: String
+ get() = nsClientReceiverDelegate.blockingReason
override fun onStart() {
paused = sp.getBoolean(R.string.key_nsclientinternal_paused, false)
autoscroll = sp.getBoolean(R.string.key_nsclientinternal_autoscroll, true)
- val intent = Intent(context, NSClientService::class.java)
- context.bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
+ context.bindService(Intent(context, NSClientService::class.java), mConnection, Context.BIND_AUTO_CREATE)
super.onStart()
nsClientReceiverDelegate.grabReceiversState()
- disposable.add(
- rxBus
- .toObservable(EventNSClientStatus::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({ event: EventNSClientStatus ->
- status = event.getStatus(rh)
- rxBus.send(EventNSClientUpdateGUI())
- }, fabricPrivacy::logException)
- )
- disposable.add(
- rxBus
- .toObservable(EventNetworkChange::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({ ev -> nsClientReceiverDelegate.onStatusEvent(ev) }, fabricPrivacy::logException)
- )
- disposable.add(
- rxBus
- .toObservable(EventPreferenceChange::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({ ev -> nsClientReceiverDelegate.onStatusEvent(ev) }, fabricPrivacy::logException)
- )
- disposable.add(
- rxBus
- .toObservable(EventAppExit::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({ if (nsClientService != null) context.unbindService(mConnection) }, fabricPrivacy::logException)
- )
- disposable.add(
- rxBus
- .toObservable(EventNSClientNewLog::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({ event: EventNSClientNewLog ->
- addToLog(event)
- aapsLogger.debug(LTag.NSCLIENT, event.action + " " + event.logText)
- }, fabricPrivacy::logException)
- )
- disposable.add(
- rxBus
- .toObservable(EventChargingState::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({ ev -> nsClientReceiverDelegate.onStatusEvent(ev) }, fabricPrivacy::logException)
- )
- disposable.add(
- rxBus
- .toObservable(EventNSClientResend::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({ event -> resend(event.reason) }, fabricPrivacy::logException)
- )
+ disposable += rxBus
+ .toObservable(EventNSClientStatus::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({ event: EventNSClientStatus ->
+ status = event.getStatus(rh)
+ rxBus.send(EventNSClientUpdateGUI())
+ }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventNetworkChange::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({ ev -> nsClientReceiverDelegate.onStatusEvent(ev) }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventPreferenceChange::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({ ev -> nsClientReceiverDelegate.onStatusEvent(ev) }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventAppExit::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({ if (nsClientService != null) context.unbindService(mConnection) }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventNSClientNewLog::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({ event: EventNSClientNewLog ->
+ addToLog(event)
+ aapsLogger.debug(LTag.NSCLIENT, event.action + " " + event.logText)
+ }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventChargingState::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({ ev -> nsClientReceiverDelegate.onStatusEvent(ev) }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventNSClientResend::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({ event -> resend(event.reason) }, fabricPrivacy::logException)
}
override fun onStop() {
@@ -159,6 +146,7 @@ class NSClientPlugin @Inject constructor(
// preferenceFragment.findPreference(rh.gs(R.string.key_ns_receive_carbs))?.isVisible = buildHelper.isEngineeringMode()
// preferenceFragment.findPreference(rh.gs(R.string.key_ns_receive_temp_target))?.isVisible = buildHelper.isEngineeringMode()
}
+ preferenceFragment.findPreference(rh.gs(R.string.key_ns_receive_tbr_eb))?.isVisible = buildHelper.isEngineeringMode()
}
private val mConnection: ServiceConnection = object : ServiceConnection {
@@ -169,8 +157,7 @@ class NSClientPlugin @Inject constructor(
override fun onServiceConnected(name: ComponentName, service: IBinder) {
aapsLogger.debug(LTag.NSCLIENT, "Service is connected")
- val mLocalBinder = service as NSClientService.LocalBinder
- @Suppress("UNNECESSARY_SAFE_CALL")
+ val mLocalBinder = service as NSClientService.LocalBinder?
nsClientService = mLocalBinder?.serviceInstance // is null when running in roboelectric
}
}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NsClientReceiverDelegate.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NsClientReceiverDelegate.kt
index 9415489c78..ad69554a51 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NsClientReceiverDelegate.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NsClientReceiverDelegate.kt
@@ -6,7 +6,7 @@ import info.nightscout.androidaps.events.EventNetworkChange
import info.nightscout.androidaps.events.EventPreferenceChange
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.receivers.ReceiverStatusStore
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import javax.inject.Inject
import javax.inject.Singleton
@@ -22,19 +22,26 @@ class NsClientReceiverDelegate @Inject constructor(
private var allowedChargingState = true
private var allowedNetworkState = true
var allowed = true
+ var blockingReason = ""
fun grabReceiversState() {
receiverStatusStore.updateNetworkStatus()
}
fun onStatusEvent(ev: EventPreferenceChange) {
- if (ev.isChanged(rh, R.string.key_ns_wifionly) ||
- ev.isChanged(rh, R.string.key_ns_wifi_ssids) ||
- ev.isChanged(rh, R.string.key_ns_allowroaming)) {
- receiverStatusStore.updateNetworkStatus()
- onStatusEvent(receiverStatusStore.lastNetworkEvent)
- } else if (ev.isChanged(rh, R.string.key_ns_chargingonly)) {
- receiverStatusStore.broadcastChargingState()
+ when {
+ ev.isChanged(rh, R.string.key_ns_wifi) ||
+ ev.isChanged(rh, R.string.key_ns_cellular) ||
+ ev.isChanged(rh, R.string.key_ns_wifi_ssids) ||
+ ev.isChanged(rh, R.string.key_ns_allow_roaming) -> {
+ receiverStatusStore.updateNetworkStatus()
+ receiverStatusStore.lastNetworkEvent?.let { onStatusEvent(it) }
+ }
+
+ ev.isChanged(rh, R.string.key_ns_charging) ||
+ ev.isChanged(rh, R.string.key_ns_battery) -> {
+ receiverStatusStore.broadcastChargingState()
+ }
}
}
@@ -42,14 +49,16 @@ class NsClientReceiverDelegate @Inject constructor(
val newChargingState = calculateStatus(ev)
if (newChargingState != allowedChargingState) {
allowedChargingState = newChargingState
+ blockingReason = rh.gs(R.string.blocked_by_charging)
processStateChange()
}
}
- fun onStatusEvent(ev: EventNetworkChange?) {
+ fun onStatusEvent(ev: EventNetworkChange) {
val newNetworkState = calculateStatus(ev)
if (newNetworkState != allowedNetworkState) {
allowedNetworkState = newNetworkState
+ blockingReason = rh.gs(R.string.blocked_by_connectivity)
processStateChange()
}
}
@@ -62,30 +71,13 @@ class NsClientReceiverDelegate @Inject constructor(
}
}
- fun calculateStatus(ev: EventChargingState): Boolean {
- val chargingOnly = sp.getBoolean(R.string.key_ns_chargingonly, false)
- var newAllowedState = true
- if (!ev.isCharging && chargingOnly) {
- newAllowedState = false
- }
- return newAllowedState
- }
+ fun calculateStatus(ev: EventChargingState): Boolean =
+ !ev.isCharging && sp.getBoolean(R.string.key_ns_battery, true) ||
+ ev.isCharging && sp.getBoolean(R.string.key_ns_charging, true)
- fun calculateStatus(ev: EventNetworkChange?): Boolean {
- val wifiOnly = sp.getBoolean(R.string.key_ns_wifionly, false)
- val allowedSsidString = sp.getString(R.string.key_ns_wifi_ssids, "")
- val allowedSSIDs: List = if (allowedSsidString.isEmpty()) List(0) { "" } else allowedSsidString.split(";")
- val allowRoaming = sp.getBoolean(R.string.key_ns_allowroaming, true)
- var newAllowedState = true
- if (ev?.wifiConnected == true) {
- if (allowedSSIDs.isNotEmpty() && !allowedSSIDs.contains(ev.ssid)) {
- newAllowedState = false
- }
- } else {
- if (!allowRoaming && ev?.roaming == true || wifiOnly) {
- newAllowedState = false
- }
- }
- return newAllowedState
- }
+ fun calculateStatus(ev: EventNetworkChange): Boolean =
+ ev.mobileConnected && sp.getBoolean(R.string.key_ns_cellular, true) && !ev.roaming ||
+ ev.mobileConnected && sp.getBoolean(R.string.key_ns_cellular, true) && ev.roaming && sp.getBoolean(R.string.key_ns_allow_roaming, true) ||
+ ev.wifiConnected && sp.getBoolean(R.string.key_ns_wifi, true) && sp.getString(R.string.key_ns_wifi_ssids, "").isEmpty() ||
+ ev.wifiConnected && sp.getBoolean(R.string.key_ns_wifi, true) && sp.getString(R.string.key_ns_wifi_ssids, "").split(";").contains(ev.ssid)
}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/data/NSDeviceStatus.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/data/NSDeviceStatus.kt
index b7435e3916..88bbbce04c 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/data/NSDeviceStatus.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/data/NSDeviceStatus.kt
@@ -4,8 +4,6 @@ import android.text.Spanned
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R
import info.nightscout.androidaps.interfaces.Config
-import info.nightscout.shared.logging.AAPSLogger
-import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.plugins.aps.loop.APSResult
import info.nightscout.androidaps.plugins.configBuilder.RunningConfiguration
import info.nightscout.androidaps.utils.DateUtil
@@ -13,7 +11,9 @@ import info.nightscout.androidaps.utils.HtmlHelper.fromHtml
import info.nightscout.androidaps.utils.JsonHelper
import info.nightscout.androidaps.utils.Round
import info.nightscout.androidaps.utils.T
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONArray
import org.json.JSONException
@@ -88,7 +88,7 @@ class NSDeviceStatus @Inject constructor(
fun handleNewData(deviceStatuses: JSONArray) {
aapsLogger.debug(LTag.NSCLIENT, "Got NS deviceStatus: \$deviceStatuses")
try {
- for (i in deviceStatuses.length() -1 downTo 0) {
+ for (i in deviceStatuses.length() - 1 downTo 0) {
val devicestatusJson = deviceStatuses.getJSONObject(i)
if (devicestatusJson != null) {
setData(devicestatusJson)
@@ -157,7 +157,7 @@ class NSDeviceStatus @Inject constructor(
//String[] ALL_STATUS_FIELDS = {"reservoir", "battery", "clock", "status", "device"};
val string = StringBuilder()
- .append("")
+ .append("")
.append(rh.gs(R.string.pump))
.append(": ")
@@ -165,13 +165,13 @@ class NSDeviceStatus @Inject constructor(
val level = when {
pumpData.clock + nsSettingsStatus.extendedPumpSettings("urgentClock") * 60 * 1000L < dateUtil.now() -> Levels.URGENT
pumpData.reservoir < nsSettingsStatus.extendedPumpSettings("urgentRes") -> Levels.URGENT
- pumpData.isPercent && pumpData.percent < nsSettingsStatus.extendedPumpSettings("urgentBattP") -> Levels.URGENT
+ pumpData.isPercent && pumpData.percent < nsSettingsStatus.extendedPumpSettings("urgentBattP") -> Levels.URGENT
!pumpData.isPercent && pumpData.voltage < nsSettingsStatus.extendedPumpSettings("urgentBattV") -> Levels.URGENT
pumpData.clock + nsSettingsStatus.extendedPumpSettings("warnClock") * 60 * 1000L < dateUtil.now() -> Levels.WARN
pumpData.reservoir < nsSettingsStatus.extendedPumpSettings("warnRes") -> Levels.WARN
- pumpData.isPercent && pumpData.percent < nsSettingsStatus.extendedPumpSettings("warnBattP") -> Levels.WARN
- !pumpData.isPercent && pumpData.voltage < nsSettingsStatus.extendedPumpSettings("warnBattV") -> Levels.WARN
- else -> Levels.INFO
+ pumpData.isPercent && pumpData.percent < nsSettingsStatus.extendedPumpSettings("warnBattP") -> Levels.WARN
+ !pumpData.isPercent && pumpData.voltage < nsSettingsStatus.extendedPumpSettings("warnBattV") -> Levels.WARN
+ else -> Levels.INFO
}
string.append("")
val fields = nsSettingsStatus.pumpExtendedSettingsFields()
@@ -248,7 +248,7 @@ class NSDeviceStatus @Inject constructor(
val openApsStatus: Spanned
get() {
val string = StringBuilder()
- .append("")
+ .append("")
.append(rh.gs(R.string.openaps_short))
.append(": ")
@@ -256,7 +256,7 @@ class NSDeviceStatus @Inject constructor(
val level = when {
deviceStatusData.openAPSData.clockSuggested + T.mins(sp.getLong(R.string.key_nsalarm_urgent_staledatavalue, 31)).msecs() < dateUtil.now() -> Levels.URGENT
deviceStatusData.openAPSData.clockSuggested + T.mins(sp.getLong(R.string.key_nsalarm_staledatavalue, 16)).msecs() < dateUtil.now() -> Levels.WARN
- else -> Levels.INFO
+ else -> Levels.INFO
}
string.append("")
if (deviceStatusData.openAPSData.clockSuggested != 0L) string.append(dateUtil.minAgo(rh, deviceStatusData.openAPSData.clockSuggested)).append(" ")
@@ -268,8 +268,10 @@ class NSDeviceStatus @Inject constructor(
get() {
val string = StringBuilder()
try {
- if (deviceStatusData.openAPSData.enacted != null && deviceStatusData.openAPSData.clockEnacted != deviceStatusData.openAPSData.clockSuggested) string.append("").append(dateUtil.minAgo(rh, deviceStatusData.openAPSData.clockEnacted)).append(" ").append(deviceStatusData.openAPSData.enacted!!.getString("reason")).append("
")
- if (deviceStatusData.openAPSData.suggested != null) string.append("").append(dateUtil.minAgo(rh, deviceStatusData.openAPSData.clockSuggested)).append(" ").append(deviceStatusData.openAPSData.suggested!!.getString("reason")).append("
")
+ if (deviceStatusData.openAPSData.enacted != null && deviceStatusData.openAPSData.clockEnacted != deviceStatusData.openAPSData.clockSuggested) string.append("")
+ .append(dateUtil.minAgo(rh, deviceStatusData.openAPSData.clockEnacted)).append(" ").append(deviceStatusData.openAPSData.enacted!!.getString("reason")).append("
")
+ if (deviceStatusData.openAPSData.suggested != null) string.append("").append(dateUtil.minAgo(rh, deviceStatusData.openAPSData.clockSuggested)).append(" ")
+ .append(deviceStatusData.openAPSData.suggested!!.getString("reason")).append("
")
return fromHtml(string.toString())
} catch (e: JSONException) {
aapsLogger.error("Unhandled exception", e)
@@ -321,7 +323,7 @@ class NSDeviceStatus @Inject constructor(
val uploaderStatusSpanned: Spanned
get() {
val string = StringBuilder()
- string.append("")
+ string.append("")
string.append(rh.gs(R.string.uploader_short))
string.append(": ")
val iterator: Iterator<*> = deviceStatusData.uploaderMap.entries.iterator()
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/data/NSSettingsStatus.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/data/NSSettingsStatus.kt
index 1aa60b800e..455466bbac 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/data/NSSettingsStatus.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/data/NSSettingsStatus.kt
@@ -16,7 +16,7 @@ import info.nightscout.androidaps.plugins.general.overview.notifications.Notific
import info.nightscout.androidaps.utils.DefaultValueHelper
import info.nightscout.androidaps.utils.JsonHelper
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONException
import org.json.JSONObject
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/events/EventNSClientStatus.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/events/EventNSClientStatus.kt
index c4291de252..abee8602e4 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/events/EventNSClientStatus.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/events/EventNSClientStatus.kt
@@ -1,7 +1,7 @@
package info.nightscout.androidaps.plugins.general.nsclient.events
import info.nightscout.androidaps.events.EventStatus
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
class EventNSClientStatus(var text: String) : EventStatus() {
override fun getStatus(rh: ResourceHelper): String = text
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/services/NSClientService.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/services/NSClientService.kt
index 040fe82453..6b12dff07f 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/services/NSClientService.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/services/NSClientService.kt
@@ -14,11 +14,17 @@ import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.events.EventAppExit
import info.nightscout.androidaps.events.EventConfigBuilderChange
import info.nightscout.androidaps.events.EventPreferenceChange
+import info.nightscout.androidaps.interfaces.BuildHelper
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.interfaces.DataSyncSelector
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.food.FoodPlugin.FoodWorker
-import info.nightscout.androidaps.plugins.general.nsclient.*
+import info.nightscout.androidaps.plugins.general.nsclient.NSClientAddAckWorker
+import info.nightscout.androidaps.plugins.general.nsclient.NSClientAddUpdateWorker
+import info.nightscout.androidaps.plugins.general.nsclient.NSClientMbgWorker
+import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin
+import info.nightscout.androidaps.plugins.general.nsclient.NSClientUpdateRemoveAckWorker
import info.nightscout.androidaps.plugins.general.nsclient.acks.NSAddAck
import info.nightscout.androidaps.plugins.general.nsclient.acks.NSAuthAck
import info.nightscout.androidaps.plugins.general.nsclient.acks.NSUpdateAck
@@ -42,14 +48,12 @@ import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.JsonHelper.safeGetString
import info.nightscout.androidaps.utils.JsonHelper.safeGetStringAllowNull
import info.nightscout.androidaps.utils.T.Companion.mins
-import info.nightscout.androidaps.utils.XDripBroadcast
-import info.nightscout.androidaps.utils.buildHelper.BuildHelper
-import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
-import io.reactivex.disposables.CompositeDisposable
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.kotlin.plusAssign
import io.socket.client.IO
import io.socket.client.Socket
import io.socket.emitter.Emitter
@@ -78,7 +82,6 @@ class NSClientService : DaggerService() {
@Inject lateinit var dataWorker: DataWorker
@Inject lateinit var dataSyncSelector: DataSyncSelector
@Inject lateinit var repository: AppRepository
- @Inject lateinit var xDripBroadcast: XDripBroadcast
companion object {
@@ -114,70 +117,56 @@ class NSClientService : DaggerService() {
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "AndroidAPS:NSClientService")
wakeLock?.acquire()
initialize()
- disposable.add(
- rxBus
- .toObservable(EventConfigBuilderChange::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({
- if (nsEnabled != nsClientPlugin.isEnabled()) {
- latestDateInReceivedData = 0
- destroy()
- initialize()
- }
- }, fabricPrivacy::logException)
- )
- disposable.add(
- rxBus
- .toObservable(EventPreferenceChange::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({ event: EventPreferenceChange ->
- if (event.isChanged(rh, R.string.key_nsclientinternal_url) ||
- event.isChanged(rh, R.string.key_nsclientinternal_api_secret) ||
- event.isChanged(rh, R.string.key_nsclientinternal_paused)
- ) {
- latestDateInReceivedData = 0
- destroy()
- initialize()
- }
- }, fabricPrivacy::logException)
- )
- disposable.add(
- rxBus
- .toObservable(EventAppExit::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({
- aapsLogger.debug(LTag.NSCLIENT, "EventAppExit received")
- destroy()
- stopSelf()
- }, fabricPrivacy::logException)
- )
- disposable.add(
- rxBus
- .toObservable(EventNSClientRestart::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({
+ disposable += rxBus
+ .toObservable(EventConfigBuilderChange::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({
+ if (nsEnabled != nsClientPlugin.isEnabled()) {
latestDateInReceivedData = 0
- restart()
- }, fabricPrivacy::logException)
- )
- disposable.add(
- rxBus
- .toObservable(NSAuthAck::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({ ack -> processAuthAck(ack) }, fabricPrivacy::logException)
- )
- disposable.add(
- rxBus
- .toObservable(NSUpdateAck::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({ ack -> processUpdateAck(ack) }, fabricPrivacy::logException)
- )
- disposable.add(
- rxBus
- .toObservable(NSAddAck::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({ ack -> processAddAck(ack) }, fabricPrivacy::logException)
- )
+ destroy()
+ initialize()
+ }
+ }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventPreferenceChange::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({ event: EventPreferenceChange ->
+ if (event.isChanged(rh, R.string.key_nsclientinternal_url) ||
+ event.isChanged(rh, R.string.key_nsclientinternal_api_secret) ||
+ event.isChanged(rh, R.string.key_nsclientinternal_paused)
+ ) {
+ latestDateInReceivedData = 0
+ destroy()
+ initialize()
+ }
+ }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventAppExit::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({
+ aapsLogger.debug(LTag.NSCLIENT, "EventAppExit received")
+ destroy()
+ stopSelf()
+ }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventNSClientRestart::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({
+ latestDateInReceivedData = 0
+ restart()
+ }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(NSAuthAck::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({ ack -> processAuthAck(ack) }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(NSUpdateAck::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({ ack -> processUpdateAck(ack) }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(NSAddAck::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({ ack -> processAddAck(ack) }, fabricPrivacy::logException)
}
override fun onDestroy() {
@@ -190,7 +179,7 @@ class NSClientService : DaggerService() {
lastAckTime = dateUtil.now()
dataWorker.enqueue(
OneTimeWorkRequest.Builder(NSClientAddAckWorker::class.java)
- .setInputData(dataWorker.storeInputData(ack, null))
+ .setInputData(dataWorker.storeInputData(ack))
.build()
)
}
@@ -199,7 +188,7 @@ class NSClientService : DaggerService() {
lastAckTime = dateUtil.now()
dataWorker.enqueue(
OneTimeWorkRequest.Builder(NSClientUpdateRemoveAckWorker::class.java)
- .setInputData(dataWorker.storeInputData(ack, null))
+ .setInputData(dataWorker.storeInputData(ack))
.build()
)
}
@@ -234,13 +223,9 @@ class NSClientService : DaggerService() {
get() = this@NSClientService
}
- override fun onBind(intent: Intent): IBinder {
- return binder
- }
+ override fun onBind(intent: Intent): IBinder = binder
- override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
- return START_STICKY
- }
+ override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int = START_STICKY
fun initialize() {
dataCounter = 0
@@ -249,8 +234,8 @@ class NSClientService : DaggerService() {
if (nsAPISecret != "") nsApiHashCode = Hashing.sha1().hashString(nsAPISecret, Charsets.UTF_8).toString()
rxBus.send(EventNSClientStatus("Initializing"))
if (!nsClientPlugin.isAllowed) {
- rxBus.send(EventNSClientNewLog("NSCLIENT", "not allowed"))
- rxBus.send(EventNSClientStatus("Not allowed"))
+ rxBus.send(EventNSClientNewLog("NSCLIENT", nsClientPlugin.blockingReason))
+ rxBus.send(EventNSClientStatus(nsClientPlugin.blockingReason))
} else if (nsClientPlugin.paused) {
rxBus.send(EventNSClientNewLog("NSCLIENT", "paused"))
rxBus.send(EventNSClientStatus("Paused"))
@@ -483,10 +468,9 @@ class NSClientService : DaggerService() {
rxBus.send(EventNSClientNewLog("PROFILE", "profile received"))
dataWorker.enqueue(
OneTimeWorkRequest.Builder(LocalProfilePlugin.NSProfileWorker::class.java)
- .setInputData(dataWorker.storeInputData(profileStoreJson, null))
+ .setInputData(dataWorker.storeInputData(profileStoreJson))
.build()
)
- xDripBroadcast.sendProfile(profileStoreJson)
}
}
if (data.has("treatments")) {
@@ -502,10 +486,9 @@ class NSClientService : DaggerService() {
if (addedOrUpdatedTreatments.length() > 0) {
dataWorker.enqueue(
OneTimeWorkRequest.Builder(NSClientAddUpdateWorker::class.java)
- .setInputData(dataWorker.storeInputData(addedOrUpdatedTreatments, null))
+ .setInputData(dataWorker.storeInputData(addedOrUpdatedTreatments))
.build()
)
- xDripBroadcast.sendTreatments(addedOrUpdatedTreatments)
}
}
if (data.has("devicestatus")) {
@@ -520,7 +503,7 @@ class NSClientService : DaggerService() {
if (foods.length() > 0) rxBus.send(EventNSClientNewLog("DATA", "received " + foods.length() + " foods"))
dataWorker.enqueue(
OneTimeWorkRequest.Builder(FoodWorker::class.java)
- .setInputData(dataWorker.storeInputData(foods, null))
+ .setInputData(dataWorker.storeInputData(foods))
.build()
)
}
@@ -529,7 +512,7 @@ class NSClientService : DaggerService() {
if (mbgArray.length() > 0) rxBus.send(EventNSClientNewLog("DATA", "received " + mbgArray.length() + " mbgs"))
dataWorker.enqueue(
OneTimeWorkRequest.Builder(NSClientMbgWorker::class.java)
- .setInputData(dataWorker.storeInputData(mbgArray, null))
+ .setInputData(dataWorker.storeInputData(mbgArray))
.build()
)
}
@@ -546,10 +529,9 @@ class NSClientService : DaggerService() {
sp.putBoolean(R.string.key_ObjectivesbgIsAvailableInNS, true)
dataWorker.enqueue(
OneTimeWorkRequest.Builder(NSClientSourceWorker::class.java)
- .setInputData(dataWorker.storeInputData(sgvs, null))
+ .setInputData(dataWorker.storeInputData(sgvs))
.build()
)
- xDripBroadcast.sendSgvs(sgvs)
}
}
rxBus.send(EventNSClientNewLog("LAST", dateUtil.dateAndTimeString(latestDateInReceivedData)))
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewData.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewData.kt
index 8437c500fa..6f0b64bc6d 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewData.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewData.kt
@@ -1,44 +1,43 @@
package info.nightscout.androidaps.plugins.general.overview
-import android.graphics.DashPathEffect
-import android.graphics.Paint
+import android.content.Context
+import androidx.annotation.ColorInt
import com.jjoe64.graphview.series.BarGraphSeries
import com.jjoe64.graphview.series.DataPoint
import com.jjoe64.graphview.series.LineGraphSeries
-import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R
import info.nightscout.androidaps.data.IobTotal
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.ValueWrapper
-import info.nightscout.androidaps.database.entities.Bolus
import info.nightscout.androidaps.database.entities.GlucoseValue
import info.nightscout.androidaps.database.entities.TemporaryTarget
-import info.nightscout.androidaps.database.entities.TherapyEvent
-import info.nightscout.androidaps.extensions.*
-import info.nightscout.androidaps.interfaces.*
-import info.nightscout.shared.logging.AAPSLogger
-import info.nightscout.shared.logging.LTag
-import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults
-import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus
-import info.nightscout.androidaps.plugins.general.overview.graphExtensions.*
-import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult
+import info.nightscout.androidaps.extensions.convertedToPercent
+import info.nightscout.androidaps.extensions.isInProgress
+import info.nightscout.androidaps.extensions.toStringFull
+import info.nightscout.androidaps.extensions.toStringShort
+import info.nightscout.androidaps.extensions.valueToUnits
+import info.nightscout.androidaps.interfaces.ActivePlugin
+import info.nightscout.androidaps.interfaces.IobCobCalculator
+import info.nightscout.androidaps.interfaces.ProfileFunction
+import info.nightscout.androidaps.plugins.general.overview.graphExtensions.DataPointWithLabelInterface
+import info.nightscout.androidaps.plugins.general.overview.graphExtensions.FixedLineGraphSeries
+import info.nightscout.androidaps.plugins.general.overview.graphExtensions.PointsWithLabelGraphSeries
+import info.nightscout.androidaps.plugins.general.overview.graphExtensions.Scale
+import info.nightscout.androidaps.plugins.general.overview.graphExtensions.ScaledDataPoint
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.CobInfo
-import info.nightscout.androidaps.plugins.iob.iobCobCalculator.data.AutosensData
-import info.nightscout.androidaps.utils.*
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.utils.DateUtil
+import info.nightscout.androidaps.utils.DefaultValueHelper
+import info.nightscout.androidaps.utils.T
+import info.nightscout.androidaps.interfaces.ResourceHelper
+import info.nightscout.androidaps.utils.FabricPrivacy
+import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.sharedPreferences.SP
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
-import kotlin.collections.ArrayList
-import kotlin.math.abs
-import kotlin.math.ceil
-import kotlin.math.max
-import kotlin.math.min
@Singleton
class OverviewData @Inject constructor(
- private val injector: HasAndroidInjector,
private val aapsLogger: AAPSLogger,
private val rh: ResourceHelper,
private val dateUtil: DateUtil,
@@ -46,13 +45,8 @@ class OverviewData @Inject constructor(
private val activePlugin: ActivePlugin,
private val defaultValueHelper: DefaultValueHelper,
private val profileFunction: ProfileFunction,
- private val config: Config,
- private val loop: Loop,
- private val nsDeviceStatus: NSDeviceStatus,
private val repository: AppRepository,
- private val overviewMenus: OverviewMenus,
- private val iobCobCalculator: IobCobCalculator,
- private val translator: Translator
+ private val fabricPrivacy: FabricPrivacy
) {
var rangeToDisplay = 6 // for graph
@@ -62,14 +56,7 @@ class OverviewData @Inject constructor(
fun reset() {
pumpStatus = ""
- calcProgress = ""
- lastBg = null
- bolusIob = null
- basalIob = null
- cobInfo = null
- lastCarbsTime = 0L
- temporaryTarget = null
- lastAutosensData = null
+ calcProgressPct = 100
bgReadingsArray = ArrayList()
bucketedGraphSeries = PointsWithLabelGraphSeries()
bgReadingGraphSeries = PointsWithLabelGraphSeries()
@@ -92,6 +79,8 @@ class OverviewData @Inject constructor(
ratioSeries = LineGraphSeries()
dsMaxSeries = LineGraphSeries()
dsMinSeries = LineGraphSeries()
+ treatmentsSeries = PointsWithLabelGraphSeries()
+ epsSeries = PointsWithLabelGraphSeries()
}
fun initRange() {
@@ -120,22 +109,43 @@ class OverviewData @Inject constructor(
* CALC PROGRESS
*/
- var calcProgress: String = ""
+ var calcProgressPct: Int = 100
/*
* BG
*/
- var lastBg: GlucoseValue? = null
-
- val lastBgColor: Int
- get() = lastBg?.let { lastBg ->
- when {
- lastBg.valueToUnits(profileFunction.getUnits()) < defaultValueHelper.determineLowLine() -> rh.gc(R.color.low)
- lastBg.valueToUnits(profileFunction.getUnits()) > defaultValueHelper.determineHighLine() -> rh.gc(R.color.high)
- else -> rh.gc(R.color.inrange)
+ val lastBg: GlucoseValue?
+ get() =
+ repository.getLastGlucoseValueWrapped().blockingGet().let { gvWrapped ->
+ if (gvWrapped is ValueWrapper.Existing) gvWrapped.value
+ else null
}
- } ?: rh.gc(R.color.inrange)
+
+ val isLow: Boolean
+ get() = lastBg?.let { lastBg ->
+ lastBg.valueToUnits(profileFunction.getUnits()) < defaultValueHelper.determineLowLine()
+ } ?: false
+
+ val isHigh: Boolean
+ get() = lastBg?.let { lastBg ->
+ lastBg.valueToUnits(profileFunction.getUnits()) > defaultValueHelper.determineHighLine()
+ } ?: false
+
+ @ColorInt
+ fun lastBgColor(context: Context?): Int =
+ when {
+ isLow -> rh.gac(context, R.attr.bgLow)
+ isHigh -> rh.gac(context, R.attr.highColor)
+ else -> rh.gac(context, R.attr.bgInRange)
+ }
+
+ val lastBgDescription: String
+ get() = when {
+ isLow -> rh.gs(R.string.a11y_low)
+ isHigh -> rh.gs(R.string.a11y_high)
+ else -> rh.gs(R.string.a11y_inrange)
+ }
val isActualBg: Boolean
get() =
@@ -147,17 +157,16 @@ class OverviewData @Inject constructor(
* TEMPORARY BASAL
*/
- val temporaryBasalText: String
- get() =
- profileFunction.getProfile()?.let { profile ->
- var temporaryBasal = iobCobCalculator.getTempBasalIncludingConvertedExtended(dateUtil.now())
- if (temporaryBasal?.isInProgress == false) temporaryBasal = null
- temporaryBasal?.let { "T:" + it.toStringShort() }
- ?: rh.gs(R.string.pump_basebasalrate, profile.getBasal())
- } ?: rh.gs(R.string.notavailable)
+ fun temporaryBasalText(iobCobCalculator: IobCobCalculator): String =
+ profileFunction.getProfile()?.let { profile ->
+ var temporaryBasal = iobCobCalculator.getTempBasalIncludingConvertedExtended(dateUtil.now())
+ if (temporaryBasal?.isInProgress == false) temporaryBasal = null
+ temporaryBasal?.let { "T:" + it.toStringShort() }
+ ?: rh.gs(R.string.pump_basebasalrate, profile.getBasal())
+ } ?: rh.gs(R.string.notavailable)
- val temporaryBasalDialogText: String
- get() = profileFunction.getProfile()?.let { profile ->
+ fun temporaryBasalDialogText(iobCobCalculator: IobCobCalculator): String =
+ profileFunction.getProfile()?.let { profile ->
iobCobCalculator.getTempBasalIncludingConvertedExtended(dateUtil.now())?.let { temporaryBasal ->
"${rh.gs(R.string.basebasalrate_label)}: ${rh.gs(R.string.pump_basebasalrate, profile.getBasal())}" +
"\n" + rh.gs(R.string.tempbasal_label) + ": " + temporaryBasal.toStringFull(profile, dateUtil)
@@ -165,76 +174,73 @@ class OverviewData @Inject constructor(
?: "${rh.gs(R.string.basebasalrate_label)}: ${rh.gs(R.string.pump_basebasalrate, profile.getBasal())}"
} ?: rh.gs(R.string.notavailable)
- val temporaryBasalIcon: Int
- get() =
- profileFunction.getProfile()?.let { profile ->
- iobCobCalculator.getTempBasalIncludingConvertedExtended(dateUtil.now())?.let { temporaryBasal ->
- val percentRate = temporaryBasal.convertedToPercent(dateUtil.now(), profile)
- when {
- percentRate > 100 -> R.drawable.ic_cp_basal_tbr_high
- percentRate < 100 -> R.drawable.ic_cp_basal_tbr_low
- else -> R.drawable.ic_cp_basal_no_tbr
- }
+ fun temporaryBasalIcon(iobCobCalculator: IobCobCalculator): Int =
+ profileFunction.getProfile()?.let { profile ->
+ iobCobCalculator.getTempBasalIncludingConvertedExtended(dateUtil.now())?.let { temporaryBasal ->
+ val percentRate = temporaryBasal.convertedToPercent(dateUtil.now(), profile)
+ when {
+ percentRate > 100 -> R.drawable.ic_cp_basal_tbr_high
+ percentRate < 100 -> R.drawable.ic_cp_basal_tbr_low
+ else -> R.drawable.ic_cp_basal_no_tbr
}
- } ?: R.drawable.ic_cp_basal_no_tbr
+ }
+ } ?: R.drawable.ic_cp_basal_no_tbr
- val temporaryBasalColor: Int
- get() = iobCobCalculator.getTempBasalIncludingConvertedExtended(dateUtil.now())?.let { rh.gc(R.color.basal) }
- ?: rh.gc(R.color.defaulttextcolor)
+ fun temporaryBasalColor(context: Context?, iobCobCalculator: IobCobCalculator): Int = iobCobCalculator.getTempBasalIncludingConvertedExtended(dateUtil.now())?.let { rh.gac(context , R.attr.basal) }
+ ?: rh.gac(context, R.attr.defaultTextColor)
/*
* EXTENDED BOLUS
*/
- val extendedBolusText: String
- get() =
- iobCobCalculator.getExtendedBolus(dateUtil.now())?.let { extendedBolus ->
- if (!extendedBolus.isInProgress(dateUtil)) ""
- else if (!activePlugin.activePump.isFakingTempsByExtendedBoluses) rh.gs(R.string.pump_basebasalrate, extendedBolus.rate)
- else ""
- } ?: ""
+ fun extendedBolusText(iobCobCalculator: IobCobCalculator): String =
+ iobCobCalculator.getExtendedBolus(dateUtil.now())?.let { extendedBolus ->
+ if (!extendedBolus.isInProgress(dateUtil)) ""
+ else if (!activePlugin.activePump.isFakingTempsByExtendedBoluses) rh.gs(R.string.pump_basebasalrate, extendedBolus.rate)
+ else ""
+ } ?: ""
- val extendedBolusDialogText: String
- get() = iobCobCalculator.getExtendedBolus(dateUtil.now())?.toStringFull(dateUtil) ?: ""
+ fun extendedBolusDialogText(iobCobCalculator: IobCobCalculator): String =
+ iobCobCalculator.getExtendedBolus(dateUtil.now())?.toStringFull(dateUtil) ?: ""
/*
* IOB, COB
*/
- var bolusIob: IobTotal? = null
- var basalIob: IobTotal? = null
- var cobInfo: CobInfo? = null
- var lastCarbsTime: Long = 0L
+ fun bolusIob(iobCobCalculator: IobCobCalculator): IobTotal = iobCobCalculator.calculateIobFromBolus().round()
+ fun basalIob(iobCobCalculator: IobCobCalculator): IobTotal = iobCobCalculator.calculateIobFromTempBasalsIncludingConvertedExtended().round()
+ fun cobInfo(iobCobCalculator: IobCobCalculator): CobInfo = iobCobCalculator.getCobInfo(true, "Overview COB")
- val iobText: String
- get() =
- bolusIob?.let { bolusIob ->
- basalIob?.let { basalIob ->
- rh.gs(R.string.formatinsulinunits, bolusIob.iob + basalIob.basaliob)
- } ?: rh.gs(R.string.value_unavailable_short)
- } ?: rh.gs(R.string.value_unavailable_short)
+ val lastCarbsTime: Long
+ get() = repository.getLastCarbsRecordWrapped().blockingGet().let { lastCarbs ->
+ if (lastCarbs is ValueWrapper.Existing) lastCarbs.value.timestamp else 0L
+ }
- val iobDialogText: String
- get() =
- bolusIob?.let { bolusIob ->
- basalIob?.let { basalIob ->
- rh.gs(R.string.formatinsulinunits, bolusIob.iob + basalIob.basaliob) + "\n" +
- rh.gs(R.string.bolus) + ": " + rh.gs(R.string.formatinsulinunits, bolusIob.iob) + "\n" +
- rh.gs(R.string.basal) + ": " + rh.gs(R.string.formatinsulinunits, basalIob.basaliob)
- } ?: rh.gs(R.string.value_unavailable_short)
- } ?: rh.gs(R.string.value_unavailable_short)
+ fun iobText(iobCobCalculator: IobCobCalculator): String =
+ rh.gs(R.string.formatinsulinunits, bolusIob(iobCobCalculator).iob + basalIob(iobCobCalculator).basaliob)
+
+ fun iobDialogText(iobCobCalculator: IobCobCalculator): String =
+ rh.gs(R.string.formatinsulinunits, bolusIob(iobCobCalculator).iob + basalIob(iobCobCalculator).basaliob) + "\n" +
+ rh.gs(R.string.bolus) + ": " + rh.gs(R.string.formatinsulinunits, bolusIob(iobCobCalculator).iob) + "\n" +
+ rh.gs(R.string.basal) + ": " + rh.gs(R.string.formatinsulinunits, basalIob(iobCobCalculator).basaliob)
/*
* TEMP TARGET
*/
- var temporaryTarget: TemporaryTarget? = null
+ val temporaryTarget: TemporaryTarget?
+ get() =
+ repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet().let { tempTarget ->
+ if (tempTarget is ValueWrapper.Existing) tempTarget.value
+ else null
+ }
/*
* SENSITIVITY
*/
- var lastAutosensData: AutosensData? = null
+ fun lastAutosensData(iobCobCalculator: IobCobCalculator) = iobCobCalculator.ads.getLastAutosensData("Overview", aapsLogger, dateUtil)
+
/*
* Graphs
*/
@@ -245,7 +251,6 @@ class OverviewData @Inject constructor(
var bgReadingGraphSeries: PointsWithLabelGraphSeries = PointsWithLabelGraphSeries()
var predictionsGraphSeries: PointsWithLabelGraphSeries = PointsWithLabelGraphSeries()
- var maxBasalValueFound = 0.0
val basalScale = Scale()
var baseBasalGraphSeries: LineGraphSeries = LineGraphSeries()
var tempBasalGraphSeries: LineGraphSeries = LineGraphSeries()
@@ -259,8 +264,13 @@ class OverviewData @Inject constructor(
var activitySeries: FixedLineGraphSeries = FixedLineGraphSeries()
var activityPredictionSeries: FixedLineGraphSeries = FixedLineGraphSeries()
+ var maxEpsValue = 0.0
+ val epsScale = Scale()
+ var epsSeries: PointsWithLabelGraphSeries = PointsWithLabelGraphSeries()
var maxTreatmentsValue = 0.0
var treatmentsSeries: PointsWithLabelGraphSeries = PointsWithLabelGraphSeries()
+ var maxTherapyEventValue = 0.0
+ var therapyEventSeries: PointsWithLabelGraphSeries = PointsWithLabelGraphSeries()
var maxIobValueFound = Double.MIN_VALUE
val iobScale = Scale()
@@ -294,532 +304,4 @@ class OverviewData @Inject constructor(
val dsMinScale = Scale()
var dsMaxSeries: LineGraphSeries = LineGraphSeries()
var dsMinSeries: LineGraphSeries = LineGraphSeries()
-
- @Synchronized
- @Suppress("SameParameterValue", "UNUSED_PARAMETER")
- fun prepareBgData(from: String) {
-// val start = dateUtil.now()
- maxBgValue = Double.MIN_VALUE
- bgReadingsArray = repository.compatGetBgReadingsDataFromTime(fromTime, toTime, false).blockingGet()
- val bgListArray: MutableList = java.util.ArrayList()
- for (bg in bgReadingsArray) {
- if (bg.timestamp < fromTime || bg.timestamp > toTime) continue
- if (bg.value > maxBgValue) maxBgValue = bg.value
- bgListArray.add(GlucoseValueDataPoint(bg, defaultValueHelper, profileFunction, rh))
- }
- bgListArray.sortWith { o1: DataPointWithLabelInterface, o2: DataPointWithLabelInterface -> o1.x.compareTo(o2.x) }
- bgReadingGraphSeries = PointsWithLabelGraphSeries(Array(bgListArray.size) { i -> bgListArray[i] })
- maxBgValue = Profile.fromMgdlToUnits(maxBgValue, profileFunction.getUnits())
- if (defaultValueHelper.determineHighLine() > maxBgValue) maxBgValue = defaultValueHelper.determineHighLine()
- maxBgValue = addUpperChartMargin(maxBgValue)
-// profiler.log(LTag.UI, "prepareBgData() $from", start)
- }
-
- @Suppress("UNUSED_PARAMETER")
- @Synchronized
- fun preparePredictions(from: String) {
-// val start = dateUtil.now()
- val apsResult = if (config.APS) loop.lastRun?.constraintsProcessed else nsDeviceStatus.getAPSResult(injector)
- val predictionsAvailable = if (config.APS) loop.lastRun?.request?.hasPredictions == true else config.NSCLIENT
- val menuChartSettings = overviewMenus.setting
- // align to hours
- val calendar = Calendar.getInstance().also {
- it.timeInMillis = System.currentTimeMillis()
- it[Calendar.MILLISECOND] = 0
- it[Calendar.SECOND] = 0
- it[Calendar.MINUTE] = 0
- it.add(Calendar.HOUR, 1)
- }
- if (predictionsAvailable && apsResult != null && menuChartSettings[0][OverviewMenus.CharType.PRE.ordinal]) {
- var predictionHours = (ceil(apsResult.latestPredictionsTime - System.currentTimeMillis().toDouble()) / (60 * 60 * 1000)).toInt()
- predictionHours = min(2, predictionHours)
- predictionHours = max(0, predictionHours)
- val hoursToFetch = rangeToDisplay - predictionHours
- toTime = calendar.timeInMillis + 100000 // little bit more to avoid wrong rounding - GraphView specific
- fromTime = toTime - T.hours(hoursToFetch.toLong()).msecs()
- endTime = toTime + T.hours(predictionHours.toLong()).msecs()
- } else {
- toTime = calendar.timeInMillis + 100000 // little bit more to avoid wrong rounding - GraphView specific
- fromTime = toTime - T.hours(rangeToDisplay.toLong()).msecs()
- endTime = toTime
- }
-
- val bgListArray: MutableList = java.util.ArrayList()
- val predictions: MutableList? = apsResult?.predictions
- ?.map { bg -> GlucoseValueDataPoint(bg, defaultValueHelper, profileFunction, rh) }
- ?.toMutableList()
- if (predictions != null) {
- predictions.sortWith { o1: GlucoseValueDataPoint, o2: GlucoseValueDataPoint -> o1.x.compareTo(o2.x) }
- for (prediction in predictions) if (prediction.data.value >= 40) bgListArray.add(prediction)
- }
- predictionsGraphSeries = PointsWithLabelGraphSeries(Array(bgListArray.size) { i -> bgListArray[i] })
-// profiler.log(LTag.UI, "preparePredictions() $from", start)
- }
-
- @Synchronized
- @Suppress("SameParameterValue", "UNUSED_PARAMETER")
- fun prepareBucketedData(from: String) {
-// val start = dateUtil.now()
- val bucketedData = iobCobCalculator.ads.getBucketedDataTableCopy() ?: return
- if (bucketedData.isEmpty()) {
- aapsLogger.debug("No bucketed data.")
- return
- }
- val bucketedListArray: MutableList = java.util.ArrayList()
- for (inMemoryGlucoseValue in bucketedData) {
- if (inMemoryGlucoseValue.timestamp < fromTime || inMemoryGlucoseValue.timestamp > toTime) continue
- bucketedListArray.add(InMemoryGlucoseValueDataPoint(inMemoryGlucoseValue, profileFunction, rh))
- }
- bucketedListArray.sortWith { o1: DataPointWithLabelInterface, o2: DataPointWithLabelInterface -> o1.x.compareTo(o2.x) }
- bucketedGraphSeries = PointsWithLabelGraphSeries(Array(bucketedListArray.size) { i -> bucketedListArray[i] })
-// profiler.log(LTag.UI, "prepareBucketedData() $from", start)
- }
-
- @Suppress("UNUSED_PARAMETER")
- @Synchronized
- fun prepareBasalData(from: String) {
-// val start = dateUtil.now()
- maxBasalValueFound = 0.0
- val baseBasalArray: MutableList = java.util.ArrayList()
- val tempBasalArray: MutableList = java.util.ArrayList()
- val basalLineArray: MutableList = java.util.ArrayList()
- val absoluteBasalLineArray: MutableList = java.util.ArrayList()
- var lastLineBasal = 0.0
- var lastAbsoluteLineBasal = -1.0
- var lastBaseBasal = 0.0
- var lastTempBasal = 0.0
- var time = fromTime
- while (time < toTime) {
- val profile = profileFunction.getProfile(time)
- if (profile == null) {
- time += 60 * 1000L
- continue
- }
- val basalData = iobCobCalculator.getBasalData(profile, time)
- val baseBasalValue = basalData.basal
- var absoluteLineValue = baseBasalValue
- var tempBasalValue = 0.0
- var basal = 0.0
- if (basalData.isTempBasalRunning) {
- tempBasalValue = basalData.tempBasalAbsolute
- absoluteLineValue = tempBasalValue
- if (tempBasalValue != lastTempBasal) {
- tempBasalArray.add(ScaledDataPoint(time, lastTempBasal, basalScale))
- tempBasalArray.add(ScaledDataPoint(time, tempBasalValue.also { basal = it }, basalScale))
- }
- if (lastBaseBasal != 0.0) {
- baseBasalArray.add(ScaledDataPoint(time, lastBaseBasal, basalScale))
- baseBasalArray.add(ScaledDataPoint(time, 0.0, basalScale))
- lastBaseBasal = 0.0
- }
- } else {
- if (baseBasalValue != lastBaseBasal) {
- baseBasalArray.add(ScaledDataPoint(time, lastBaseBasal, basalScale))
- baseBasalArray.add(ScaledDataPoint(time, baseBasalValue.also { basal = it }, basalScale))
- lastBaseBasal = baseBasalValue
- }
- if (lastTempBasal != 0.0) {
- tempBasalArray.add(ScaledDataPoint(time, lastTempBasal, basalScale))
- tempBasalArray.add(ScaledDataPoint(time, 0.0, basalScale))
- }
- }
- if (baseBasalValue != lastLineBasal) {
- basalLineArray.add(ScaledDataPoint(time, lastLineBasal, basalScale))
- basalLineArray.add(ScaledDataPoint(time, baseBasalValue, basalScale))
- }
- if (absoluteLineValue != lastAbsoluteLineBasal) {
- absoluteBasalLineArray.add(ScaledDataPoint(time, lastAbsoluteLineBasal, basalScale))
- absoluteBasalLineArray.add(ScaledDataPoint(time, basal, basalScale))
- }
- lastAbsoluteLineBasal = absoluteLineValue
- lastLineBasal = baseBasalValue
- lastTempBasal = tempBasalValue
- maxBasalValueFound = max(maxBasalValueFound, max(tempBasalValue, baseBasalValue))
- time += 60 * 1000L
- }
-
- // final points
- basalLineArray.add(ScaledDataPoint(toTime, lastLineBasal, basalScale))
- baseBasalArray.add(ScaledDataPoint(toTime, lastBaseBasal, basalScale))
- tempBasalArray.add(ScaledDataPoint(toTime, lastTempBasal, basalScale))
- absoluteBasalLineArray.add(ScaledDataPoint(toTime, lastAbsoluteLineBasal, basalScale))
-
- // create series
- baseBasalGraphSeries = LineGraphSeries(Array(baseBasalArray.size) { i -> baseBasalArray[i] }).also {
- it.isDrawBackground = true
- it.backgroundColor = rh.gc(R.color.basebasal)
- it.thickness = 0
- }
- tempBasalGraphSeries = LineGraphSeries(Array(tempBasalArray.size) { i -> tempBasalArray[i] }).also {
- it.isDrawBackground = true
- it.backgroundColor = rh.gc(R.color.tempbasal)
- it.thickness = 0
- }
- basalLineGraphSeries = LineGraphSeries(Array(basalLineArray.size) { i -> basalLineArray[i] }).also {
- it.setCustomPaint(Paint().also { paint ->
- paint.style = Paint.Style.STROKE
- paint.strokeWidth = rh.getDisplayMetrics().scaledDensity * 2
- paint.pathEffect = DashPathEffect(floatArrayOf(2f, 4f), 0f)
- paint.color = rh.gc(R.color.basal)
- })
- }
- absoluteBasalGraphSeries = LineGraphSeries(Array(absoluteBasalLineArray.size) { i -> absoluteBasalLineArray[i] }).also {
- it.setCustomPaint(Paint().also { absolutePaint ->
- absolutePaint.style = Paint.Style.STROKE
- absolutePaint.strokeWidth = rh.getDisplayMetrics().scaledDensity * 2
- absolutePaint.color = rh.gc(R.color.basal)
- })
- }
-// profiler.log(LTag.UI, "prepareBasalData() $from", start)
- }
-
- @Suppress("UNUSED_PARAMETER")
- @Synchronized
- fun prepareTemporaryTargetData(from: String) {
-// val start = dateUtil.now()
- val profile = profileFunction.getProfile() ?: return
- val units = profileFunction.getUnits()
- var toTime = toTime
- val targetsSeriesArray: MutableList = java.util.ArrayList()
- var lastTarget = -1.0
- loop.lastRun?.constraintsProcessed?.let { toTime = max(it.latestPredictionsTime, toTime) }
- var time = fromTime
- while (time < toTime) {
- val tt = repository.getTemporaryTargetActiveAt(time).blockingGet()
- val value: Double = if (tt is ValueWrapper.Existing) {
- Profile.fromMgdlToUnits(tt.value.target(), units)
- } else {
- Profile.fromMgdlToUnits((profile.getTargetLowMgdl(time) + profile.getTargetHighMgdl(time)) / 2, units)
- }
- if (lastTarget != value) {
- if (lastTarget != -1.0) targetsSeriesArray.add(DataPoint(time.toDouble(), lastTarget))
- targetsSeriesArray.add(DataPoint(time.toDouble(), value))
- }
- lastTarget = value
- time += 5 * 60 * 1000L
- }
- // final point
- targetsSeriesArray.add(DataPoint(toTime.toDouble(), lastTarget))
- // create series
- temporaryTargetSeries = LineGraphSeries(Array(targetsSeriesArray.size) { i -> targetsSeriesArray[i] }).also {
- it.isDrawBackground = false
- it.color = rh.gc(R.color.tempTargetBackground)
- it.thickness = 2
- }
-// profiler.log(LTag.UI, "prepareTemporaryTargetData() $from", start)
- }
-
- @Suppress("UNUSED_PARAMETER")
- @Synchronized
- fun prepareTreatmentsData(from: String) {
-// val start = dateUtil.now()
- maxTreatmentsValue = 0.0
- val filteredTreatments: MutableList = java.util.ArrayList()
- repository.getBolusesDataFromTimeToTime(fromTime, endTime, true).blockingGet()
- .map { BolusDataPoint(it, rh, activePlugin, defaultValueHelper) }
- .filter { it.data.type == Bolus.Type.NORMAL || it.data.type == Bolus.Type.SMB }
- .forEach {
- it.y = getNearestBg(it.x.toLong())
- filteredTreatments.add(it)
- }
- repository.getCarbsDataFromTimeToTimeExpanded(fromTime, endTime, true).blockingGet()
- .map { CarbsDataPoint(it, rh) }
- .forEach {
- it.y = getNearestBg(it.x.toLong())
- filteredTreatments.add(it)
- }
-
- // ProfileSwitch
- repository.getEffectiveProfileSwitchDataFromTimeToTime(fromTime, endTime, true).blockingGet()
- .map { EffectiveProfileSwitchDataPoint(it) }
- .forEach(filteredTreatments::add)
-
- // OfflineEvent
- repository.getOfflineEventDataFromTimeToTime(fromTime, endTime, true).blockingGet()
- .map {
- TherapyEventDataPoint(
- TherapyEvent(timestamp = it.timestamp, duration = it.duration, type = TherapyEvent.Type.APS_OFFLINE, glucoseUnit = TherapyEvent.GlucoseUnit.MMOL),
- rh,
- profileFunction,
- translator
- )
- }
- .forEach(filteredTreatments::add)
-
- // Extended bolus
- if (!activePlugin.activePump.isFakingTempsByExtendedBoluses) {
- repository.getExtendedBolusDataFromTimeToTime(fromTime, endTime, true).blockingGet()
- .map { ExtendedBolusDataPoint(it) }
- .filter { it.duration != 0L }
- .forEach {
- it.y = getNearestBg(it.x.toLong())
- filteredTreatments.add(it)
- }
- }
-
- // Careportal
- repository.compatGetTherapyEventDataFromToTime(fromTime - T.hours(6).msecs(), endTime).blockingGet()
- .map { TherapyEventDataPoint(it, rh, profileFunction, translator) }
- .filterTimeframe(fromTime, endTime)
- .forEach {
- if (it.y == 0.0) it.y = getNearestBg(it.x.toLong())
- filteredTreatments.add(it)
- }
-
- // increase maxY if a treatment forces it's own height that's higher than a BG value
- filteredTreatments.map { it.y }
- .maxOrNull()
- ?.let(::addUpperChartMargin)
- ?.let { maxTreatmentsValue = maxOf(maxTreatmentsValue, it) }
-
- treatmentsSeries = PointsWithLabelGraphSeries(filteredTreatments.toTypedArray())
-// profiler.log(LTag.UI, "prepareTreatmentsData() $from", start)
- }
-
- @Suppress("UNUSED_PARAMETER")
- @Synchronized
- fun prepareIobAutosensData(from: String) {
-// val start = dateUtil.now()
- val iobArray: MutableList = java.util.ArrayList()
- val absIobArray: MutableList = java.util.ArrayList()
- maxIobValueFound = Double.MIN_VALUE
- var lastIob = 0.0
- var absLastIob = 0.0
- var time = fromTime
-
- val minFailOverActiveList: MutableList = java.util.ArrayList()
- val cobArray: MutableList = java.util.ArrayList()
- maxCobValueFound = Double.MIN_VALUE
- var lastCob = 0
-
- val actArrayHist: MutableList = java.util.ArrayList()
- val actArrayPrediction: MutableList = java.util.ArrayList()
- val now = dateUtil.now().toDouble()
- maxIAValue = 0.0
-
- val bgiArrayHist: MutableList = java.util.ArrayList()
- val bgiArrayPrediction: MutableList = java.util.ArrayList()
- maxBGIValue = Double.MIN_VALUE
-
- val devArray: MutableList = java.util.ArrayList()
- maxDevValueFound = Double.MIN_VALUE
-
- val ratioArray: MutableList = java.util.ArrayList()
- maxRatioValueFound = 5.0 //even if sens data equals 0 for all the period, minimum scale is between 95% and 105%
- minRatioValueFound = -5.0
-
- val dsMaxArray: MutableList = java.util.ArrayList()
- val dsMinArray: MutableList = java.util.ArrayList()
- maxFromMaxValueFound = Double.MIN_VALUE
- maxFromMinValueFound = Double.MIN_VALUE
-
- val adsData = iobCobCalculator.ads.clone()
-
- while (time <= toTime) {
- val profile = profileFunction.getProfile(time)
- if (profile == null) {
- time += 5 * 60 * 1000L
- continue
- }
- // IOB
- val iob = iobCobCalculator.calculateFromTreatmentsAndTemps(time, profile)
- val baseBasalIob = iobCobCalculator.calculateAbsoluteIobFromBaseBasals(time)
- val absIob = IobTotal.combine(iob, baseBasalIob)
- val autosensData = adsData.getAutosensDataAtTime(time)
- if (abs(lastIob - iob.iob) > 0.02) {
- if (abs(lastIob - iob.iob) > 0.2) iobArray.add(ScaledDataPoint(time, lastIob, iobScale))
- iobArray.add(ScaledDataPoint(time, iob.iob, iobScale))
- maxIobValueFound = maxOf(maxIobValueFound, abs(iob.iob))
- lastIob = iob.iob
- }
- if (abs(absLastIob - absIob.iob) > 0.02) {
- if (abs(absLastIob - absIob.iob) > 0.2) absIobArray.add(ScaledDataPoint(time, absLastIob, iobScale))
- absIobArray.add(ScaledDataPoint(time, absIob.iob, iobScale))
- maxIobValueFound = maxOf(maxIobValueFound, abs(absIob.iob))
- absLastIob = absIob.iob
- }
-
- // COB
- if (autosensData != null) {
- val cob = autosensData.cob.toInt()
- if (cob != lastCob) {
- if (autosensData.carbsFromBolus > 0) cobArray.add(ScaledDataPoint(time, lastCob.toDouble(), cobScale))
- cobArray.add(ScaledDataPoint(time, cob.toDouble(), cobScale))
- maxCobValueFound = max(maxCobValueFound, cob.toDouble())
- lastCob = cob
- }
- if (autosensData.failOverToMinAbsorptionRate) {
- autosensData.scale = cobScale
- autosensData.chartTime = time
- minFailOverActiveList.add(autosensData)
- }
- }
-
- // ACTIVITY
- if (time <= now) actArrayHist.add(ScaledDataPoint(time, iob.activity, actScale))
- else actArrayPrediction.add(ScaledDataPoint(time, iob.activity, actScale))
- maxIAValue = max(maxIAValue, abs(iob.activity))
-
- // BGI
- val devBgiScale = overviewMenus.isEnabledIn(OverviewMenus.CharType.DEV) == overviewMenus.isEnabledIn(OverviewMenus.CharType.BGI)
- val deviation = if (devBgiScale) autosensData?.deviation ?: 0.0 else 0.0
- val bgi: Double = iob.activity * profile.getIsfMgdl(time) * 5.0
- if (time <= now) bgiArrayHist.add(ScaledDataPoint(time, bgi, bgiScale))
- else bgiArrayPrediction.add(ScaledDataPoint(time, bgi, bgiScale))
- maxBGIValue = max(maxBGIValue, max(abs(bgi), deviation))
-
- // DEVIATIONS
- if (autosensData != null) {
- var color = rh.gc(R.color.deviationblack) // "="
- if (autosensData.type == "" || autosensData.type == "non-meal") {
- if (autosensData.pastSensitivity == "C") color = rh.gc(R.color.deviationgrey)
- if (autosensData.pastSensitivity == "+") color = rh.gc(R.color.deviationgreen)
- if (autosensData.pastSensitivity == "-") color = rh.gc(R.color.deviationred)
- } else if (autosensData.type == "uam") {
- color = rh.gc(R.color.uam)
- } else if (autosensData.type == "csf") {
- color = rh.gc(R.color.deviationgrey)
- }
- devArray.add(OverviewPlugin.DeviationDataPoint(time.toDouble(), autosensData.deviation, color, devScale))
- maxDevValueFound = maxOf(maxDevValueFound, abs(autosensData.deviation), abs(bgi))
- }
-
- // RATIO
- if (autosensData != null) {
- ratioArray.add(ScaledDataPoint(time, 100.0 * (autosensData.autosensResult.ratio - 1), ratioScale))
- maxRatioValueFound = max(maxRatioValueFound, 100.0 * (autosensData.autosensResult.ratio - 1))
- minRatioValueFound = min(minRatioValueFound, 100.0 * (autosensData.autosensResult.ratio - 1))
- }
-
- // DEV SLOPE
- if (autosensData != null) {
- dsMaxArray.add(ScaledDataPoint(time, autosensData.slopeFromMaxDeviation, dsMaxScale))
- dsMinArray.add(ScaledDataPoint(time, autosensData.slopeFromMinDeviation, dsMinScale))
- maxFromMaxValueFound = max(maxFromMaxValueFound, abs(autosensData.slopeFromMaxDeviation))
- maxFromMinValueFound = max(maxFromMinValueFound, abs(autosensData.slopeFromMinDeviation))
- }
-
- time += 5 * 60 * 1000L
- }
- // IOB
- iobSeries = FixedLineGraphSeries(Array(iobArray.size) { i -> iobArray[i] }).also {
- it.isDrawBackground = true
- it.backgroundColor = -0x7f000001 and rh.gc(R.color.iob) //50%
- it.color = rh.gc(R.color.iob)
- it.thickness = 3
- }
- absIobSeries = FixedLineGraphSeries(Array(absIobArray.size) { i -> absIobArray[i] }).also {
- it.isDrawBackground = true
- it.backgroundColor = -0x7f000001 and rh.gc(R.color.iob) //50%
- it.color = rh.gc(R.color.iob)
- it.thickness = 3
- }
-
- if (overviewMenus.setting[0][OverviewMenus.CharType.PRE.ordinal]) {
- val autosensData = adsData.getLastAutosensData("GraphData", aapsLogger, dateUtil)
- val lastAutosensResult = autosensData?.autosensResult ?: AutosensResult()
- val isTempTarget = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet() is ValueWrapper.Existing
- val iobPrediction: MutableList = java.util.ArrayList()
- val iobPredictionArray = iobCobCalculator.calculateIobArrayForSMB(lastAutosensResult, SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget)
- for (i in iobPredictionArray) {
- iobPrediction.add(i.setColor(rh.gc(R.color.iobPredAS)))
- maxIobValueFound = max(maxIobValueFound, abs(i.iob))
- }
- iobPredictions1Series = PointsWithLabelGraphSeries(Array(iobPrediction.size) { i -> iobPrediction[i] })
- aapsLogger.debug(LTag.AUTOSENS, "IOB prediction for AS=" + DecimalFormatter.to2Decimal(lastAutosensResult.ratio) + ": " + iobCobCalculator.iobArrayToString(iobPredictionArray))
- /*
- val iobPrediction2: MutableList = java.util.ArrayList()
- val iobPredictionArray2 = iobCobCalculator.calculateIobArrayForSMB(AutosensResult(), SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget)
- for (i in iobPredictionArray2) {
- iobPrediction2.add(i.setColor(rh.gc(R.color.iobPred)))
- maxIobValueFound = max(maxIobValueFound, abs(i.iob))
- }
- iobPredictions2Series = PointsWithLabelGraphSeries(Array(iobPrediction2.size) { i -> iobPrediction2[i] })
- aapsLogger.debug(LTag.AUTOSENS, "IOB prediction for AS=" + DecimalFormatter.to2Decimal(1.0) + ": " + iobCobCalculator.iobArrayToString(iobPredictionArray2))
- */
- } else {
- iobPredictions1Series = PointsWithLabelGraphSeries()
- //iobPredictions2Series = PointsWithLabelGraphSeries()
- }
-
- // COB
- cobSeries = FixedLineGraphSeries(Array(cobArray.size) { i -> cobArray[i] }).also {
- it.isDrawBackground = true
- it.backgroundColor = -0x7f000001 and rh.gc(R.color.cob) //50%
- it.color = rh.gc(R.color.cob)
- it.thickness = 3
- }
- cobMinFailOverSeries = PointsWithLabelGraphSeries(Array(minFailOverActiveList.size) { i -> minFailOverActiveList[i] })
-
- // ACTIVITY
- activitySeries = FixedLineGraphSeries(Array(actArrayHist.size) { i -> actArrayHist[i] }).also {
- it.isDrawBackground = false
- it.color = rh.gc(R.color.activity)
- it.thickness = 3
- }
- activityPredictionSeries = FixedLineGraphSeries(Array(actArrayPrediction.size) { i -> actArrayPrediction[i] }).also {
- it.setCustomPaint(Paint().also { paint ->
- paint.style = Paint.Style.STROKE
- paint.strokeWidth = 3f
- paint.pathEffect = DashPathEffect(floatArrayOf(4f, 4f), 0f)
- paint.color = rh.gc(R.color.activity)
- })
- }
-
- // BGI
- minusBgiSeries = FixedLineGraphSeries(Array(bgiArrayHist.size) { i -> bgiArrayHist[i] }).also {
- it.isDrawBackground = false
- it.color = rh.gc(R.color.bgi)
- it.thickness = 3
- }
- minusBgiHistSeries = FixedLineGraphSeries(Array(bgiArrayPrediction.size) { i -> bgiArrayPrediction[i] }).also {
- it.setCustomPaint(Paint().also { paint ->
- paint.style = Paint.Style.STROKE
- paint.strokeWidth = 3f
- paint.pathEffect = DashPathEffect(floatArrayOf(4f, 4f), 0f)
- paint.color = rh.gc(R.color.bgi)
- })
- }
-
- // DEVIATIONS
- deviationsSeries = BarGraphSeries(Array(devArray.size) { i -> devArray[i] }).also {
- it.setValueDependentColor { data: OverviewPlugin.DeviationDataPoint -> data.color }
- }
-
- // RATIO
- ratioSeries = LineGraphSeries(Array(ratioArray.size) { i -> ratioArray[i] }).also {
- it.color = rh.gc(R.color.ratio)
- it.thickness = 3
- }
-
- // DEV SLOPE
- dsMaxSeries = LineGraphSeries(Array(dsMaxArray.size) { i -> dsMaxArray[i] }).also {
- it.color = rh.gc(R.color.devslopepos)
- it.thickness = 3
- }
- dsMinSeries = LineGraphSeries(Array(dsMinArray.size) { i -> dsMinArray[i] }).also {
- it.color = rh.gc(R.color.devslopeneg)
- it.thickness = 3
- }
-
-// profiler.log(LTag.UI, "prepareIobAutosensData() $from", start)
- }
-
- private fun addUpperChartMargin(maxBgValue: Double) =
- if (profileFunction.getUnits() == GlucoseUnit.MGDL) Round.roundTo(maxBgValue, 40.0) + 80 else Round.roundTo(maxBgValue, 2.0) + 4
-
- private fun getNearestBg(date: Long): Double {
- bgReadingsArray.let { bgReadingsArray ->
- for (reading in bgReadingsArray) {
- if (reading.timestamp > date) continue
- return Profile.fromMgdlToUnits(reading.value, profileFunction.getUnits())
- }
- return if (bgReadingsArray.isNotEmpty()) Profile.fromMgdlToUnits(bgReadingsArray[0].value, profileFunction.getUnits())
- else Profile.fromMgdlToUnits(100.0, profileFunction.getUnits())
- }
- }
-
- private fun List.filterTimeframe(fromTime: Long, endTime: Long): List =
- filter { it.x + it.duration >= fromTime && it.x <= endTime }
-
}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.kt
index d82e315359..8ecba9d5ad 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.kt
@@ -5,9 +5,11 @@ import android.app.NotificationManager
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
-import android.graphics.Color
import android.graphics.Paint
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
import android.graphics.drawable.AnimationDrawable
+import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.HandlerThread
@@ -34,31 +36,31 @@ import info.nightscout.androidaps.database.entities.UserEntry.Sources
import info.nightscout.androidaps.database.interfaces.end
import info.nightscout.androidaps.databinding.OverviewFragmentBinding
import info.nightscout.androidaps.dialogs.*
-import info.nightscout.androidaps.events.EventAcceptOpenLoopChange
-import info.nightscout.androidaps.events.EventInitializationChanged
-import info.nightscout.androidaps.events.EventPreferenceChange
-import info.nightscout.androidaps.events.EventPumpStatusChanged
-import info.nightscout.androidaps.events.EventRefreshOverview
+import info.nightscout.androidaps.events.*
import info.nightscout.androidaps.extensions.directionToIcon
-import info.nightscout.androidaps.extensions.isInProgress
+import info.nightscout.androidaps.extensions.runOnUiThread
import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.extensions.valueToUnitsString
import info.nightscout.androidaps.interfaces.*
-import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.aps.loop.events.EventNewOpenLoopNotification
+import info.nightscout.androidaps.plugins.aps.openAPSSMB.DetermineBasalResultSMB
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.androidaps.plugins.constraints.bgQualityCheck.BgQualityCheckPlugin
import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin
import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus
import info.nightscout.androidaps.plugins.general.overview.activities.QuickWizardListActivity
-import info.nightscout.androidaps.plugins.general.overview.events.*
+import info.nightscout.androidaps.plugins.general.overview.events.EventUpdateOverviewCalcProgress
+import info.nightscout.androidaps.plugins.general.overview.events.EventUpdateOverviewGraph
+import info.nightscout.androidaps.plugins.general.overview.events.EventUpdateOverviewIobCob
+import info.nightscout.androidaps.plugins.general.overview.events.EventUpdateOverviewNotification
+import info.nightscout.androidaps.plugins.general.overview.events.EventUpdateOverviewSensitivity
import info.nightscout.androidaps.plugins.general.overview.graphData.GraphData
import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationStore
-import info.nightscout.androidaps.plugins.general.wear.events.EventWearInitiateAction
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProvider
import info.nightscout.androidaps.plugins.pump.common.defs.PumpType
+import info.nightscout.androidaps.plugins.pump.omnipod.eros.OmnipodErosPumpPlugin
import info.nightscout.androidaps.plugins.source.DexcomPlugin
import info.nightscout.androidaps.plugins.source.XdripPlugin
import info.nightscout.androidaps.skins.SkinProvider
@@ -68,20 +70,19 @@ import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.ToastUtils
import info.nightscout.androidaps.utils.TrendCalculator
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
-import info.nightscout.androidaps.utils.buildHelper.BuildHelper
import info.nightscout.androidaps.utils.protection.ProtectionCheck
-import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
-import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.androidaps.utils.ui.SingleClickButton
import info.nightscout.androidaps.utils.ui.UIRunnable
import info.nightscout.androidaps.utils.wizard.QuickWizard
-import io.reactivex.disposables.CompositeDisposable
-import io.reactivex.rxkotlin.plusAssign
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.shared.sharedPreferences.SP
+import info.nightscout.shared.weardata.EventData
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.kotlin.plusAssign
import java.util.*
import java.util.concurrent.TimeUnit
import javax.inject.Inject
-import kotlin.collections.ArrayList
import kotlin.math.abs
import kotlin.math.min
@@ -148,10 +149,11 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
_binding = it
//check screen width
dm = DisplayMetrics()
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R)
+ @Suppress("DEPRECATION")
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
activity?.display?.getRealMetrics(dm)
else
- @Suppress("DEPRECATION") activity?.windowManager?.defaultDisplay?.getMetrics(dm)
+ activity?.windowManager?.defaultDisplay?.getMetrics(dm)
}.root
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -164,13 +166,13 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
smallHeight = screenHeight <= Constants.SMALL_HEIGHT
val landscape = screenHeight < screenWidth
- skinProvider.activeSkin().preProcessLandscapeOverviewLayout(dm, view, landscape, rh.gb(R.bool.isTablet), smallHeight)
- binding.nsclientLayout.visibility = config.NSCLIENT.toVisibility()
+ skinProvider.activeSkin().preProcessLandscapeOverviewLayout(dm, binding, landscape, rh.gb(R.bool.isTablet), smallHeight)
+ binding.nsclientCard.visibility = config.NSCLIENT.toVisibility()
binding.notifications.setHasFixedSize(false)
binding.notifications.layoutManager = LinearLayoutManager(view.context)
axisWidth = if (dm.densityDpi <= 120) 3 else if (dm.densityDpi <= 160) 10 else if (dm.densityDpi <= 320) 35 else if (dm.densityDpi <= 420) 50 else if (dm.densityDpi <= 560) 70 else 80
- binding.graphsLayout.bgGraph.gridLabelRenderer?.gridColor = rh.gc(R.color.graphgrid)
+ binding.graphsLayout.bgGraph.gridLabelRenderer?.gridColor = rh.gac(context, R.attr.graphGrid)
binding.graphsLayout.bgGraph.gridLabelRenderer?.reloadStyles()
binding.graphsLayout.bgGraph.gridLabelRenderer?.labelVerticalWidth = axisWidth
binding.graphsLayout.bgGraph.layoutParams?.height = rh.dpToPx(skinProvider.activeSkin().mainGraphHeight)
@@ -179,21 +181,16 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
carbAnimation?.setEnterFadeDuration(1200)
carbAnimation?.setExitFadeDuration(1200)
-
binding.graphsLayout.bgGraph.setOnLongClickListener {
overviewData.rangeToDisplay += 6
overviewData.rangeToDisplay = if (overviewData.rangeToDisplay > 24) 6 else overviewData.rangeToDisplay
sp.putInt(R.string.key_rangetodisplay, overviewData.rangeToDisplay)
- overviewData.initRange()
- overviewData.prepareBucketedData("EventBucketedDataCreated")
- overviewData.prepareBgData("EventBucketedDataCreated")
- updateGraph("rangeChange")
rxBus.send(EventPreferenceChange(rh, R.string.key_rangetodisplay))
sp.putBoolean(R.string.key_objectiveusescale, true)
false
}
prepareGraphsIfNeeded(overviewMenus.setting.size)
- overviewMenus.setupChartMenu(binding.graphsLayout.chartMenuButton)
+ context?.let { overviewMenus.setupChartMenu(it, binding.graphsLayout.chartMenuButton) }
binding.activeProfile.setOnClickListener(this)
binding.activeProfile.setOnLongClickListener(this)
@@ -223,113 +220,114 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
@Synchronized
override fun onResume() {
super.onResume()
- disposable += activePlugin.activeOverview.overviewBus
- .toObservable(EventUpdateOverviewTime::class.java)
- .debounce(1L, TimeUnit.SECONDS)
- .observeOn(aapsSchedulers.main)
- .subscribe({ updateTime(it.from) }, fabricPrivacy::logException)
disposable += activePlugin.activeOverview.overviewBus
.toObservable(EventUpdateOverviewCalcProgress::class.java)
.observeOn(aapsSchedulers.main)
- .subscribe({ updateCalcProgress(it.from) }, fabricPrivacy::logException)
- disposable += activePlugin.activeOverview.overviewBus
- .toObservable(EventUpdateOverviewProfile::class.java)
- .debounce(1L, TimeUnit.SECONDS)
- .observeOn(aapsSchedulers.main)
- .subscribe({ updateProfile(it.from) }, fabricPrivacy::logException)
- disposable += activePlugin.activeOverview.overviewBus
- .toObservable(EventUpdateOverviewTemporaryBasal::class.java)
- .debounce(1L, TimeUnit.SECONDS)
- .observeOn(aapsSchedulers.main)
- .subscribe({ updateTemporaryBasal(it.from) }, fabricPrivacy::logException)
- disposable += activePlugin.activeOverview.overviewBus
- .toObservable(EventUpdateOverviewExtendedBolus::class.java)
- .debounce(1L, TimeUnit.SECONDS)
- .observeOn(aapsSchedulers.main)
- .subscribe({ updateExtendedBolus(it.from) }, fabricPrivacy::logException)
- disposable += activePlugin.activeOverview.overviewBus
- .toObservable(EventUpdateOverviewTemporaryTarget::class.java)
- .debounce(1L, TimeUnit.SECONDS)
- .observeOn(aapsSchedulers.main)
- .subscribe({ updateTemporaryTarget(it.from) }, fabricPrivacy::logException)
- disposable += activePlugin.activeOverview.overviewBus
- .toObservable(EventUpdateOverviewBg::class.java)
- .debounce(1L, TimeUnit.SECONDS)
- .observeOn(aapsSchedulers.main)
- .subscribe({ updateBg(it.from) }, fabricPrivacy::logException)
+ .subscribe({ updateCalcProgress() }, fabricPrivacy::logException)
disposable += activePlugin.activeOverview.overviewBus
.toObservable(EventUpdateOverviewIobCob::class.java)
.debounce(1L, TimeUnit.SECONDS)
- .observeOn(aapsSchedulers.main)
- .subscribe({ updateIobCob(it.from) }, fabricPrivacy::logException)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({ updateIobCob() }, fabricPrivacy::logException)
disposable += activePlugin.activeOverview.overviewBus
.toObservable(EventUpdateOverviewSensitivity::class.java)
.debounce(1L, TimeUnit.SECONDS)
.observeOn(aapsSchedulers.main)
- .subscribe({ updateSensitivity(it.from) }, fabricPrivacy::logException)
+ .subscribe({ updateSensitivity() }, fabricPrivacy::logException)
disposable += activePlugin.activeOverview.overviewBus
.toObservable(EventUpdateOverviewGraph::class.java)
.debounce(1L, TimeUnit.SECONDS)
.observeOn(aapsSchedulers.main)
- .subscribe({ updateGraph(it.from) }, fabricPrivacy::logException)
- disposable += activePlugin.activeOverview.overviewBus
- .toObservable(EventUpdateOverviewPumpStatus::class.java)
- .observeOn(aapsSchedulers.main)
- .subscribe({ updatePumpStatus(it.from) }, fabricPrivacy::logException)
+ .subscribe({ updateGraph() }, fabricPrivacy::logException)
disposable += activePlugin.activeOverview.overviewBus
.toObservable(EventUpdateOverviewNotification::class.java)
.observeOn(aapsSchedulers.main)
- .subscribe({ updateNotification(it.from) }, fabricPrivacy::logException)
+ .subscribe({ updateNotification() }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventScale::class.java)
+ .observeOn(aapsSchedulers.main)
+ .subscribe({
+ overviewData.rangeToDisplay = it.hours
+ sp.putInt(R.string.key_rangetodisplay, it.hours)
+ rxBus.send(EventPreferenceChange(rh, R.string.key_rangetodisplay))
+ sp.putBoolean(R.string.key_objectiveusescale, true)
+ }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventNewBG::class.java)
+ .debounce(1L, TimeUnit.SECONDS)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({ updateBg() }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventRefreshOverview::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({
- if (it.now) overviewPlugin.refreshLoop(it.from)
- else scheduleUpdateGUI(it.from)
+ if (it.now) refreshAll()
+ else scheduleUpdateGUI()
}, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventAcceptOpenLoopChange::class.java)
.observeOn(aapsSchedulers.io)
- .subscribe({ scheduleUpdateGUI("EventAcceptOpenLoopChange") }, fabricPrivacy::logException)
- disposable += rxBus
- .toObservable(EventInitializationChanged::class.java)
- .observeOn(aapsSchedulers.main)
- .subscribe({ updateTime("EventInitializationChanged") }, fabricPrivacy::logException)
+ .subscribe({ scheduleUpdateGUI() }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventPreferenceChange::class.java)
.observeOn(aapsSchedulers.io)
- .subscribe({ scheduleUpdateGUI("EventPreferenceChange") }, fabricPrivacy::logException)
+ .subscribe({ scheduleUpdateGUI() }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventNewOpenLoopNotification::class.java)
.observeOn(aapsSchedulers.io)
- .subscribe({ scheduleUpdateGUI("EventNewOpenLoopNotification") }, fabricPrivacy::logException)
+ .subscribe({ scheduleUpdateGUI() }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventPumpStatusChanged::class.java)
.observeOn(aapsSchedulers.main)
.delay(30, TimeUnit.MILLISECONDS, aapsSchedulers.main)
.subscribe({
overviewData.pumpStatus = it.getStatus(rh)
- updatePumpStatus("EventPumpStatusChanged")
+ updatePumpStatus()
}, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventEffectiveProfileSwitchChanged::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({ updateProfile() }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventTempTargetChange::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({ updateTemporaryTarget() }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventExtendedBolusChange::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({ updateExtendedBolus() }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventTempBasalChange::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({ updateTemporaryBasal() }, fabricPrivacy::logException)
refreshLoop = Runnable {
- overviewPlugin.refreshLoop("refreshLoop")
+ refreshAll()
handler.postDelayed(refreshLoop, 60 * 1000L)
}
handler.postDelayed(refreshLoop, 60 * 1000L)
- updateTime("onResume")
- updateCalcProgress("onResume")
- updateProfile("onResume")
- updateTemporaryBasal("onResume")
- updateExtendedBolus("onResume")
- updateTemporaryTarget("onResume")
- updateBg("onResume")
- updateIobCob("onResume")
- updateSensitivity("onResume")
- updateGraph("onResume")
- updatePumpStatus("onResume")
- updateNotification("onResume")
+ handler.post { refreshAll() }
+ updatePumpStatus()
+ updateCalcProgress()
+ }
+
+ fun refreshAll() {
+ runOnUiThread {
+ _binding ?: return@runOnUiThread
+ updateTime()
+ updateSensitivity()
+ updateGraph()
+ updateNotification()
+ }
+ updateBg()
+ updateTemporaryBasal()
+ updateExtendedBolus()
+ updateIobCob()
+ processButtonsVisibility()
+ processAps()
+ updateProfile()
+ updateTemporaryTarget()
}
@Synchronized
@@ -413,14 +411,15 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
loop.invoke("Accept temp button", false)
if (lastRun?.lastAPSRun != null && lastRun.constraintsProcessed?.isChangeRequested == true) {
protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, UIRunnable {
- OKDialog.showConfirmation(activity, rh.gs(R.string.tempbasal_label), lastRun.constraintsProcessed?.toSpanned()
- ?: "".toSpanned(), {
- uel.log(Action.ACCEPTS_TEMP_BASAL, Sources.Overview)
- (context?.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).cancel(Constants.notificationID)
- rxBus.send(EventWearInitiateAction("cancelChangeRequest"))
- Thread { loop.acceptChangeRequest() }.run()
- binding.buttonsLayout.acceptTempButton.visibility = View.GONE
- })
+ if (isAdded)
+ OKDialog.showConfirmation(activity, rh.gs(R.string.tempbasal_label), lastRun.constraintsProcessed?.toSpanned()
+ ?: "".toSpanned(), {
+ uel.log(Action.ACCEPTS_TEMP_BASAL, Sources.Overview)
+ (context?.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager?)?.cancel(Constants.notificationID)
+ rxBus.send(EventMobileToWear(EventData.CancelNotification(dateUtil.now())))
+ Thread { loop.acceptChangeRequest() }.run()
+ binding.buttonsLayout.acceptTempButton.visibility = View.GONE
+ })
})
}
}
@@ -515,13 +514,16 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
// QuickWizard button
val quickWizardEntry = quickWizard.getActive()
- if (quickWizardEntry != null && lastBG != null && profile != null && pump.isInitialized() && !pump.isSuspended() && !loop.isDisconnected) {
- binding.buttonsLayout.quickWizardButton.visibility = View.VISIBLE
- val wizard = quickWizardEntry.doCalc(profile, profileName, lastBG, false)
- binding.buttonsLayout.quickWizardButton.text = quickWizardEntry.buttonText() + "\n" + rh.gs(R.string.format_carbs, quickWizardEntry.carbs()) +
- " " + rh.gs(R.string.formatinsulinunits, wizard.calculatedTotalInsulin)
- if (wizard.calculatedTotalInsulin <= 0) binding.buttonsLayout.quickWizardButton.visibility = View.GONE
- } else binding.buttonsLayout.quickWizardButton.visibility = View.GONE
+ runOnUiThread {
+ _binding ?: return@runOnUiThread
+ if (quickWizardEntry != null && lastBG != null && profile != null && pump.isInitialized() && !pump.isSuspended() && !loop.isDisconnected) {
+ binding.buttonsLayout.quickWizardButton.visibility = View.VISIBLE
+ val wizard = quickWizardEntry.doCalc(profile, profileName, lastBG, false)
+ binding.buttonsLayout.quickWizardButton.text = quickWizardEntry.buttonText() + "\n" + rh.gs(R.string.format_carbs, quickWizardEntry.carbs()) +
+ " " + rh.gs(R.string.formatinsulinunits, wizard.calculatedTotalInsulin)
+ if (wizard.calculatedTotalInsulin <= 0) binding.buttonsLayout.quickWizardButton.visibility = View.GONE
+ } else binding.buttonsLayout.quickWizardButton.visibility = View.GONE
+ }
// **** Temp button ****
val lastRun = loop.lastRun
@@ -532,144 +534,188 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
(lastRun.lastOpenModeAccept == 0L || lastRun.lastOpenModeAccept < lastRun.lastAPSRun) &&// never accepted or before last result
lastRun.constraintsProcessed?.isChangeRequested == true // change is requested
- if (showAcceptButton && pump.isInitialized() && !pump.isSuspended() && (loop as PluginBase).isEnabled()) {
- binding.buttonsLayout.acceptTempButton.visibility = View.VISIBLE
- binding.buttonsLayout.acceptTempButton.text = "${rh.gs(R.string.setbasalquestion)}\n${lastRun!!.constraintsProcessed}"
- } else {
- binding.buttonsLayout.acceptTempButton.visibility = View.GONE
- }
+ runOnUiThread {
+ _binding ?: return@runOnUiThread
+ if (showAcceptButton && pump.isInitialized() && !pump.isSuspended() && (loop as PluginBase).isEnabled()) {
+ binding.buttonsLayout.acceptTempButton.visibility = View.VISIBLE
+ binding.buttonsLayout.acceptTempButton.text = "${rh.gs(R.string.setbasalquestion)}\n${lastRun!!.constraintsProcessed}"
+ } else {
+ binding.buttonsLayout.acceptTempButton.visibility = View.GONE
+ }
- // **** Various treatment buttons ****
- binding.buttonsLayout.carbsButton.visibility =
- ((!activePlugin.activePump.pumpDescription.storesCarbInfo || pump.isInitialized() && !pump.isSuspended()) && profile != null
- && sp.getBoolean(R.string.key_show_carbs_button, true)).toVisibility()
- binding.buttonsLayout.treatmentButton.visibility = (!loop.isDisconnected && pump.isInitialized() && !pump.isSuspended() && profile != null
- && sp.getBoolean(R.string.key_show_treatment_button, false)).toVisibility()
- binding.buttonsLayout.wizardButton.visibility = (!loop.isDisconnected && pump.isInitialized() && !pump.isSuspended() && profile != null
- && sp.getBoolean(R.string.key_show_wizard_button, true)).toVisibility()
- binding.buttonsLayout.insulinButton.visibility = (!loop.isDisconnected && pump.isInitialized() && !pump.isSuspended() && profile != null
- && sp.getBoolean(R.string.key_show_insulin_button, true)).toVisibility()
+ // **** Various treatment buttons ****
+ binding.buttonsLayout.carbsButton.visibility =
+ ((!activePlugin.activePump.pumpDescription.storesCarbInfo || pump.isInitialized() && !pump.isSuspended()) && profile != null
+ && sp.getBoolean(R.string.key_show_carbs_button, true)).toVisibility()
+ binding.buttonsLayout.treatmentButton.visibility = (!loop.isDisconnected && pump.isInitialized() && !pump.isSuspended() && profile != null
+ && sp.getBoolean(R.string.key_show_treatment_button, false)).toVisibility()
+ binding.buttonsLayout.wizardButton.visibility = (!loop.isDisconnected && pump.isInitialized() && !pump.isSuspended() && profile != null
+ && sp.getBoolean(R.string.key_show_wizard_button, true)).toVisibility()
+ binding.buttonsLayout.insulinButton.visibility = (!loop.isDisconnected && pump.isInitialized() && !pump.isSuspended() && profile != null
+ && sp.getBoolean(R.string.key_show_insulin_button, true)).toVisibility()
- // **** Calibration & CGM buttons ****
- val xDripIsBgSource = xdripPlugin.isEnabled()
- val dexcomIsSource = dexcomPlugin.isEnabled()
- binding.buttonsLayout.calibrationButton.visibility = (xDripIsBgSource && actualBG != null && sp.getBoolean(R.string.key_show_calibration_button, true)).toVisibility()
- if (dexcomIsSource) {
- binding.buttonsLayout.cgmButton.setCompoundDrawablesWithIntrinsicBounds(null, rh.gd(R.drawable.ic_byoda), null, null)
- binding.buttonsLayout.cgmButton.setTextColor(rh.gc(R.color.colorLightGray))
- } else if (xDripIsBgSource) {
- binding.buttonsLayout.cgmButton.setCompoundDrawablesWithIntrinsicBounds(null, rh.gd(R.drawable.ic_xdrip), null, null)
- binding.buttonsLayout.cgmButton.setTextColor(rh.gc(R.color.colorCalibrationButton))
- }
- binding.buttonsLayout.cgmButton.visibility = (sp.getBoolean(R.string.key_show_cgm_button, false) && (xDripIsBgSource || dexcomIsSource)).toVisibility()
+ // **** Calibration & CGM buttons ****
+ val xDripIsBgSource = xdripPlugin.isEnabled()
+ val dexcomIsSource = dexcomPlugin.isEnabled()
+ binding.buttonsLayout.calibrationButton.visibility = (xDripIsBgSource && actualBG != null && sp.getBoolean(R.string.key_show_calibration_button, true)).toVisibility()
+ if (dexcomIsSource) {
+ binding.buttonsLayout.cgmButton.setCompoundDrawablesWithIntrinsicBounds(null, rh.gd(R.drawable.ic_byoda), null, null)
+ for (drawable in binding.buttonsLayout.cgmButton.compoundDrawables) {
+ drawable?.mutate()
+ drawable?.colorFilter = PorterDuffColorFilter(rh.gac(context, R.attr.cgmDexColor), PorterDuff.Mode.SRC_IN)
+ }
+ binding.buttonsLayout.cgmButton.setTextColor(rh.gac(context, R.attr.cgmDexColor))
+ } else if (xDripIsBgSource) {
+ binding.buttonsLayout.cgmButton.setCompoundDrawablesWithIntrinsicBounds(null, rh.gd(R.drawable.ic_xdrip), null, null)
+ for (drawable in binding.buttonsLayout.cgmButton.compoundDrawables) {
+ drawable?.mutate()
+ drawable?.colorFilter = PorterDuffColorFilter(rh.gac(context, R.attr.cgmXdripColor), PorterDuff.Mode.SRC_IN)
+ }
+ binding.buttonsLayout.cgmButton.setTextColor(rh.gac(context, R.attr.cgmXdripColor))
+ }
+ binding.buttonsLayout.cgmButton.visibility = (sp.getBoolean(R.string.key_show_cgm_button, false) && (xDripIsBgSource || dexcomIsSource)).toVisibility()
- // Automation buttons
- binding.buttonsLayout.userButtonsLayout.removeAllViews()
- val events = automationPlugin.userEvents()
- if (!loop.isDisconnected && pump.isInitialized() && !pump.isSuspended() && profile != null)
- for (event in events)
- if (event.isEnabled && event.trigger.shouldRun())
- context?.let { context ->
- SingleClickButton(context).also {
- it.setTextColor(rh.gc(R.color.colorTreatmentButton))
- it.setTextSize(TypedValue.COMPLEX_UNIT_SP, 10f)
- it.layoutParams = LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 0.5f).also { l ->
- l.setMargins(0, 0, rh.dpToPx(-4), 0)
+ // Automation buttons
+ binding.buttonsLayout.userButtonsLayout.removeAllViews()
+ val events = automationPlugin.userEvents()
+ if (!loop.isDisconnected && pump.isInitialized() && !pump.isSuspended() && profile != null)
+ for (event in events)
+ if (event.isEnabled && event.trigger.shouldRun())
+ context?.let { context ->
+ SingleClickButton(context).also {
+ it.setTextColor(rh.gac(context, R.attr.treatmentButton))
+ it.setTextSize(TypedValue.COMPLEX_UNIT_SP, 10f)
+ it.layoutParams = LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 0.5f).also { l ->
+ l.setMargins(0, 0, rh.dpToPx(-4), 0)
+ }
+ it.setCompoundDrawablesWithIntrinsicBounds(null, rh.gd(R.drawable.ic_danar_useropt), null, null)
+ it.text = event.title
+
+ it.setOnClickListener {
+ OKDialog.showConfirmation(context, rh.gs(R.string.run_question, event.title), { handler.post { automationPlugin.processEvent(event) } })
+ }
+ binding.buttonsLayout.userButtonsLayout.addView(it)
}
- it.setCompoundDrawablesWithIntrinsicBounds(null, rh.gd(R.drawable.ic_danar_useropt), null, null)
- it.text = event.title
-
- it.setOnClickListener {
- OKDialog.showConfirmation(
- context,
- rh.gs(R.string.run_question, event.title),
- { handler.post { automationPlugin.processEvent(event) } }
- )
- }
- binding.buttonsLayout.userButtonsLayout.addView(it)
}
- }
- binding.buttonsLayout.userButtonsLayout.visibility = events.isNotEmpty().toVisibility()
+ binding.buttonsLayout.userButtonsLayout.visibility = events.isNotEmpty().toVisibility()
+ }
}
private fun processAps() {
val pump = activePlugin.activePump
+ val profile = profileFunction.getProfile()
// aps mode
val closedLoopEnabled = constraintChecker.isClosedLoopAllowed()
- if (config.APS && pump.pumpDescription.isTempBasalCapable) {
- binding.infoLayout.apsMode.visibility = View.VISIBLE
- binding.infoLayout.timeLayout.visibility = View.GONE
- when {
- (loop as PluginBase).isEnabled() && loop.isSuperBolus -> {
- binding.infoLayout.apsMode.setImageResource(R.drawable.ic_loop_superbolus)
- binding.infoLayout.apsModeText.text = dateUtil.age(loop.minutesToEndOfSuspend() * 60000L, true, rh)
- binding.infoLayout.apsModeText.visibility = View.VISIBLE
- }
- loop.isDisconnected -> {
- binding.infoLayout.apsMode.setImageResource(R.drawable.ic_loop_disconnected)
- binding.infoLayout.apsModeText.text = dateUtil.age(loop.minutesToEndOfSuspend() * 60000L, true, rh)
- binding.infoLayout.apsModeText.visibility = View.VISIBLE
- }
-
- (loop as PluginBase).isEnabled() && loop.isSuspended -> {
- binding.infoLayout.apsMode.setImageResource(R.drawable.ic_loop_paused)
- binding.infoLayout.apsModeText.text = dateUtil.age(loop.minutesToEndOfSuspend() * 60000L, true, rh)
- binding.infoLayout.apsModeText.visibility = View.VISIBLE
- }
-
- pump.isSuspended() -> {
- binding.infoLayout.apsMode.setImageResource(
- if (pump.model() == PumpType.OMNIPOD_EROS || pump.model() == PumpType.OMNIPOD_DASH) {
- // For Omnipod, indicate the pump as disconnected when it's suspended.
- // The only way to 'reconnect' it, is through the Omnipod tab
- R.drawable.ic_loop_disconnected
- } else {
- R.drawable.ic_loop_paused
- }
- )
- binding.infoLayout.apsModeText.visibility = View.GONE
- }
-
- (loop as PluginBase).isEnabled() && closedLoopEnabled.value() && loop.isLGS -> {
- binding.infoLayout.apsMode.setImageResource(R.drawable.ic_loop_lgs)
- binding.infoLayout.apsModeText.visibility = View.GONE
- }
-
- (loop as PluginBase).isEnabled() && closedLoopEnabled.value() -> {
- binding.infoLayout.apsMode.setImageResource(R.drawable.ic_loop_closed)
- binding.infoLayout.apsModeText.visibility = View.GONE
- }
-
- (loop as PluginBase).isEnabled() && !closedLoopEnabled.value() -> {
- binding.infoLayout.apsMode.setImageResource(R.drawable.ic_loop_open)
- binding.infoLayout.apsModeText.visibility = View.GONE
- }
-
- else -> {
- binding.infoLayout.apsMode.setImageResource(R.drawable.ic_loop_disabled)
- binding.infoLayout.apsModeText.visibility = View.GONE
- }
+ fun apsModeSetA11yLabel(stringRes: Int) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ binding.infoLayout.apsMode.stateDescription = rh.gs(stringRes)
+ } else {
+ binding.infoLayout.apsMode.contentDescription = rh.gs(R.string.apsmode_title) + " " + rh.gs(stringRes)
}
- } else {
- //nsclient
- binding.infoLayout.apsMode.visibility = View.GONE
- binding.infoLayout.apsModeText.visibility = View.GONE
- binding.infoLayout.timeLayout.visibility = View.VISIBLE
}
- // pump status from ns
- binding.pump.text = nsDeviceStatus.pumpStatus
- binding.pump.setOnClickListener { activity?.let { OKDialog.show(it, rh.gs(R.string.pump), nsDeviceStatus.extendedPumpStatus) } }
+ runOnUiThread {
+ _binding ?: return@runOnUiThread
+ if (config.APS && pump.pumpDescription.isTempBasalCapable) {
+ binding.infoLayout.apsMode.visibility = View.VISIBLE
+ binding.infoLayout.timeLayout.visibility = View.GONE
+ when {
+ (loop as PluginBase).isEnabled() && loop.isSuperBolus -> {
+ binding.infoLayout.apsMode.setImageResource(R.drawable.ic_loop_superbolus)
+ apsModeSetA11yLabel(R.string.superbolus)
+ binding.infoLayout.apsModeText.text = dateUtil.age(loop.minutesToEndOfSuspend() * 60000L, true, rh)
+ binding.infoLayout.apsModeText.visibility = View.VISIBLE
+ }
- // OpenAPS status from ns
- binding.openaps.text = nsDeviceStatus.openApsStatus
- binding.openaps.setOnClickListener { activity?.let { OKDialog.show(it, rh.gs(R.string.openaps), nsDeviceStatus.extendedOpenApsStatus) } }
+ loop.isDisconnected -> {
+ binding.infoLayout.apsMode.setImageResource(R.drawable.ic_loop_disconnected)
+ apsModeSetA11yLabel(R.string.disconnected)
+ binding.infoLayout.apsModeText.text = dateUtil.age(loop.minutesToEndOfSuspend() * 60000L, true, rh)
+ binding.infoLayout.apsModeText.visibility = View.VISIBLE
+ }
- // Uploader status from ns
- binding.uploader.text = nsDeviceStatus.uploaderStatusSpanned
- binding.uploader.setOnClickListener { activity?.let { OKDialog.show(it, rh.gs(R.string.uploader), nsDeviceStatus.extendedUploaderStatus) } }
+ (loop as PluginBase).isEnabled() && loop.isSuspended -> {
+ binding.infoLayout.apsMode.setImageResource(R.drawable.ic_loop_paused)
+ apsModeSetA11yLabel(R.string.suspendloop_label)
+ binding.infoLayout.apsModeText.text = dateUtil.age(loop.minutesToEndOfSuspend() * 60000L, true, rh)
+ binding.infoLayout.apsModeText.visibility = View.VISIBLE
+ }
+
+ pump.isSuspended() -> {
+ binding.infoLayout.apsMode.setImageResource(
+ if (pump.model() == PumpType.OMNIPOD_EROS || pump.model() == PumpType.OMNIPOD_DASH) {
+ // For Omnipod, indicate the pump as disconnected when it's suspended.
+ // The only way to 'reconnect' it, is through the Omnipod tab
+ apsModeSetA11yLabel(R.string.disconnected)
+ R.drawable.ic_loop_disconnected
+ } else {
+ apsModeSetA11yLabel(R.string.pump_paused)
+ R.drawable.ic_loop_paused
+ }
+ )
+ binding.infoLayout.apsModeText.visibility = View.GONE
+ }
+
+ (loop as PluginBase).isEnabled() && closedLoopEnabled.value() && loop.isLGS -> {
+ binding.infoLayout.apsMode.setImageResource(R.drawable.ic_loop_lgs)
+ apsModeSetA11yLabel(R.string.uel_lgs_loop_mode)
+ binding.infoLayout.apsModeText.visibility = View.GONE
+ }
+
+ (loop as PluginBase).isEnabled() && closedLoopEnabled.value() -> {
+ binding.infoLayout.apsMode.setImageResource(R.drawable.ic_loop_closed)
+ apsModeSetA11yLabel(R.string.closedloop)
+ binding.infoLayout.apsModeText.visibility = View.GONE
+ }
+
+ (loop as PluginBase).isEnabled() && !closedLoopEnabled.value() -> {
+ binding.infoLayout.apsMode.setImageResource(R.drawable.ic_loop_open)
+ apsModeSetA11yLabel(R.string.openloop)
+ binding.infoLayout.apsModeText.visibility = View.GONE
+ }
+
+ else -> {
+ binding.infoLayout.apsMode.setImageResource(R.drawable.ic_loop_disabled)
+ apsModeSetA11yLabel(R.string.disabledloop)
+ binding.infoLayout.apsModeText.visibility = View.GONE
+ }
+ }
+ // Show variable sensitivity
+ val request = loop.lastRun?.request
+ if (request is DetermineBasalResultSMB) {
+ val isfMgdl = profile?.getIsfMgdl()
+ val variableSens = request.variableSens
+ if (variableSens != isfMgdl && variableSens != null && isfMgdl != null) {
+ binding.infoLayout.variableSensitivity.text =
+ String.format(
+ Locale.getDefault(), "%1$.1f→%2$.1f",
+ Profile.toUnits(isfMgdl, isfMgdl * Constants.MGDL_TO_MMOLL, profileFunction.getUnits()),
+ Profile.toUnits(variableSens, variableSens * Constants.MGDL_TO_MMOLL, profileFunction.getUnits())
+ )
+ binding.infoLayout.variableSensitivity.visibility = View.VISIBLE
+ } else binding.infoLayout.variableSensitivity.visibility = View.GONE
+ } else binding.infoLayout.variableSensitivity.visibility = View.GONE
+ } else {
+ //nsclient
+ binding.infoLayout.apsMode.visibility = View.GONE
+ binding.infoLayout.apsModeText.visibility = View.GONE
+ binding.infoLayout.timeLayout.visibility = View.VISIBLE
+ }
+
+ // pump status from ns
+ binding.pump.text = nsDeviceStatus.pumpStatus
+ binding.pump.setOnClickListener { activity?.let { OKDialog.show(it, rh.gs(R.string.pump), nsDeviceStatus.extendedPumpStatus) } }
+
+ // OpenAPS status from ns
+ binding.openaps.text = nsDeviceStatus.openApsStatus
+ binding.openaps.setOnClickListener { activity?.let { OKDialog.show(it, rh.gs(R.string.openaps), nsDeviceStatus.extendedOpenApsStatus) } }
+
+ // Uploader status from ns
+ binding.uploader.text = nsDeviceStatus.uploaderStatusSpanned
+ binding.uploader.setOnClickListener { activity?.let { OKDialog.show(it, rh.gs(R.string.uploader), nsDeviceStatus.extendedUploaderStatus) } }
+ }
}
private fun prepareGraphsIfNeeded(numOfGraphs: Int) {
@@ -686,12 +732,12 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
val graph = GraphView(context)
graph.layoutParams =
LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, rh.dpToPx(skinProvider.activeSkin().secondaryGraphHeight)).also { it.setMargins(0, rh.dpToPx(15), 0, rh.dpToPx(10)) }
- graph.gridLabelRenderer?.gridColor = rh.gc(R.color.graphgrid)
+ graph.gridLabelRenderer?.gridColor = rh.gac(context, R.attr.graphGrid)
graph.gridLabelRenderer?.reloadStyles()
graph.gridLabelRenderer?.isHorizontalLabelsVisible = false
graph.gridLabelRenderer?.labelVerticalWidth = axisWidth
graph.gridLabelRenderer?.numVerticalLabels = 3
- graph.viewport.backgroundColor = Color.argb(20, 255, 255, 255) // 8% of gray
+ graph.viewport.backgroundColor = rh.gac(context, R.attr.viewPortBackgroundColor)
relativeLayout.addView(graph)
val label = TextView(context)
@@ -710,11 +756,11 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
var task: Runnable? = null
- private fun scheduleUpdateGUI(from: String) {
+ private fun scheduleUpdateGUI() {
class UpdateRunnable : Runnable {
override fun run() {
- overviewPlugin.refreshLoop(from)
+ refreshAll()
task = null
}
}
@@ -724,112 +770,138 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
}
@SuppressLint("SetTextI18n")
- @Suppress("UNUSED_PARAMETER")
- fun updateBg(from: String) {
+ fun updateBg() {
val units = profileFunction.getUnits()
- binding.infoLayout.bg.text = overviewData.lastBg?.valueToUnitsString(units)
- ?: rh.gs(R.string.notavailable)
- binding.infoLayout.bg.setTextColor(overviewData.lastBgColor)
- binding.infoLayout.arrow.setImageResource(trendCalculator.getTrendArrow(overviewData.lastBg).directionToIcon())
- binding.infoLayout.arrow.setColorFilter(overviewData.lastBgColor)
-
+ val lastBg = overviewData.lastBg
+ val lastBgColor = overviewData.lastBgColor(context)
+ val isActualBg = overviewData.isActualBg
val glucoseStatus = glucoseStatusProvider.glucoseStatusData
- if (glucoseStatus != null) {
- binding.infoLayout.deltaLarge.text = Profile.toSignedUnitsString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units)
- binding.infoLayout.deltaLarge.setTextColor(overviewData.lastBgColor)
- binding.infoLayout.delta.text = Profile.toSignedUnitsString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units)
- binding.infoLayout.avgDelta.text = Profile.toSignedUnitsString(glucoseStatus.shortAvgDelta, glucoseStatus.shortAvgDelta * Constants.MGDL_TO_MMOLL, units)
- binding.infoLayout.longAvgDelta.text = Profile.toSignedUnitsString(glucoseStatus.longAvgDelta, glucoseStatus.longAvgDelta * Constants.MGDL_TO_MMOLL, units)
- } else {
- binding.infoLayout.deltaLarge.text = ""
- binding.infoLayout.delta.text = "Δ " + rh.gs(R.string.notavailable)
- binding.infoLayout.avgDelta.text = ""
- binding.infoLayout.longAvgDelta.text = ""
- }
+ val trendDescription = trendCalculator.getTrendDescription(lastBg)
+ val trendArrow = trendCalculator.getTrendArrow(lastBg)
+ val lastBgDescription = overviewData.lastBgDescription
+ runOnUiThread {
+ _binding ?: return@runOnUiThread
+ binding.infoLayout.bg.text = lastBg?.valueToUnitsString(units)
+ ?: rh.gs(R.string.notavailable)
+ binding.infoLayout.bg.setTextColor(lastBgColor)
+ binding.infoLayout.arrow.setImageResource(trendArrow.directionToIcon())
+ binding.infoLayout.arrow.setColorFilter(lastBgColor)
+ binding.infoLayout.arrow.contentDescription = lastBgDescription + " " + rh.gs(R.string.and) + " " + trendDescription
- // strike through if BG is old
- binding.infoLayout.bg.paintFlags =
- if (!overviewData.isActualBg) binding.infoLayout.bg.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
- else binding.infoLayout.bg.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
- binding.infoLayout.timeAgo.text = dateUtil.minAgo(rh, overviewData.lastBg?.timestamp)
- binding.infoLayout.timeAgoShort.text = "(" + dateUtil.minAgoShort(overviewData.lastBg?.timestamp) + ")"
-
- val qualityIcon = bgQualityCheckPlugin.icon()
- if (qualityIcon != 0) {
- binding.infoLayout.bgQuality.visibility = View.VISIBLE
- binding.infoLayout.bgQuality.setImageResource(qualityIcon)
- binding.infoLayout.bgQuality.setOnClickListener {
- context?.let { context -> OKDialog.show(context, rh.gs(R.string.data_status), bgQualityCheckPlugin.message) }
+ if (glucoseStatus != null) {
+ binding.infoLayout.deltaLarge.text = Profile.toSignedUnitsString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units)
+ binding.infoLayout.deltaLarge.setTextColor(lastBgColor)
+ binding.infoLayout.delta.text = Profile.toSignedUnitsString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units)
+ binding.infoLayout.avgDelta.text = Profile.toSignedUnitsString(glucoseStatus.shortAvgDelta, glucoseStatus.shortAvgDelta * Constants.MGDL_TO_MMOLL, units)
+ binding.infoLayout.longAvgDelta.text = Profile.toSignedUnitsString(glucoseStatus.longAvgDelta, glucoseStatus.longAvgDelta * Constants.MGDL_TO_MMOLL, units)
+ } else {
+ binding.infoLayout.deltaLarge.text = ""
+ binding.infoLayout.delta.text = "Δ " + rh.gs(R.string.notavailable)
+ binding.infoLayout.avgDelta.text = ""
+ binding.infoLayout.longAvgDelta.text = ""
+ }
+
+ // strike through if BG is old
+ binding.infoLayout.bg.paintFlags =
+ if (!isActualBg) binding.infoLayout.bg.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
+ else binding.infoLayout.bg.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
+
+ val outDate = (if (!isActualBg) rh.gs(R.string.a11y_bg_outdated) else "")
+ binding.infoLayout.bg.contentDescription = rh.gs(R.string.a11y_blood_glucose) + " " + binding.infoLayout.bg.text.toString() + " " + lastBgDescription + " " + outDate
+
+ binding.infoLayout.timeAgo.text = dateUtil.minAgo(rh, lastBg?.timestamp)
+ binding.infoLayout.timeAgo.contentDescription = dateUtil.minAgoLong(rh, lastBg?.timestamp)
+ binding.infoLayout.timeAgoShort.text = "(" + dateUtil.minAgoShort(lastBg?.timestamp) + ")"
+
+ val qualityIcon = bgQualityCheckPlugin.icon()
+ if (qualityIcon != 0) {
+ binding.infoLayout.bgQuality.visibility = View.VISIBLE
+ binding.infoLayout.bgQuality.setImageResource(qualityIcon)
+ binding.infoLayout.bgQuality.contentDescription = rh.gs(R.string.a11y_bg_quality) + " " + bgQualityCheckPlugin.stateDescription()
+ binding.infoLayout.bgQuality.setOnClickListener {
+ context?.let { context -> OKDialog.show(context, rh.gs(R.string.data_status), bgQualityCheckPlugin.message) }
+ }
+ } else {
+ binding.infoLayout.bgQuality.visibility = View.GONE
}
- } else {
- binding.infoLayout.bgQuality.visibility = View.GONE
}
}
- @Suppress("UNUSED_PARAMETER")
- fun updateProfile(from: String) {
- val profileBackgroundColor =
- profileFunction.getProfile()?.let {
+ fun updateProfile() {
+ val profile = profileFunction.getProfile()
+ runOnUiThread {
+ _binding ?: return@runOnUiThread
+ val profileBackgroundColor = profile?.let {
if (it is ProfileSealed.EPS) {
if (it.value.originalPercentage != 100 || it.value.originalTimeshift != 0L || it.value.originalDuration != 0L)
- rh.gc(R.color.ribbonWarning)
- else rh.gc(R.color.ribbonDefault)
+ R.attr.ribbonWarningColor
+ else R.attr.ribbonDefaultColor
} else if (it is ProfileSealed.PS) {
- rh.gc(R.color.ribbonDefault)
+ R.attr.ribbonDefaultColor
} else {
- rh.gc(R.color.ribbonDefault)
+ R.attr.ribbonDefaultColor
}
- } ?: rh.gc(R.color.ribbonCritical)
+ } ?: R.attr.ribbonCriticalColor
- val profileTextColor =
- profileFunction.getProfile()?.let {
+ val profileTextColor = profile?.let {
if (it is ProfileSealed.EPS) {
if (it.value.originalPercentage != 100 || it.value.originalTimeshift != 0L || it.value.originalDuration != 0L)
- rh.gc(R.color.ribbonTextWarning)
- else rh.gc(R.color.ribbonTextDefault)
+ R.attr.ribbonTextWarningColor
+ else R.attr.ribbonTextDefaultColor
} else if (it is ProfileSealed.PS) {
- rh.gc(R.color.ribbonTextDefault)
+ R.attr.ribbonTextDefaultColor
} else {
- rh.gc(R.color.ribbonTextDefault)
+ R.attr.ribbonTextDefaultColor
}
- } ?: rh.gc(R.color.ribbonTextDefault)
-
- binding.activeProfile.text = profileFunction.getProfileNameWithRemainingTime()
- binding.activeProfile.setBackgroundColor(profileBackgroundColor)
- binding.activeProfile.setTextColor(profileTextColor)
- }
-
- @Suppress("UNUSED_PARAMETER")
- fun updateTemporaryBasal(from: String) {
- binding.infoLayout.baseBasal.text = overviewData.temporaryBasalText
- binding.infoLayout.baseBasal.setTextColor(overviewData.temporaryBasalColor)
- binding.infoLayout.baseBasalIcon.setImageResource(overviewData.temporaryBasalIcon)
- binding.infoLayout.basalLayout.setOnClickListener {
- activity?.let { OKDialog.show(it, rh.gs(R.string.basal), overviewData.temporaryBasalDialogText) }
+ } ?: R.attr.ribbonTextDefaultColor
+ setRibbon(binding.activeProfile, profileTextColor, profileBackgroundColor, profileFunction.getProfileNameWithRemainingTime())
}
}
- @Suppress("UNUSED_PARAMETER")
- fun updateExtendedBolus(from: String) {
+ private fun updateTemporaryBasal() {
+ val temporaryBasalText = overviewData.temporaryBasalText(iobCobCalculator)
+ val temporaryBasalColor = overviewData.temporaryBasalColor(context, iobCobCalculator)
+ val temporaryBasalIcon = overviewData.temporaryBasalIcon(iobCobCalculator)
+ val temporaryBasalDialogText = overviewData.temporaryBasalDialogText(iobCobCalculator)
+ runOnUiThread {
+ _binding ?: return@runOnUiThread
+ binding.infoLayout.baseBasal.text = temporaryBasalText
+ binding.infoLayout.baseBasal.setTextColor(temporaryBasalColor)
+ binding.infoLayout.baseBasalIcon.setImageResource(temporaryBasalIcon)
+ binding.infoLayout.basalLayout.setOnClickListener { activity?.let { OKDialog.show(it, rh.gs(R.string.basal), temporaryBasalDialogText) } }
+ }
+ }
+
+ private fun updateExtendedBolus() {
val pump = activePlugin.activePump
- binding.infoLayout.extendedBolus.text = overviewData.extendedBolusText
- binding.infoLayout.extendedLayout.setOnClickListener {
- activity?.let { OKDialog.show(it, rh.gs(R.string.extended_bolus), overviewData.extendedBolusDialogText) }
+ val extendedBolus = iobCobCalculator.getExtendedBolus(dateUtil.now())
+ val extendedBolusText = overviewData.extendedBolusText(iobCobCalculator)
+ val extendedBolusDialogText = overviewData.extendedBolusDialogText(iobCobCalculator)
+ runOnUiThread {
+ _binding ?: return@runOnUiThread
+ binding.infoLayout.extendedBolus.text = extendedBolusText
+ binding.infoLayout.extendedLayout.setOnClickListener { activity?.let { OKDialog.show(it, rh.gs(R.string.extended_bolus), extendedBolusDialogText) } }
+ binding.infoLayout.extendedLayout.visibility = (extendedBolus != null && !pump.isFakingTempsByExtendedBoluses).toVisibility()
}
- binding.infoLayout.extendedLayout.visibility = (iobCobCalculator.getExtendedBolus(dateUtil.now()) != null && !pump.isFakingTempsByExtendedBoluses).toVisibility()
}
- @Suppress("UNUSED_PARAMETER")
- fun updateTime(from: String) {
+ fun updateTime() {
+ _binding ?: return
binding.infoLayout.time.text = dateUtil.timeString(dateUtil.now())
// Status lights
- val isPatchPump = activePlugin.activePump.pumpDescription.isPatchPump
+ val pump = activePlugin.activePump
+ val isPatchPump = pump.pumpDescription.isPatchPump
binding.statusLightsLayout.apply {
cannulaOrPatch.setImageResource(if (isPatchPump) R.drawable.ic_patch_pump_outline else R.drawable.ic_cp_age_cannula)
cannulaOrPatch.contentDescription = rh.gs(if (isPatchPump) R.string.statuslights_patch_pump_age else R.string.statuslights_cannula_age)
cannulaOrPatch.scaleX = if (isPatchPump) 1.4f else 2f
cannulaOrPatch.scaleY = cannulaOrPatch.scaleX
insulinAge.visibility = isPatchPump.not().toVisibility()
+ batteryLayout.visibility = (!isPatchPump || pump.pumpDescription.useHardwareLink).toVisibility()
+ pbAge.visibility = (pump.pumpDescription.isBatteryReplaceable || pump.isBatteryChangeLoggingEnabled()).toVisibility()
+ val useBatteryLevel = (pump.model() == PumpType.OMNIPOD_EROS && pump is OmnipodErosPumpPlugin)
+ || (pump.model() != PumpType.ACCU_CHEK_COMBO && pump.model() != PumpType.OMNIPOD_DASH)
+ batteryLevel.visibility = useBatteryLevel.toVisibility()
statusLights.visibility = (sp.getBoolean(R.string.key_show_statuslights, true) || config.NSCLIENT).toVisibility()
}
statusLightHandler.updateStatusLights(
@@ -841,75 +913,99 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
binding.statusLightsLayout.pbAge,
binding.statusLightsLayout.batteryLevel
)
- processButtonsVisibility()
- processAps()
}
- @Suppress("UNUSED_PARAMETER")
- fun updateIobCob(from: String) {
- binding.infoLayout.iob.text = overviewData.iobText
- binding.infoLayout.iobLayout.setOnClickListener {
- activity?.let { OKDialog.show(it, rh.gs(R.string.iob), overviewData.iobDialogText) }
- }
- // cob
- var cobText = overviewData.cobInfo?.displayText(rh, dateUtil, buildHelper.isEngineeringMode()) ?: rh.gs(R.string.value_unavailable_short)
+ fun updateIobCob() {
+ val iobText = overviewData.iobText(iobCobCalculator)
+ val iobDialogText = overviewData.iobDialogText(iobCobCalculator)
+ val displayText = overviewData.cobInfo(iobCobCalculator).displayText(rh, dateUtil, buildHelper.isEngineeringMode())
+ val lastCarbsTime = overviewData.lastCarbsTime
+ runOnUiThread {
+ _binding ?: return@runOnUiThread
+ binding.infoLayout.iob.text = iobText
+ binding.infoLayout.iobLayout.setOnClickListener { activity?.let { OKDialog.show(it, rh.gs(R.string.iob), iobDialogText) } }
+ // cob
+ var cobText = displayText ?: rh.gs(R.string.value_unavailable_short)
- val constraintsProcessed = loop.lastRun?.constraintsProcessed
- val lastRun = loop.lastRun
- if (config.APS && constraintsProcessed != null && lastRun != null) {
- if (constraintsProcessed.carbsReq > 0) {
- //only display carbsreq when carbs have not been entered recently
- if (overviewData.lastCarbsTime < lastRun.lastAPSRun) {
- cobText += " | " + constraintsProcessed.carbsReq + " " + rh.gs(R.string.required)
+ val constraintsProcessed = loop.lastRun?.constraintsProcessed
+ val lastRun = loop.lastRun
+ if (config.APS && constraintsProcessed != null && lastRun != null) {
+ if (constraintsProcessed.carbsReq > 0) {
+ //only display carbsreq when carbs have not been entered recently
+ if (lastCarbsTime < lastRun.lastAPSRun) {
+ cobText += " | " + constraintsProcessed.carbsReq + " " + rh.gs(R.string.required)
+ }
+ if (carbAnimation?.isRunning == false)
+ carbAnimation?.start()
+ } else {
+ carbAnimation?.stop()
+ carbAnimation?.selectDrawable(0)
}
- if (carbAnimation?.isRunning == false)
- carbAnimation?.start()
- } else {
- carbAnimation?.stop()
- carbAnimation?.selectDrawable(0)
}
+ binding.infoLayout.cob.text = cobText
}
- binding.infoLayout.cob.text = cobText
}
@SuppressLint("SetTextI18n")
- @Suppress("UNUSED_PARAMETER")
- fun updateTemporaryTarget(from: String) {
+ fun updateTemporaryTarget() {
val units = profileFunction.getUnits()
- if (overviewData.temporaryTarget?.isInProgress(dateUtil) == false) overviewData.temporaryTarget = null
val tempTarget = overviewData.temporaryTarget
- if (tempTarget != null) {
- binding.tempTarget.setTextColor(rh.gc(R.color.ribbonTextWarning))
- binding.tempTarget.setBackgroundColor(rh.gc(R.color.ribbonWarning))
- binding.tempTarget.text = Profile.toTargetRangeString(tempTarget.lowTarget, tempTarget.highTarget, GlucoseUnit.MGDL, units) + " " + dateUtil.untilString(tempTarget.end, rh)
- } else {
- // If the target is not the same as set in the profile then oref has overridden it
- profileFunction.getProfile()?.let { profile ->
- val targetUsed = loop.lastRun?.constraintsProcessed?.targetBG ?: 0.0
+ runOnUiThread {
+ _binding ?: return@runOnUiThread
+ if (tempTarget != null) {
+ setRibbon(
+ binding.tempTarget,
+ R.attr.ribbonTextWarningColor,
+ R.attr.ribbonWarningColor,
+ Profile.toTargetRangeString(tempTarget.lowTarget, tempTarget.highTarget, GlucoseUnit.MGDL, units) + " " + dateUtil.untilString(tempTarget.end, rh)
+ )
+ } else {
+ // If the target is not the same as set in the profile then oref has overridden it
+ profileFunction.getProfile()?.let { profile ->
+ val targetUsed = loop.lastRun?.constraintsProcessed?.targetBG ?: 0.0
- if (targetUsed != 0.0 && abs(profile.getTargetMgdl() - targetUsed) > 0.01) {
- aapsLogger.debug("Adjusted target. Profile: ${profile.getTargetMgdl()} APS: $targetUsed")
- binding.tempTarget.text = Profile.toTargetRangeString(targetUsed, targetUsed, GlucoseUnit.MGDL, units)
- binding.tempTarget.setTextColor(rh.gc(R.color.ribbonTextWarning))
- binding.tempTarget.setBackgroundColor(rh.gc(R.color.tempTargetBackground))
- } else {
- binding.tempTarget.setTextColor(rh.gc(R.color.ribbonTextDefault))
- binding.tempTarget.setBackgroundColor(rh.gc(R.color.ribbonDefault))
- binding.tempTarget.text = Profile.toTargetRangeString(profile.getTargetLowMgdl(), profile.getTargetHighMgdl(), GlucoseUnit.MGDL, units)
+ if (targetUsed != 0.0 && abs(profile.getTargetMgdl() - targetUsed) > 0.01) {
+ aapsLogger.debug("Adjusted target. Profile: ${profile.getTargetMgdl()} APS: $targetUsed")
+ setRibbon(
+ binding.tempTarget,
+ R.attr.ribbonTextWarningColor,
+ R.attr.tempTargetBackgroundColor,
+ Profile.toTargetRangeString(targetUsed, targetUsed, GlucoseUnit.MGDL, units)
+ )
+ } else {
+ setRibbon(
+ binding.tempTarget,
+ R.attr.ribbonTextDefaultColor,
+ R.attr.ribbonDefaultColor,
+ Profile.toTargetRangeString(profile.getTargetLowMgdl(), profile.getTargetHighMgdl(), GlucoseUnit.MGDL, units)
+ )
+ }
}
}
}
}
- @Suppress("UNUSED_PARAMETER")
- fun updateGraph(from: String) {
+ private fun setRibbon(view: TextView, attrResText: Int, attrResBack: Int, text: String) {
+ with(view) {
+ setText(text)
+ setBackgroundColor(rh.gac(context, attrResBack))
+ setTextColor(rh.gac(context, attrResText))
+ compoundDrawables[0]?.setTint(rh.gac(context, attrResText))
+ }
+ }
+
+ private fun updateGraph() {
+ _binding ?: return
val pump = activePlugin.activePump
val graphData = GraphData(injector, binding.graphsLayout.bgGraph, overviewData)
val menuChartSettings = overviewMenus.setting
graphData.addInRangeArea(overviewData.fromTime, overviewData.endTime, defaultValueHelper.determineLowLine(), defaultValueHelper.determineHighLine())
- graphData.addBgReadings(menuChartSettings[0][OverviewMenus.CharType.PRE.ordinal])
+ graphData.addBgReadings(menuChartSettings[0][OverviewMenus.CharType.PRE.ordinal], context)
if (buildHelper.isDev()) graphData.addBucketedData()
- graphData.addTreatments()
+ graphData.addTreatments(context)
+ graphData.addEps(context, 0.95)
+ if (menuChartSettings[0][OverviewMenus.CharType.TREAT.ordinal])
+ graphData.addTherapyEvents()
if (menuChartSettings[0][OverviewMenus.CharType.ACT.ordinal])
graphData.addActivity(0.8)
if ((pump.pumpDescription.isTempBasalCapable || config.NSCLIENT) && menuChartSettings[0][OverviewMenus.CharType.BAS.ordinal])
@@ -980,13 +1076,14 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
}
}
- @Suppress("UNUSED_PARAMETER")
- fun updateCalcProgress(from: String) {
- binding.graphsLayout.iobCalculationProgress.text = overviewData.calcProgress
+ private fun updateCalcProgress() {
+ _binding ?: return
+ binding.progressBar.progress = overviewData.calcProgressPct
+ binding.progressBar.visibility = (overviewData.calcProgressPct != 100).toVisibility()
}
- @Suppress("UNUSED_PARAMETER")
- fun updateSensitivity(from: String) {
+ private fun updateSensitivity() {
+ _binding ?: return
if (sp.getBoolean(R.string.key_openapsama_useautosens, false) && constraintChecker.isAutosensModeEnabled().value()) {
binding.infoLayout.sensitivityIcon.setImageResource(R.drawable.ic_swap_vert_black_48dp_green)
} else {
@@ -994,20 +1091,20 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
}
binding.infoLayout.sensitivity.text =
- overviewData.lastAutosensData?.let { autosensData ->
+ overviewData.lastAutosensData(iobCobCalculator)?.let { autosensData ->
String.format(Locale.ENGLISH, "%.0f%%", autosensData.autosensResult.ratio * 100)
} ?: ""
}
- @Suppress("UNUSED_PARAMETER")
- fun updatePumpStatus(from: String) {
+ private fun updatePumpStatus() {
+ _binding ?: return
val status = overviewData.pumpStatus
binding.pumpStatus.text = status
binding.pumpStatusLayout.visibility = (status != "").toVisibility()
}
- @Suppress("UNUSED_PARAMETER")
- fun updateNotification(from: String) {
+ private fun updateNotification() {
+ _binding ?: return
binding.notifications.let { notificationStore.updateNotifications(it) }
}
}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewMenus.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewMenus.kt
index d1838947b2..e388adb8bf 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewMenus.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewMenus.kt
@@ -1,22 +1,26 @@
package info.nightscout.androidaps.plugins.general.overview
+import android.content.Context
import android.text.SpannableString
+import android.text.style.BackgroundColorSpan
import android.text.style.ForegroundColorSpan
import android.view.Menu
import android.view.View
import android.widget.ImageButton
-import androidx.annotation.ColorRes
+import androidx.annotation.AttrRes
import androidx.annotation.StringRes
import androidx.appcompat.widget.PopupMenu
import com.google.gson.Gson
import info.nightscout.androidaps.R
import info.nightscout.androidaps.events.EventRefreshOverview
+import info.nightscout.androidaps.events.EventScale
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.interfaces.Loop
-import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.plugins.bus.RxBus
-import info.nightscout.androidaps.utils.buildHelper.BuildHelper
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.utils.FabricPrivacy
+import info.nightscout.androidaps.interfaces.BuildHelper
+import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.sharedPreferences.SP
import javax.inject.Inject
import javax.inject.Singleton
@@ -29,25 +33,28 @@ class OverviewMenus @Inject constructor(
private val rxBus: RxBus,
private val buildHelper: BuildHelper,
private val loop: Loop,
- private val config: Config
+ private val config: Config,
+ private val fabricPrivacy: FabricPrivacy
) {
- enum class CharType(@StringRes val nameId: Int, @ColorRes val colorId: Int, val primary: Boolean, val secondary: Boolean, @StringRes val shortnameId: Int) {
- PRE(R.string.overview_show_predictions, R.color.prediction, primary = true, secondary = false, shortnameId = R.string.prediction_shortname),
- BAS(R.string.overview_show_basals, R.color.basal, primary = true, secondary = false, shortnameId = R.string.basal_shortname),
- ABS(R.string.overview_show_absinsulin, R.color.iob, primary = false, secondary = true, shortnameId = R.string.abs_insulin_shortname),
- IOB(R.string.overview_show_iob, R.color.iob, primary = false, secondary = true, shortnameId = R.string.iob),
- COB(R.string.overview_show_cob, R.color.cob, primary = false, secondary = true, shortnameId = R.string.cob),
- DEV(R.string.overview_show_deviations, R.color.bgi, primary = false, secondary = true, shortnameId = R.string.deviation_shortname),
- BGI(R.string.overview_show_bgi, R.color.bgi, primary = false, secondary = true, shortnameId = R.string.bgi_shortname),
- SEN(R.string.overview_show_sensitivity, R.color.ratio, primary = false, secondary = true, shortnameId = R.string.sensitivity_shortname),
- ACT(R.string.overview_show_activity, R.color.activity, primary = true, secondary = false, shortnameId = R.string.activity_shortname),
- DEVSLOPE(R.string.overview_show_deviationslope, R.color.devslopepos, primary = false, secondary = true, shortnameId = R.string.devslope_shortname)
+ enum class CharType(@StringRes val nameId: Int, @AttrRes val attrId: Int, @AttrRes val attrTextId: Int, val primary: Boolean, val secondary: Boolean, @StringRes val shortnameId: Int) {
+ PRE(R.string.overview_show_predictions, R.attr.predictionColor, R.attr.menuTextColor, primary = true, secondary = false, shortnameId = R.string.prediction_shortname),
+ TREAT(R.string.overview_show_treatments, R.attr.cobColor, R.attr.menuTextColor, primary = true, secondary = false, shortnameId = R.string.treatments_shortname),
+ BAS(R.string.overview_show_basals, R.attr.basal, R.attr.menuTextColor, primary = true, secondary = false, shortnameId = R.string.basal_shortname),
+ ABS(R.string.overview_show_absinsulin, R.attr.iobColor, R.attr.menuTextColor, primary = false, secondary = true, shortnameId = R.string.abs_insulin_shortname),
+ IOB(R.string.overview_show_iob, R.attr.iobColor, R.attr.menuTextColor, primary = false, secondary = true, shortnameId = R.string.iob),
+ COB(R.string.overview_show_cob, R.attr.cobColor, R.attr.menuTextColor, primary = false, secondary = true, shortnameId = R.string.cob),
+ DEV(R.string.overview_show_deviations, R.attr.bgiColor, R.attr.menuTextColor, primary = false, secondary = true, shortnameId = R.string.deviation_shortname),
+ BGI(R.string.overview_show_bgi, R.attr.bgiColor, R.attr.menuTextColor, primary = false, secondary = true, shortnameId = R.string.bgi_shortname),
+ SEN(R.string.overview_show_sensitivity, R.attr.ratioColor, R.attr.menuTextColorInverse, primary = false, secondary = true, shortnameId = R.string.sensitivity_shortname),
+ ACT(R.string.overview_show_activity, R.attr.activityColor, R.attr.menuTextColor, primary = true, secondary = false, shortnameId = R.string.activity_shortname),
+ DEVSLOPE(R.string.overview_show_deviationslope, R.attr.devSlopePosColor, R.attr.menuTextColor, primary = false, secondary = true, shortnameId = R.string.devslope_shortname)
}
companion object {
const val MAX_GRAPHS = 5 // including main
+ const val SCALE_ID = 1001
}
fun enabledTypes(graph: Int): String {
@@ -86,7 +93,7 @@ class OverviewMenus @Inject constructor(
}
}
- fun setupChartMenu(chartButton: ImageButton) {
+ fun setupChartMenu(context: Context, chartButton: ImageButton) {
val settingsCopy = setting
val numOfGraphs = settingsCopy.size // 1 main + x secondary
@@ -98,6 +105,13 @@ class OverviewMenus @Inject constructor(
}
val popup = PopupMenu(v.context, v)
+ popup.menu.addSubMenu(Menu.NONE, SCALE_ID, Menu.NONE, rh.gs(R.string.graph_scale)).also {
+ it.add(Menu.NONE, SCALE_ID + 6, Menu.NONE, "6")
+ it.add(Menu.NONE, SCALE_ID + 12, Menu.NONE, "12")
+ it.add(Menu.NONE, SCALE_ID + 18, Menu.NONE, "18")
+ it.add(Menu.NONE, SCALE_ID + 24, Menu.NONE, "24")
+ }
+
val used = arrayListOf()
for (g in 0 until numOfGraphs) {
@@ -119,8 +133,9 @@ class OverviewMenus @Inject constructor(
if (insert) {
val item = popup.menu.add(Menu.NONE, m.ordinal + 100 * (g + 1), Menu.NONE, rh.gs(m.nameId))
val title = item.title
- val s = SpannableString(title)
- s.setSpan(ForegroundColorSpan(rh.gc(m.colorId)), 0, s.length, 0)
+ val s = SpannableString(" $title ")
+ s.setSpan(ForegroundColorSpan(rh.gac(context, m.attrTextId)), 0, s.length, 0)
+ s.setSpan(BackgroundColorSpan(rh.gac(context, m.attrId)), 0, s.length, 0)
item.title = s
item.isCheckable = true
item.isChecked = settingsCopy[g][m.ordinal]
@@ -135,24 +150,39 @@ class OverviewMenus @Inject constructor(
}
popup.setOnMenuItemClickListener {
- // id < 100 graph header - divider 1, 2, 3 .....
- when {
- it.itemId == numOfGraphs -> {
- // add new empty
- _setting.add(Array(CharType.values().size) { false })
- }
- it.itemId < 100 -> {
- // remove graph
- _setting.removeAt(it.itemId)
- }
- else -> {
- val graphNumber = it.itemId / 100 - 1
- val item = it.itemId % 100
- _setting[graphNumber][item] = !it.isChecked
+ try {
+ // id < 100 graph header - divider 1, 2, 3 .....
+ when {
+ it.itemId == SCALE_ID -> {
+ // do nothing, submenu
+ }
+
+ it.itemId > SCALE_ID && it.itemId < SCALE_ID + 100 -> {
+ val hours = it.itemId - SCALE_ID // 6,12,....
+ rxBus.send(EventScale(hours))
+ }
+
+ it.itemId == numOfGraphs -> {
+ // add new empty
+ _setting.add(Array(CharType.values().size) { false })
+ }
+
+ it.itemId < 100 -> {
+ // remove graph
+ _setting.removeAt(it.itemId)
+ }
+
+ else -> {
+ val graphNumber = it.itemId / 100 - 1
+ val item = it.itemId % 100
+ _setting[graphNumber][item] = !it.isChecked
+ }
}
+ } catch (exception: Exception) {
+ fabricPrivacy.logException(exception)
}
storeGraphConfig()
- setupChartMenu(chartButton)
+ setupChartMenu(context, chartButton)
rxBus.send(EventRefreshOverview("OnMenuItemClickListener", now = true))
return@setOnMenuItemClickListener true
}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewPlugin.kt
index f9c34a49b1..58b2de95d6 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewPlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewPlugin.kt
@@ -4,28 +4,24 @@ import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreference
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R
-import info.nightscout.androidaps.database.AppRepository
-import info.nightscout.androidaps.database.ValueWrapper
-import info.nightscout.androidaps.events.*
+import info.nightscout.androidaps.events.EventPumpStatusChanged
import info.nightscout.androidaps.extensions.*
import info.nightscout.androidaps.interfaces.*
-import info.nightscout.shared.logging.AAPSLogger
-import info.nightscout.shared.logging.LTag
-import info.nightscout.androidaps.plugins.aps.events.EventLoopInvoked
import info.nightscout.androidaps.plugins.bus.RxBus
-import info.nightscout.androidaps.plugins.general.overview.events.*
+import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification
+import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification
+import info.nightscout.androidaps.plugins.general.overview.events.EventUpdateOverviewCalcProgress
+import info.nightscout.androidaps.plugins.general.overview.events.EventUpdateOverviewNotification
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.Scale
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.ScaledDataPoint
import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationStore
-import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventBucketedDataCreated
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress
-import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
-import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
+import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.sharedPreferences.SP
-import io.reactivex.disposables.CompositeDisposable
-import io.reactivex.rxkotlin.plusAssign
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.kotlin.plusAssign
import org.json.JSONObject
import javax.inject.Inject
import javax.inject.Singleton
@@ -41,9 +37,6 @@ class OverviewPlugin @Inject constructor(
private val aapsSchedulers: AapsSchedulers,
rh: ResourceHelper,
private val config: Config,
- private val dateUtil: DateUtil,
- private val iobCobCalculator: IobCobCalculator,
- private val repository: AppRepository,
private val overviewData: OverviewData,
private val overviewMenus: OverviewMenus
) : PluginBase(
@@ -89,62 +82,9 @@ class OverviewPlugin @Inject constructor(
disposable += rxBus
.toObservable(EventIobCalculationProgress::class.java)
.observeOn(aapsSchedulers.io)
- .subscribe({ overviewData.calcProgress = it.progress; overviewBus.send(EventUpdateOverviewCalcProgress("EventIobCalculationProgress")) }, fabricPrivacy::logException)
- disposable += rxBus
- .toObservable(EventTempBasalChange::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({ overviewBus.send(EventUpdateOverviewTemporaryBasal("EventTempBasalChange")) }, fabricPrivacy::logException)
- disposable += rxBus
- .toObservable(EventExtendedBolusChange::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({ overviewBus.send(EventUpdateOverviewExtendedBolus("EventExtendedBolusChange")) }, fabricPrivacy::logException)
- disposable += rxBus
- .toObservable(EventNewBG::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({ loadBg("EventNewBG") }, fabricPrivacy::logException)
- disposable += rxBus
- .toObservable(EventTempTargetChange::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({ loadTemporaryTarget("EventTempTargetChange") }, fabricPrivacy::logException)
- disposable += rxBus
- .toObservable(EventTreatmentChange::class.java)
- .observeOn(aapsSchedulers.io)
.subscribe({
- loadIobCobResults("EventTreatmentChange")
- overviewData.prepareTreatmentsData("EventTreatmentChange")
- overviewBus.send(EventUpdateOverviewGraph("EventTreatmentChange"))
- }, fabricPrivacy::logException)
- disposable += rxBus
- .toObservable(EventTherapyEventChange::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({
- overviewData.prepareTreatmentsData("EventTherapyEventChange")
- overviewBus.send(EventUpdateOverviewGraph("EventTherapyEventChange"))
- }, fabricPrivacy::logException)
- disposable += rxBus
- .toObservable(EventBucketedDataCreated::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({
- overviewData.prepareBucketedData("EventBucketedDataCreated")
- overviewData.prepareBgData("EventBucketedDataCreated")
- overviewBus.send(EventUpdateOverviewGraph("EventBucketedDataCreated"))
- }, fabricPrivacy::logException)
- disposable += rxBus
- .toObservable(EventLoopInvoked::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({ overviewData.preparePredictions("EventLoopInvoked") }, fabricPrivacy::logException)
- disposable += rxBus
- .toObservable(EventEffectiveProfileSwitchChanged::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({
- loadProfile("EventEffectiveProfileSwitchChanged")
- overviewData.prepareBasalData("EventEffectiveProfileSwitchChanged")
- }, fabricPrivacy::logException)
- disposable += rxBus
- .toObservable(EventAutosensCalculationFinished::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({
- if (it.cause !is EventCustomCalculationFinished) refreshLoop("EventAutosensCalculationFinished")
+ overviewData.calcProgressPct = it.pass.finalPercent(it.progressPct)
+ overviewBus.send(EventUpdateOverviewCalcProgress("EventIobCalculationProgress"))
}, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventPumpStatusChanged::class.java)
@@ -152,20 +92,7 @@ class OverviewPlugin @Inject constructor(
.subscribe({
overviewData.pumpStatus = it.getStatus(rh)
}, fabricPrivacy::logException)
- disposable += rxBus
- .toObservable(EventPreferenceChange::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({ event ->
- if (event.isChanged(rh, R.string.key_units)) {
- overviewData.reset()
- overviewData.prepareBucketedData("EventBucketedDataCreated")
- overviewData.prepareBgData("EventBucketedDataCreated")
- overviewBus.send(EventUpdateOverviewGraph("EventBucketedDataCreated"))
- loadAll("EventPreferenceChange")
- }
- }, fabricPrivacy::logException)
- Thread { loadAll("onResume") }.start()
}
override fun onStop() {
@@ -243,79 +170,4 @@ class OverviewPlugin @Inject constructor(
.storeDouble(R.string.key_statuslights_bat_critical, sp, rh)
.storeInt(R.string.key_boluswizard_percentage, sp, rh)
}
-
- @Volatile
- var runningRefresh = false
- override fun refreshLoop(from: String) {
- if (runningRefresh) return
- runningRefresh = true
- overviewBus.send(EventUpdateOverviewNotification(from))
- loadIobCobResults(from)
- overviewBus.send(EventUpdateOverviewProfile(from))
- overviewBus.send(EventUpdateOverviewBg(from))
- overviewBus.send(EventUpdateOverviewTime(from))
- overviewBus.send(EventUpdateOverviewTemporaryBasal(from))
- overviewBus.send(EventUpdateOverviewExtendedBolus(from))
- overviewBus.send(EventUpdateOverviewTemporaryTarget(from))
- loadAsData(from)
- overviewData.preparePredictions(from)
- overviewData.prepareBasalData(from)
- overviewData.prepareTemporaryTargetData(from)
- overviewData.prepareTreatmentsData(from)
- overviewData.prepareIobAutosensData(from)
- overviewBus.send(EventUpdateOverviewGraph(from))
- overviewBus.send(EventUpdateOverviewIobCob(from))
- aapsLogger.debug(LTag.UI, "refreshLoop finished")
- runningRefresh = false
- }
-
- @Suppress("SameParameterValue")
- private fun loadAll(from: String) {
- loadBg(from)
- loadProfile(from)
- loadTemporaryTarget(from)
- loadIobCobResults(from)
- loadAsData(from)
- overviewData.prepareBasalData(from)
- overviewData.prepareTemporaryTargetData(from)
- overviewData.prepareTreatmentsData(from)
-// prepareIobAutosensData(from)
-// preparePredictions(from)
- overviewBus.send(EventUpdateOverviewGraph(from))
- aapsLogger.debug(LTag.UI, "loadAll finished")
- }
-
- private fun loadProfile(from: String) {
- overviewBus.send(EventUpdateOverviewProfile(from))
- }
-
- private fun loadTemporaryTarget(from: String) {
- val tempTarget = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet()
- if (tempTarget is ValueWrapper.Existing) overviewData.temporaryTarget = tempTarget.value
- else overviewData.temporaryTarget = null
- overviewBus.send(EventUpdateOverviewTemporaryTarget(from))
- }
-
- private fun loadAsData(from: String) {
- overviewData.lastAutosensData = iobCobCalculator.ads.getLastAutosensData("Overview", aapsLogger, dateUtil)
- overviewBus.send(EventUpdateOverviewSensitivity(from))
- }
-
- private fun loadBg(from: String) {
- val gvWrapped = repository.getLastGlucoseValueWrapped().blockingGet()
- if (gvWrapped is ValueWrapper.Existing) overviewData.lastBg = gvWrapped.value
- else overviewData.lastBg = null
- overviewBus.send(EventUpdateOverviewBg(from))
- }
-
- private fun loadIobCobResults(from: String) {
- overviewData.bolusIob = iobCobCalculator.calculateIobFromBolus().round()
- overviewData.basalIob = iobCobCalculator.calculateIobFromTempBasalsIncludingConvertedExtended().round()
- overviewData.cobInfo = iobCobCalculator.getCobInfo(true, "Overview COB")
- val lastCarbs = repository.getLastCarbsRecordWrapped().blockingGet()
- overviewData.lastCarbsTime = if (lastCarbs is ValueWrapper.Existing) lastCarbs.value.timestamp else 0L
-
- overviewBus.send(EventUpdateOverviewIobCob(from))
- }
-
}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/StatusLightHandler.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/StatusLightHandler.kt
index ba73fab429..04095b64a8 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/StatusLightHandler.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/StatusLightHandler.kt
@@ -1,7 +1,5 @@
package info.nightscout.androidaps.plugins.general.overview
-import android.graphics.Color
-import android.view.View
import android.widget.TextView
import androidx.annotation.StringRes
import info.nightscout.androidaps.R
@@ -17,7 +15,7 @@ import info.nightscout.androidaps.plugins.pump.omnipod.eros.driver.definition.Om
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.DecimalFormatter
import info.nightscout.androidaps.utils.WarnColors
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import javax.inject.Inject
import javax.inject.Singleton
@@ -42,7 +40,7 @@ class StatusLightHandler @Inject constructor(
handleAge(careportal_cannula_age, TherapyEvent.Type.CANNULA_CHANGE, R.string.key_statuslights_cage_warning, 48.0, R.string.key_statuslights_cage_critical, 72.0)
handleAge(careportal_insulin_age, TherapyEvent.Type.INSULIN_CHANGE, R.string.key_statuslights_iage_warning, 72.0, R.string.key_statuslights_iage_critical, 144.0)
handleAge(careportal_sensor_age, TherapyEvent.Type.SENSOR_CHANGE, R.string.key_statuslights_sage_warning, 216.0, R.string.key_statuslights_sage_critical, 240.0)
- if (pump.pumpDescription.isBatteryReplaceable || (pump is OmnipodErosPumpPlugin && pump.isUseRileyLinkBatteryLevel && pump.isBatteryChangeLoggingEnabled)) {
+ if (pump.pumpDescription.isBatteryReplaceable || pump.isBatteryChangeLoggingEnabled()) {
handleAge(careportal_pb_age, TherapyEvent.Type.PUMP_BATTERY_CHANGE, R.string.key_statuslights_bage_warning, 216.0, R.string.key_statuslights_bage_critical, 240.0)
}
if (!config.NSCLIENT) {
@@ -58,16 +56,16 @@ class StatusLightHandler @Inject constructor(
}
if (!config.NSCLIENT) {
- if (pump.model() == PumpType.OMNIPOD_DASH) {
- // Omnipod Dash does not report its battery level
- careportal_battery_level?.text = rh.gs(R.string.notavailable)
- careportal_battery_level?.setTextColor(Color.WHITE)
- } else if (pump.model() == PumpType.OMNIPOD_EROS && pump is OmnipodErosPumpPlugin) { // instance of check is needed because at startup, pump can still be VirtualPumpPlugin and that will cause a crash because of the class cast below
- // The Omnipod Eros does not report its battery level. However, some RileyLink alternatives do.
- // Depending on the user's configuration, we will either show the battery level reported by the RileyLink or "n/a"
- handleOmnipodErosBatteryLevel(careportal_battery_level, R.string.key_statuslights_bat_critical, 26.0, R.string.key_statuslights_bat_warning, 51.0, pump.batteryLevel.toDouble(), "%", pump.isUseRileyLinkBatteryLevel)
- } else if (pump.model() != PumpType.ACCU_CHEK_COMBO) {
+ // The Omnipod Eros does not report its battery level. However, some RileyLink alternatives do.
+ // Depending on the user's configuration, we will either show the battery level reported by the RileyLink or "n/a"
+ // Pump instance check is needed because at startup, the pump can still be VirtualPumpPlugin and that will cause a crash
+ val erosBatteryLinkAvailable = pump.model() == PumpType.OMNIPOD_EROS && pump is OmnipodErosPumpPlugin && pump.isUseRileyLinkBatteryLevel
+
+ if (pump.model().supportBatteryLevel || erosBatteryLinkAvailable) {
handleLevel(careportal_battery_level, R.string.key_statuslights_bat_critical, 26.0, R.string.key_statuslights_bat_warning, 51.0, pump.batteryLevel.toDouble(), "%")
+ } else {
+ careportal_battery_level?.text = rh.gs(R.string.notavailable)
+ careportal_battery_level?.setTextColor(rh.gac(careportal_battery_level.context, R.attr.defaultTextColor))
}
}
}
@@ -98,19 +96,10 @@ class StatusLightHandler @Inject constructor(
if (level > OmnipodConstants.MAX_RESERVOIR_READING) {
@Suppress("SetTextI18n")
view?.text = " 50+$units"
- view?.setTextColor(Color.WHITE)
+ view?.setTextColor(rh.gac(view.context, R.attr.defaultTextColor))
} else {
handleLevel(view, criticalSetting, criticalDefaultValue, warnSetting, warnDefaultValue, level, units)
}
}
- @Suppress("SameParameterValue")
- private fun handleOmnipodErosBatteryLevel(view: TextView?, criticalSetting: Int, criticalDefaultValue: Double, warnSetting: Int, warnDefaultValue: Double, level: Double, units: String, useRileyLinkBatteryLevel: Boolean) {
- if (useRileyLinkBatteryLevel) {
- handleLevel(view, criticalSetting, criticalDefaultValue, warnSetting, warnDefaultValue, level, units)
- } else {
- view?.text = rh.gs(R.string.notavailable)
- view?.setTextColor(Color.WHITE)
- }
- }
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/activities/QuickWizardListActivity.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/activities/QuickWizardListActivity.kt
index eb56d8067e..7522148982 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/activities/QuickWizardListActivity.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/activities/QuickWizardListActivity.kt
@@ -1,79 +1,123 @@
package info.nightscout.androidaps.plugins.general.overview.activities
+import android.annotation.SuppressLint
import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.Button
-import android.widget.TextView
+import android.util.SparseArray
+import android.view.*
+import androidx.core.util.forEach
import androidx.fragment.app.FragmentManager
+import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import info.nightscout.androidaps.R
-import info.nightscout.androidaps.activities.NoSplashAppCompatActivity
+import info.nightscout.androidaps.activities.DaggerAppCompatActivityWithResult
import info.nightscout.androidaps.databinding.OverviewQuickwizardlistActivityBinding
+import info.nightscout.androidaps.databinding.OverviewQuickwizardlistItemBinding
+import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.plugins.bus.RxBus
+import info.nightscout.androidaps.utils.dragHelpers.ItemTouchHelperAdapter
+import info.nightscout.androidaps.utils.dragHelpers.OnStartDragListener
+import info.nightscout.androidaps.utils.dragHelpers.SimpleItemTouchHelperCallback
import info.nightscout.androidaps.plugins.general.overview.dialogs.EditQuickWizardDialog
import info.nightscout.androidaps.plugins.general.overview.events.EventQuickWizardChange
+import info.nightscout.androidaps.utils.ActionModeHelper
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
-import io.reactivex.rxkotlin.plusAssign
+import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.androidaps.utils.wizard.QuickWizard
-import io.reactivex.disposables.CompositeDisposable
+import info.nightscout.androidaps.utils.wizard.QuickWizardEntry
+import info.nightscout.shared.sharedPreferences.SP
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.kotlin.plusAssign
import javax.inject.Inject
-class QuickWizardListActivity : NoSplashAppCompatActivity() {
+class QuickWizardListActivity : DaggerAppCompatActivityWithResult(), OnStartDragListener {
@Inject lateinit var aapsSchedulers: AapsSchedulers
@Inject lateinit var rxBus: RxBus
@Inject lateinit var fabricPrivacy: FabricPrivacy
@Inject lateinit var quickWizard: QuickWizard
@Inject lateinit var dateUtil: DateUtil
+ @Inject lateinit var sp: SP
private var disposable: CompositeDisposable = CompositeDisposable()
-
+ private lateinit var actionHelper: ActionModeHelper
+ private val itemTouchHelper = ItemTouchHelper(SimpleItemTouchHelperCallback())
private lateinit var binding: OverviewQuickwizardlistActivityBinding
- private inner class RecyclerViewAdapter(var fragmentManager: FragmentManager) : RecyclerView.Adapter() {
+ override fun onStartDrag(viewHolder: RecyclerView.ViewHolder) {
+ itemTouchHelper.startDrag(viewHolder)
+ }
+
+ private inner class RecyclerViewAdapter(var fragmentManager: FragmentManager) : RecyclerView.Adapter(), ItemTouchHelperAdapter {
+
+ private inner class QuickWizardEntryViewHolder(val binding: OverviewQuickwizardlistItemBinding) : RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QuickWizardEntryViewHolder {
- return QuickWizardEntryViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.overview_quickwizardlist_item, parent, false), fragmentManager)
+ val binding = OverviewQuickwizardlistItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ return QuickWizardEntryViewHolder(binding)
}
+ @SuppressLint("ClickableViewAccessibility")
override fun onBindViewHolder(holder: QuickWizardEntryViewHolder, position: Int) {
- holder.from.text = dateUtil.timeString(quickWizard[position].validFromDate())
- holder.to.text = dateUtil.timeString(quickWizard[position].validToDate())
- holder.buttonText.text = quickWizard[position].buttonText()
- holder.carbs.text = rh.gs(R.string.format_carbs, quickWizard[position].carbs())
- }
-
- override fun getItemCount(): Int = quickWizard.size()
-
- private inner class QuickWizardEntryViewHolder(itemView: View, var fragmentManager: FragmentManager) : RecyclerView.ViewHolder(itemView) {
-
- val buttonText: TextView = itemView.findViewById(R.id.overview_quickwizard_item_buttonText)
- val carbs: TextView = itemView.findViewById(R.id.overview_quickwizard_item_carbs)
- val from: TextView = itemView.findViewById(R.id.overview_quickwizard_item_from)
- val to: TextView = itemView.findViewById(R.id.overview_quickwizard_item_to)
- private val editButton: Button = itemView.findViewById(R.id.overview_quickwizard_item_edit_button)
- private val removeButton: Button = itemView.findViewById(R.id.overview_quickwizard_item_remove_button)
-
- init {
- editButton.setOnClickListener {
+ val entry = quickWizard[position]
+ holder.binding.from.text = dateUtil.timeString(entry.validFromDate())
+ holder.binding.to.text = dateUtil.timeString(entry.validToDate())
+ holder.binding.buttonText.text = entry.buttonText()
+ holder.binding.carbs.text = rh.gs(R.string.format_carbs, entry.carbs())
+ if (entry.device() == QuickWizardEntry.DEVICE_ALL) {
+ holder.binding.device.visibility = View.GONE
+ } else {
+ holder.binding.device.visibility = View.VISIBLE
+ holder.binding.device.setImageResource(
+ when (quickWizard[position].device()) {
+ QuickWizardEntry.DEVICE_WATCH -> R.drawable.ic_watch
+ else -> R.drawable.ic_smartphone
+ }
+ )
+ holder.binding.device.contentDescription = when (quickWizard[position].device()) {
+ QuickWizardEntry.DEVICE_WATCH -> rh.gs(R.string.a11y_only_on_watch)
+ else -> rh.gs(R.string.a11y_only_on_phone)
+ }
+ }
+ holder.binding.root.setOnClickListener {
+ if (actionHelper.isNoAction) {
val manager = fragmentManager
val editQuickWizardDialog = EditQuickWizardDialog()
val bundle = Bundle()
- bundle.putInt("position", bindingAdapterPosition)
+ bundle.putInt("position", position)
editQuickWizardDialog.arguments = bundle
editQuickWizardDialog.show(manager, "EditQuickWizardDialog")
- }
- removeButton.setOnClickListener {
- quickWizard.remove(bindingAdapterPosition)
- rxBus.send(EventQuickWizardChange())
+ } else if (actionHelper.isRemoving) {
+ holder.binding.cbRemove.toggle()
+ actionHelper.updateSelection(position, entry, holder.binding.cbRemove.isChecked)
}
}
+ holder.binding.sortHandle.setOnTouchListener { _, event ->
+ if (event.actionMasked == MotionEvent.ACTION_DOWN) {
+ onStartDrag(holder)
+ return@setOnTouchListener true
+ }
+ return@setOnTouchListener false
+ }
+ holder.binding.cbRemove.isChecked = actionHelper.isSelected(position)
+ holder.binding.cbRemove.setOnCheckedChangeListener { _, value ->
+ actionHelper.updateSelection(position, entry, value)
+ }
+ holder.binding.sortHandle.visibility = actionHelper.isSorting.toVisibility()
+ holder.binding.cbRemove.visibility = actionHelper.isRemoving.toVisibility()
}
+
+ override fun getItemCount() = quickWizard.size()
+
+ override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
+ binding.recyclerview.adapter?.notifyItemMoved(fromPosition, toPosition)
+ quickWizard.move(fromPosition, toPosition)
+ return true
+ }
+
+ override fun onDrop() = rxBus.send(EventQuickWizardChange())
}
override fun onCreate(savedInstanceState: Bundle?) {
@@ -81,11 +125,22 @@ class QuickWizardListActivity : NoSplashAppCompatActivity() {
binding = OverviewQuickwizardlistActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
+ actionHelper = ActionModeHelper(rh, this, null)
+ actionHelper.setUpdateListHandler { binding.recyclerview.adapter?.notifyDataSetChanged() }
+ actionHelper.setOnRemoveHandler { removeSelected(it) }
+ actionHelper.enableSort = true
+
+ title = rh.gs(R.string.quickwizard)
+ supportActionBar?.setDisplayHomeAsUpEnabled(true)
+ supportActionBar?.setDisplayShowHomeEnabled(true)
+
binding.recyclerview.setHasFixedSize(true)
binding.recyclerview.layoutManager = LinearLayoutManager(this)
binding.recyclerview.adapter = RecyclerViewAdapter(supportFragmentManager)
+ itemTouchHelper.attachToRecyclerView(binding.recyclerview)
binding.addButton.setOnClickListener {
+ actionHelper.finish()
val manager = supportFragmentManager
val editQuickWizardDialog = EditQuickWizardDialog()
editQuickWizardDialog.show(manager, "EditQuickWizardDialog")
@@ -98,13 +153,50 @@ class QuickWizardListActivity : NoSplashAppCompatActivity() {
.toObservable(EventQuickWizardChange::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({
- val adapter = RecyclerViewAdapter(supportFragmentManager)
- binding.recyclerview.swapAdapter(adapter, false)
- }, fabricPrivacy::logException)
+ val adapter = RecyclerViewAdapter(supportFragmentManager)
+ binding.recyclerview.swapAdapter(adapter, false)
+ }, fabricPrivacy::logException)
}
override fun onPause() {
disposable.clear()
+ actionHelper.finish()
super.onPause()
}
-}
\ No newline at end of file
+
+ private fun removeSelected(selectedItems: SparseArray) {
+ OKDialog.showConfirmation(this, rh.gs(R.string.removerecord), getConfirmationText(selectedItems), Runnable {
+ selectedItems.forEach { _, item ->
+ quickWizard.remove(item.position)
+ rxBus.send(EventQuickWizardChange())
+ }
+ actionHelper.finish()
+ })
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu): Boolean {
+ menuInflater.inflate(R.menu.menu_actions, menu)
+ return super.onCreateOptionsMenu(menu)
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean =
+ when (item.itemId) {
+ android.R.id.home -> {
+ finish()
+ true
+ }
+
+ else -> actionHelper.onOptionsItemSelected(item)
+
+ }
+
+ private fun getConfirmationText(selectedItems: SparseArray): String {
+ if (selectedItems.size() == 1) {
+ val entry = selectedItems.valueAt(0)
+ return "${rh.gs(R.string.remove_button)} ${entry.buttonText()} ${rh.gs(R.string.format_carbs, entry.carbs())}\n" +
+ "${dateUtil.timeString(entry.validFromDate())} - ${dateUtil.timeString(entry.validToDate())}"
+ }
+ return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size())
+ }
+
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/EditQuickWizardDialog.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/EditQuickWizardDialog.kt
index bfe47b2bdf..9341f74dc7 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/EditQuickWizardDialog.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/EditQuickWizardDialog.kt
@@ -1,6 +1,5 @@
package info.nightscout.androidaps.plugins.general.overview.dialogs
-import android.app.TimePickerDialog
import android.os.Bundle
import android.text.format.DateFormat
import android.view.LayoutInflater
@@ -8,7 +7,10 @@ import android.view.View
import android.view.ViewGroup
import android.view.Window
import android.view.WindowManager
+import com.google.android.material.timepicker.MaterialTimePicker
+import com.google.android.material.timepicker.TimeFormat
import dagger.android.support.DaggerDialogFragment
+import info.nightscout.androidaps.R
import info.nightscout.androidaps.databinding.OverviewEditquickwizardDialogBinding
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.androidaps.plugins.bus.RxBus
@@ -21,7 +23,9 @@ import info.nightscout.androidaps.utils.extensions.setEnableForChildren
import info.nightscout.androidaps.utils.extensions.setSelection
import info.nightscout.androidaps.utils.wizard.QuickWizard
import info.nightscout.androidaps.utils.wizard.QuickWizardEntry
+import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONException
+import java.util.*
import javax.inject.Inject
class EditQuickWizardDialog : DaggerDialogFragment(), View.OnClickListener {
@@ -30,20 +34,21 @@ class EditQuickWizardDialog : DaggerDialogFragment(), View.OnClickListener {
@Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var quickWizard: QuickWizard
@Inject lateinit var dateUtil: DateUtil
+ @Inject lateinit var sp: SP
var position = -1
-
var fromSeconds: Int = 0
var toSeconds: Int = 0
private var _binding: OverviewEditquickwizardDialogBinding? = null
- // This property is only valid between onCreateView and
- // onDestroyView.
+ // This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!!
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?): View {
+ 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
@@ -57,6 +62,14 @@ class EditQuickWizardDialog : DaggerDialogFragment(), View.OnClickListener {
position = bundle.getInt("position", -1)
}
val entry = if (position == -1) quickWizard.newEmptyItem() else quickWizard[position]
+ if (sp.getBoolean(R.string.key_wear_control, false)) {
+ binding.deviceLabel.visibility = View.VISIBLE
+ binding.device.visibility = View.VISIBLE
+ } else {
+ binding.deviceLabel.visibility = View.GONE
+ binding.device.visibility = View.GONE
+ }
+
binding.okcancel.ok.setOnClickListener {
try {
entry.storage.put("buttonText", binding.buttonEdit.text.toString())
@@ -66,10 +79,14 @@ class EditQuickWizardDialog : DaggerDialogFragment(), View.OnClickListener {
entry.storage.put("useBG", binding.useBg.selectedItemPosition)
entry.storage.put("useCOB", binding.useCob.selectedItemPosition)
entry.storage.put("useBolusIOB", binding.useBolusIob.selectedItemPosition)
+ entry.storage.put("device", binding.device.selectedItemPosition)
entry.storage.put("useBasalIOB", binding.useBasalIob.selectedItemPosition)
entry.storage.put("useTrend", binding.useTrend.selectedItemPosition)
entry.storage.put("useSuperBolus", binding.useSuperBolus.selectedItemPosition)
entry.storage.put("useTempTarget", binding.useTempTarget.selectedItemPosition)
+ entry.storage.put("usePercentage", binding.usePercentage.selectedItemPosition)
+ val percentage = SafeParse.stringToInt(binding.percentage.text.toString())
+ entry.storage.put("percentage", percentage)
} catch (e: JSONException) {
aapsLogger.error("Unhandled exception", e)
}
@@ -80,38 +97,51 @@ class EditQuickWizardDialog : DaggerDialogFragment(), View.OnClickListener {
}
binding.okcancel.cancel.setOnClickListener { dismiss() }
- // create an OnTimeSetListener
- val fromTimeSetListener = TimePickerDialog.OnTimeSetListener { _, hour, minute ->
- fromSeconds = (T.hours(hour.toLong()).secs() + T.mins(minute.toLong()).secs()).toInt()
- binding.from.text = dateUtil.timeString(dateUtil.secondsOfTheDayToMilliseconds(fromSeconds))
+ binding.from.setOnClickListener {
+ val clockFormat = if (DateFormat.is24HourFormat(context)) TimeFormat.CLOCK_24H else TimeFormat.CLOCK_12H
+ val timePicker = MaterialTimePicker.Builder()
+ .setTimeFormat(clockFormat)
+ .setHour(T.secs(fromSeconds.toLong()).hours().toInt())
+ .setMinute(T.secs((fromSeconds % 3600).toLong()).mins().toInt())
+ .build()
+ timePicker.addOnPositiveButtonClickListener {
+ fromSeconds = (T.hours(timePicker.hour.toLong()).secs() + T.mins(timePicker.minute.toLong()).secs()).toInt()
+ binding.from.text = dateUtil.timeString(dateUtil.secondsOfTheDayToMilliseconds(fromSeconds))
+ }
+ timePicker.show(parentFragmentManager, "event_time_time_picker")
}
- binding.from.setOnClickListener {
- context?.let {
- TimePickerDialog(it, fromTimeSetListener,
- T.secs(fromSeconds.toLong()).hours().toInt(),
- T.secs((fromSeconds % 3600).toLong()).mins().toInt(),
- DateFormat.is24HourFormat(context)
- ).show()
- }
- }
fromSeconds = entry.validFrom()
binding.from.text = dateUtil.timeString(dateUtil.secondsOfTheDayToMilliseconds(fromSeconds))
- val toTimeSetListener = TimePickerDialog.OnTimeSetListener { _, hour, minute ->
- toSeconds = (T.hours(hour.toLong()).secs() + T.mins(minute.toLong()).secs()).toInt()
- binding.to.text = dateUtil.timeString(dateUtil.secondsOfTheDayToMilliseconds(toSeconds))
+ binding.to.setOnClickListener {
+ val clockFormat = if (DateFormat.is24HourFormat(context)) TimeFormat.CLOCK_24H else TimeFormat.CLOCK_12H
+ val timePicker = MaterialTimePicker.Builder()
+ .setTimeFormat(clockFormat)
+ .setHour(T.secs(toSeconds.toLong()).hours().toInt())
+ .setMinute(T.secs((toSeconds % 3600).toLong()).mins().toInt())
+ .build()
+ timePicker.addOnPositiveButtonClickListener {
+ toSeconds = (T.hours(timePicker.hour.toLong()).secs() + T.mins(timePicker.minute.toLong()).secs()).toInt()
+ binding.to.text = dateUtil.timeString(dateUtil.secondsOfTheDayToMilliseconds(toSeconds))
+ }
+ timePicker.show(parentFragmentManager, "event_time_time_picker")
}
- binding.to.setOnClickListener {
- context?.let {
- TimePickerDialog(it, toTimeSetListener,
- T.secs(toSeconds.toLong()).hours().toInt(),
- T.secs((toSeconds % 3600).toLong()).mins().toInt(),
- DateFormat.is24HourFormat(context)
- ).show()
+ fun usePercentage(custom: Boolean) {
+ if (custom) {
+ binding.percentageLabel.visibility = View.VISIBLE
+ binding.percentage.visibility = View.VISIBLE
+ } else {
+ binding.percentageLabel.visibility = View.GONE
+ binding.percentage.visibility = View.GONE
}
}
+
+ binding.usePercentage.setOnCheckedChangeListener { _, checkedId ->
+ usePercentage(checkedId == R.id.use_percentage_custom)
+ }
+
toSeconds = entry.validTo()
binding.to.text = dateUtil.timeString(dateUtil.secondsOfTheDayToMilliseconds(toSeconds))
@@ -122,10 +152,13 @@ class EditQuickWizardDialog : DaggerDialogFragment(), View.OnClickListener {
binding.useCob.setSelection(entry.useCOB())
binding.useBolusIob.setSelection(entry.useBolusIOB())
binding.useBasalIob.setSelection(entry.useBasalIOB())
+ binding.device.setSelection(entry.device())
binding.useTrend.setSelection(entry.useTrend())
binding.useSuperBolus.setSelection(entry.useSuperBolus())
binding.useTempTarget.setSelection(entry.useTempTarget())
-
+ binding.usePercentage.setSelection(entry.usePercentage())
+ usePercentage(entry.usePercentage() == QuickWizardEntry.CUSTOM)
+ binding.percentage.setText(entry.percentage().toString())
binding.useCobYes.setOnClickListener(this)
binding.useCobNo.setOnClickListener(this)
processCob()
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewBg.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewBg.kt
deleted file mode 100644
index 2bbfb7813c..0000000000
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewBg.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package info.nightscout.androidaps.plugins.general.overview.events
-
-import info.nightscout.androidaps.events.Event
-
-class EventUpdateOverviewBg(val from: String) : Event()
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewExtendedBolus.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewExtendedBolus.kt
deleted file mode 100644
index 51b493acf4..0000000000
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewExtendedBolus.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package info.nightscout.androidaps.plugins.general.overview.events
-
-import info.nightscout.androidaps.events.Event
-
-class EventUpdateOverviewExtendedBolus(val from: String) : Event()
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewProfile.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewProfile.kt
deleted file mode 100644
index 6e4bb95490..0000000000
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewProfile.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package info.nightscout.androidaps.plugins.general.overview.events
-
-import info.nightscout.androidaps.events.Event
-
-class EventUpdateOverviewProfile(val from: String) : Event()
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewPumpStatus.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewPumpStatus.kt
deleted file mode 100644
index 30ad6b7507..0000000000
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewPumpStatus.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package info.nightscout.androidaps.plugins.general.overview.events
-
-import info.nightscout.androidaps.events.Event
-
-class EventUpdateOverviewPumpStatus(val from: String) : Event()
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewTemporaryBasal.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewTemporaryBasal.kt
deleted file mode 100644
index 315bf8d651..0000000000
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewTemporaryBasal.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package info.nightscout.androidaps.plugins.general.overview.events
-
-import info.nightscout.androidaps.events.Event
-
-class EventUpdateOverviewTemporaryBasal(val from: String) : Event()
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewTemporaryTarget.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewTemporaryTarget.kt
deleted file mode 100644
index 83cafcd664..0000000000
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewTemporaryTarget.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package info.nightscout.androidaps.plugins.general.overview.events
-
-import info.nightscout.androidaps.events.Event
-
-class EventUpdateOverviewTemporaryTarget(val from: String) : Event()
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewTime.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewTime.kt
deleted file mode 100644
index 5da5b72136..0000000000
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewTime.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package info.nightscout.androidaps.plugins.general.overview.events
-
-import info.nightscout.androidaps.events.Event
-
-class EventUpdateOverviewTime(val from: String) : Event()
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphData/GraphData.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphData/GraphData.kt
index 4f6096fbcf..7b34ba26c3 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphData/GraphData.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphData/GraphData.kt
@@ -1,6 +1,6 @@
package info.nightscout.androidaps.plugins.general.overview.graphData
-import android.graphics.Color
+import android.content.Context
import android.graphics.DashPathEffect
import android.graphics.Paint
import com.jjoe64.graphview.GraphView
@@ -11,14 +11,13 @@ import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R
import info.nightscout.androidaps.interfaces.GlucoseUnit
import info.nightscout.androidaps.interfaces.ProfileFunction
-import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.plugins.general.overview.OverviewData
-import info.nightscout.androidaps.plugins.general.overview.graphExtensions.AreaGraphSeries
-import info.nightscout.androidaps.plugins.general.overview.graphExtensions.DoubleDataPoint
-import info.nightscout.androidaps.plugins.general.overview.graphExtensions.TimeAsXAxisLabelFormatter
+import info.nightscout.androidaps.plugins.general.overview.graphExtensions.*
import info.nightscout.androidaps.utils.DefaultValueHelper
import info.nightscout.androidaps.utils.Round
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.utils.ToastUtils
+import info.nightscout.shared.logging.AAPSLogger
import java.util.*
import javax.inject.Inject
import kotlin.math.abs
@@ -49,13 +48,16 @@ class GraphData(
addSeries(overviewData.bucketedGraphSeries)
}
- fun addBgReadings(addPredictions: Boolean) {
+ fun addBgReadings(addPredictions: Boolean, context: Context?) {
maxY = if (overviewData.bgReadingsArray.isEmpty()) {
if (units == GlucoseUnit.MGDL) 180.0 else 10.0
} else overviewData.maxBgValue
minY = 0.0
addSeries(overviewData.bgReadingGraphSeries)
if (addPredictions) addSeries(overviewData.predictionsGraphSeries)
+ overviewData.bgReadingGraphSeries.setOnDataPointTapListener { _, dataPoint ->
+ if (dataPoint is GlucoseValueDataPoint) ToastUtils.showToastInUiThread(context, dataPoint.label)
+ }
}
fun addInRangeArea(fromTime: Long, toTime: Long, lowLine: Double, highLine: Double) {
@@ -66,26 +68,46 @@ class GraphData(
addSeries(AreaGraphSeries(inRangeAreaDataPoints).also {
it.color = 0
it.isDrawBackground = true
- it.backgroundColor = rh.gc(R.color.inrangebackground)
+ it.backgroundColor = rh.gac(graph.context, R.attr.inRangeBackground)
})
}
fun addBasals() {
- val scale = defaultValueHelper.determineLowLine() / maxY / 1.2
+ overviewData.basalScale.multiplier = 1.0 // get unscaled Y-values for max calculation
+ var maxBasalValue = maxOf(0.1, overviewData.baseBasalGraphSeries.highestValueY, overviewData.tempBasalGraphSeries.highestValueY)
+ maxBasalValue = maxOf(maxBasalValue, overviewData.basalLineGraphSeries.highestValueY, overviewData.absoluteBasalGraphSeries.highestValueY)
addSeries(overviewData.baseBasalGraphSeries)
addSeries(overviewData.tempBasalGraphSeries)
addSeries(overviewData.basalLineGraphSeries)
addSeries(overviewData.absoluteBasalGraphSeries)
- overviewData.basalScale.multiplier = maxY * scale / overviewData.maxBasalValueFound
+ maxY = max(maxY, defaultValueHelper.determineHighLine())
+ val scale = defaultValueHelper.determineLowLine() / maxY / 1.2
+ overviewData.basalScale.multiplier = maxY * scale / maxBasalValue
}
fun addTargetLine() {
addSeries(overviewData.temporaryTargetSeries)
}
- fun addTreatments() {
+ fun addTreatments(context: Context?) {
maxY = maxOf(maxY, overviewData.maxTreatmentsValue)
addSeries(overviewData.treatmentsSeries)
+ overviewData.treatmentsSeries.setOnDataPointTapListener { _, dataPoint ->
+ if (dataPoint is BolusDataPoint) ToastUtils.showToastInUiThread(context, dataPoint.label)
+ }
+ }
+
+ fun addEps(context: Context?, scale: Double) {
+ addSeries(overviewData.epsSeries)
+ overviewData.epsSeries.setOnDataPointTapListener { _, dataPoint ->
+ if (dataPoint is EffectiveProfileSwitchDataPoint) ToastUtils.showToastInUiThread(context, dataPoint.data.originalCustomizedName)
+ }
+ overviewData.epsScale.multiplier = maxY * scale / overviewData.maxEpsValue
+ }
+
+ fun addTherapyEvents() {
+ maxY = maxOf(maxY, overviewData.maxTherapyEventValue)
+ addSeries(overviewData.therapyEventSeries)
}
fun addActivity(scale: Double) {
@@ -196,7 +218,7 @@ class GraphData(
paint.style = Paint.Style.STROKE
paint.strokeWidth = 2f
paint.pathEffect = DashPathEffect(floatArrayOf(10f, 20f), 0f)
- paint.color = Color.WHITE
+ paint.color = rh.gac(graph.context, R.attr.dotLineColor)
})
})
}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/BolusDataPoint.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/BolusDataPoint.kt
index a5f9bbed50..f70199cd44 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/BolusDataPoint.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/BolusDataPoint.kt
@@ -1,15 +1,14 @@
package info.nightscout.androidaps.plugins.general.overview.graphExtensions
-import android.graphics.Color
+import android.content.Context
import info.nightscout.androidaps.core.R
import info.nightscout.androidaps.database.entities.Bolus
import info.nightscout.androidaps.interfaces.ActivePlugin
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.DecimalFormatter
import info.nightscout.androidaps.utils.DefaultValueHelper
-import info.nightscout.androidaps.utils.resources.ResourceHelper
-import javax.inject.Inject
-class BolusDataPoint @Inject constructor(
+class BolusDataPoint(
val data: Bolus,
private val rh: ResourceHelper,
private val activePlugin: ActivePlugin,
@@ -28,15 +27,12 @@ class BolusDataPoint @Inject constructor(
override val shape
get() = if (data.type == Bolus.Type.SMB) PointsWithLabelGraphSeries.Shape.SMB else PointsWithLabelGraphSeries.Shape.BOLUS
- override val color
- get() =
- when {
- data.type == Bolus.Type.SMB -> rh.gc(R.color.tempbasal)
- data.isValid -> Color.CYAN
- else -> rh.gc(android.R.color.holo_red_light)
- }
+ override fun color(context: Context?): Int =
+ if (data.type == Bolus.Type.SMB) rh.gac(context, R.attr.smbColor)
+ else if (data.isValid) rh.gac(context, R.attr.bolusDataPointColor)
+ else rh.gac(context, R.attr.alarmColor)
override fun setY(y: Double) {
yValue = y
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/CarbsDataPoint.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/CarbsDataPoint.kt
index e8dd54b2cf..6933771c62 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/CarbsDataPoint.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/CarbsDataPoint.kt
@@ -1,11 +1,11 @@
package info.nightscout.androidaps.plugins.general.overview.graphExtensions
+import android.content.Context
import info.nightscout.androidaps.core.R
import info.nightscout.androidaps.database.entities.Carbs
-import info.nightscout.androidaps.utils.resources.ResourceHelper
-import javax.inject.Inject
+import info.nightscout.androidaps.interfaces.ResourceHelper
-class CarbsDataPoint @Inject constructor(
+class CarbsDataPoint(
val data: Carbs,
private val rh: ResourceHelper
) : DataPointWithLabelInterface {
@@ -18,7 +18,10 @@ class CarbsDataPoint @Inject constructor(
override val duration = 0L
override val size = 2f
override val shape = PointsWithLabelGraphSeries.Shape.CARBS
- override val color get() = if (data.isValid) rh.gc(R.color.carbs) else rh.gc(android.R.color.holo_red_light)
+
+ override fun color(context: Context?): Int {
+ return if (data.isValid) rh.gac(context, R.attr.cobColor) else rh.gac(context, R.attr.alarmColor)
+ }
override fun setY(y: Double) {
yValue = y
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/EffectiveProfileSwitchDataPoint.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/EffectiveProfileSwitchDataPoint.kt
index 11ebc2aa11..716a0fd0b7 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/EffectiveProfileSwitchDataPoint.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/EffectiveProfileSwitchDataPoint.kt
@@ -1,25 +1,24 @@
package info.nightscout.androidaps.plugins.general.overview.graphExtensions
-import android.graphics.Color
+import android.content.Context
+import info.nightscout.androidaps.core.R
import info.nightscout.androidaps.database.entities.EffectiveProfileSwitch
-import javax.inject.Inject
+import info.nightscout.androidaps.interfaces.ResourceHelper
-class EffectiveProfileSwitchDataPoint @Inject constructor(
- val data: EffectiveProfileSwitch
+class EffectiveProfileSwitchDataPoint(
+ val data: EffectiveProfileSwitch,
+ private val rh: ResourceHelper,
+ private val scale: Scale
) : DataPointWithLabelInterface {
- private var yValue = 0.0
-
override fun getX(): Double = data.timestamp.toDouble()
- override fun getY(): Double = yValue
-
- override fun setY(y: Double) {
- yValue = y
- }
-
- override val label get() = data.originalCustomizedName
+ override fun getY(): Double = scale.transform(data.originalPercentage.toDouble())
+ override fun setY(y: Double) {}
+ override val label get() = if (data.originalPercentage != 100) data.originalPercentage.toString() + "%" else ""
override val duration = 0L
override val shape = PointsWithLabelGraphSeries.Shape.PROFILE
- override val size = 10f
- override val color = Color.CYAN
+ override val size = 2f
+ override fun color(context: Context?): Int {
+ return rh.gac(context, R.attr.profileSwitchColor)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/ExtendedBolusDataPoint.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/ExtendedBolusDataPoint.kt
index 4352f3fb7b..e6388038d8 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/ExtendedBolusDataPoint.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/ExtendedBolusDataPoint.kt
@@ -1,12 +1,14 @@
package info.nightscout.androidaps.plugins.general.overview.graphExtensions
-import android.graphics.Color
+import android.content.Context
+import info.nightscout.androidaps.core.R
import info.nightscout.androidaps.database.entities.ExtendedBolus
import info.nightscout.androidaps.extensions.toStringTotal
-import javax.inject.Inject
+import info.nightscout.androidaps.interfaces.ResourceHelper
-class ExtendedBolusDataPoint @Inject constructor(
- val data: ExtendedBolus
+class ExtendedBolusDataPoint(
+ val data: ExtendedBolus,
+ private val rh: ResourceHelper
) : DataPointWithLabelInterface {
private var yValue = 0.0
@@ -17,7 +19,9 @@ class ExtendedBolusDataPoint @Inject constructor(
override val duration get() = data.duration
override val size = 10f
override val shape = PointsWithLabelGraphSeries.Shape.EXTENDEDBOLUS
- override val color = Color.CYAN
+ override fun color(context: Context?): Int {
+ return rh.gac(context, R.attr.extBolusColor)
+ }
override fun setY(y: Double) {
yValue = y
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/FixedLineGraphSeries.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/FixedLineGraphSeries.java
index 4cfb27edcd..b938d02b45 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/FixedLineGraphSeries.java
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/FixedLineGraphSeries.java
@@ -261,7 +261,7 @@ public class FixedLineGraphSeries extends BaseSeri
//fix: last value not drawn as datapoint. Draw first point here, and then on every step the end values (above)
// float first_X = (float) x + (graphLeft + 1);
// float first_Y = (float) (graphTop - y) + graphHeight;
- //TODO canvas.drawCircle(first_X, first_Y, dataPointsRadius, mPaint);
+ // canvas.drawCircle(first_X, first_Y, dataPointsRadius, mPaint);
}
lastEndY = orgY;
lastEndX = orgX;
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/GlucoseValueDataPoint.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/GlucoseValueDataPoint.kt
index bd5ba6feef..af527dec05 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/GlucoseValueDataPoint.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/GlucoseValueDataPoint.kt
@@ -1,15 +1,16 @@
package info.nightscout.androidaps.plugins.general.overview.graphExtensions
+import android.content.Context
import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.core.R
import info.nightscout.androidaps.database.entities.GlucoseValue
import info.nightscout.androidaps.interfaces.GlucoseUnit
+import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.interfaces.ProfileFunction
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.DefaultValueHelper
-import info.nightscout.androidaps.utils.resources.ResourceHelper
-import javax.inject.Inject
-class GlucoseValueDataPoint @Inject constructor(
+class GlucoseValueDataPoint(
val data: GlucoseValue,
private val defaultValueHelper: DefaultValueHelper,
private val profileFunction: ProfileFunction,
@@ -23,34 +24,32 @@ class GlucoseValueDataPoint @Inject constructor(
override fun getY(): Double = valueToUnits(profileFunction.getUnits())
override fun setY(y: Double) {}
- override val label: String? = null
+ override val label: String = Profile.toCurrentUnitsString(profileFunction, data.value)
override val duration = 0L
override val shape get() = if (isPrediction) PointsWithLabelGraphSeries.Shape.PREDICTION else PointsWithLabelGraphSeries.Shape.BG
override val size = 1f
- override val color: Int
- get() {
- val units = profileFunction.getUnits()
- val lowLine = defaultValueHelper.determineLowLine()
- val highLine = defaultValueHelper.determineHighLine()
- return when {
- isPrediction -> predictionColor
- valueToUnits(units) < lowLine -> rh.gc(R.color.low)
- valueToUnits(units) > highLine -> rh.gc(R.color.high)
- else -> rh.gc(R.color.inrange)
- }
+ override fun color(context: Context?): Int {
+ val units = profileFunction.getUnits()
+ val lowLine = defaultValueHelper.determineLowLine()
+ val highLine = defaultValueHelper.determineHighLine()
+ return when {
+ isPrediction -> predictionColor(context)
+ valueToUnits(units) < lowLine -> rh.gac(context, R.attr.bgLow)
+ valueToUnits(units) > highLine -> rh.gac(context, R.attr.highColor)
+ else -> rh.gac(context, R.attr.bgInRange)
}
+ }
- val predictionColor: Int
- get() {
- return when (data.sourceSensor) {
- GlucoseValue.SourceSensor.IOB_PREDICTION -> rh.gc(R.color.iob)
- GlucoseValue.SourceSensor.COB_PREDICTION -> rh.gc(R.color.cob)
- GlucoseValue.SourceSensor.A_COB_PREDICTION -> -0x7f000001 and rh.gc(R.color.cob)
- GlucoseValue.SourceSensor.UAM_PREDICTION -> rh.gc(R.color.uam)
- GlucoseValue.SourceSensor.ZT_PREDICTION -> rh.gc(R.color.zt)
- else -> R.color.white
- }
+ private fun predictionColor(context: Context?): Int {
+ return when (data.sourceSensor) {
+ GlucoseValue.SourceSensor.IOB_PREDICTION -> rh.gac(context, R.attr.iobColor)
+ GlucoseValue.SourceSensor.COB_PREDICTION -> rh.gac(context, R.attr.cobColor)
+ GlucoseValue.SourceSensor.A_COB_PREDICTION -> -0x7f000001 and rh.gac(context, R.attr.cobColor)
+ GlucoseValue.SourceSensor.UAM_PREDICTION -> rh.gac(context, R.attr.uamColor)
+ GlucoseValue.SourceSensor.ZT_PREDICTION -> rh.gac(context, R.attr.ztColor)
+ else -> rh.gac(context, R.attr.defaultTextColor)
}
+ }
private val isPrediction: Boolean
get() = data.sourceSensor == GlucoseValue.SourceSensor.IOB_PREDICTION ||
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/InMemoryGlucoseValueDataPoint.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/InMemoryGlucoseValueDataPoint.kt
index 4882f73450..712ef22895 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/InMemoryGlucoseValueDataPoint.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/InMemoryGlucoseValueDataPoint.kt
@@ -1,14 +1,14 @@
package info.nightscout.androidaps.plugins.general.overview.graphExtensions
+import android.content.Context
import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.core.R
import info.nightscout.androidaps.data.InMemoryGlucoseValue
import info.nightscout.androidaps.interfaces.GlucoseUnit
import info.nightscout.androidaps.interfaces.ProfileFunction
-import info.nightscout.androidaps.utils.resources.ResourceHelper
-import javax.inject.Inject
+import info.nightscout.androidaps.interfaces.ResourceHelper
-class InMemoryGlucoseValueDataPoint @Inject constructor(
+class InMemoryGlucoseValueDataPoint(
val data: InMemoryGlucoseValue,
private val profileFunction: ProfileFunction,
private val rh: ResourceHelper
@@ -20,9 +20,11 @@ class InMemoryGlucoseValueDataPoint @Inject constructor(
override fun getX(): Double = data.timestamp.toDouble()
override fun getY(): Double = valueToUnits(profileFunction.getUnits())
override fun setY(y: Double) {}
- override val label: String? = null
+ override val label: String = ""
override val duration = 0L
override val shape = PointsWithLabelGraphSeries.Shape.BUCKETED_BG
override val size = 0.3f
- override val color get() = rh.gc(R.color.white)
+ override fun color(context: Context?): Int {
+ return rh.gac(context, R.attr.inMemoryColor)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/ScaledDataPoint.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/ScaledDataPoint.java
deleted file mode 100644
index 8add353517..0000000000
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/ScaledDataPoint.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package info.nightscout.androidaps.plugins.general.overview.graphExtensions;
-
-import com.jjoe64.graphview.series.DataPointInterface;
-
-import java.io.Serializable;
-import java.util.Date;
-
-/**
- * Created by mike on 18.10.2017.
- */
-
-public class ScaledDataPoint implements DataPointInterface, Serializable {
- private static final long serialVersionUID=1428263342645L;
-
- private final double x;
- private final double y;
-
- private final Scale scale;
-
- public ScaledDataPoint(double x, double y, Scale scale) {
- this.x=x;
- this.y=y;
- this.scale = scale;
- }
-
- public ScaledDataPoint(long x, double y, Scale scale) {
- this.x=x;
- this.y=y;
- this.scale = scale;
- }
-
- public ScaledDataPoint(Date x, double y, Scale scale) {
- this.x = x.getTime();
- this.y = y;
- this.scale = scale;
- }
-
- @Override
- public double getX() {
- return x;
- }
-
- @Override
- public double getY() {
- return scale.transform(y);
- }
-
- @Override
- public String toString() {
- return "["+x+"/"+y+"]";
- }
-}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/ScaledDataPoint.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/ScaledDataPoint.kt
new file mode 100644
index 0000000000..43be685ce9
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/ScaledDataPoint.kt
@@ -0,0 +1,26 @@
+package info.nightscout.androidaps.plugins.general.overview.graphExtensions
+
+import com.jjoe64.graphview.series.DataPointInterface
+
+open class ScaledDataPoint : DataPointInterface {
+
+ private val x: Double
+ private val y: Double
+ private val scale: Scale
+
+ constructor(x: Double, y: Double, scale: Scale) {
+ this.x = x
+ this.y = y
+ this.scale = scale
+ }
+
+ constructor(x: Long, y: Double, scale: Scale) {
+ this.x = x.toDouble()
+ this.y = y
+ this.scale = scale
+ }
+
+ override fun getX(): Double = x
+ override fun getY(): Double = scale.transform(y)
+ override fun toString(): String = "[$x/$y]"
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/TherapyEventDataPoint.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/TherapyEventDataPoint.kt
index d6ae2afb0d..af7179b02e 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/TherapyEventDataPoint.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/TherapyEventDataPoint.kt
@@ -1,16 +1,15 @@
package info.nightscout.androidaps.plugins.general.overview.graphExtensions
-import android.graphics.Color
+import android.content.Context
import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.core.R
import info.nightscout.androidaps.database.entities.TherapyEvent
import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.interfaces.ProfileFunction
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.Translator
-import info.nightscout.androidaps.utils.resources.ResourceHelper
-import javax.inject.Inject
-class TherapyEventDataPoint @Inject constructor(
+class TherapyEventDataPoint(
val data: TherapyEvent,
private val rh: ResourceHelper,
private val profileFunction: ProfileFunction,
@@ -44,7 +43,7 @@ class TherapyEventDataPoint @Inject constructor(
yValue = y
}
- override val label get() = if (data.note.isNullOrBlank().not()) data.note else translator.translate(data.type)
+ override val label get() = if (data.note.isNullOrBlank().not()) data.note!! else translator.translate(data.type)
override val duration get() = data.duration
override val shape
get() =
@@ -52,21 +51,21 @@ class TherapyEventDataPoint @Inject constructor(
data.type == TherapyEvent.Type.NS_MBG -> PointsWithLabelGraphSeries.Shape.MBG
data.type == TherapyEvent.Type.FINGER_STICK_BG_VALUE -> PointsWithLabelGraphSeries.Shape.BGCHECK
data.type == TherapyEvent.Type.ANNOUNCEMENT -> PointsWithLabelGraphSeries.Shape.ANNOUNCEMENT
- data.type == TherapyEvent.Type.APS_OFFLINE -> PointsWithLabelGraphSeries.Shape.OPENAPSOFFLINE
+ data.type == TherapyEvent.Type.APS_OFFLINE -> PointsWithLabelGraphSeries.Shape.OPENAPS_OFFLINE
data.type == TherapyEvent.Type.EXERCISE -> PointsWithLabelGraphSeries.Shape.EXERCISE
- duration > 0 -> PointsWithLabelGraphSeries.Shape.GENERALWITHDURATION
+ duration > 0 -> PointsWithLabelGraphSeries.Shape.GENERAL_WITH_DURATION
else -> PointsWithLabelGraphSeries.Shape.GENERAL
}
override val size get() = if (rh.gb(R.bool.isTablet)) 12.0f else 10.0f
- override val color
- get() =
- when (data.type) {
- TherapyEvent.Type.ANNOUNCEMENT -> rh.gc(R.color.notificationAnnouncement)
- TherapyEvent.Type.NS_MBG -> Color.RED
- TherapyEvent.Type.FINGER_STICK_BG_VALUE -> Color.RED
- TherapyEvent.Type.EXERCISE -> Color.BLUE
- TherapyEvent.Type.APS_OFFLINE -> Color.GRAY and -0x7f000001
- else -> Color.GRAY
- }
+ override fun color(context: Context?): Int {
+ return when (data.type) {
+ TherapyEvent.Type.ANNOUNCEMENT -> rh.gac(context, R.attr.notificationAnnouncement)
+ TherapyEvent.Type.NS_MBG -> rh.gac(context, R.attr.therapyEvent_NS_MBG)
+ TherapyEvent.Type.FINGER_STICK_BG_VALUE -> rh.gac(context, R.attr.therapyEvent_FINGER_STICK_BG_VALUE)
+ TherapyEvent.Type.EXERCISE -> rh.gac(context, R.attr.therapyEvent_EXERCISE)
+ TherapyEvent.Type.APS_OFFLINE -> rh.gac(context, R.attr.therapyEvent_APS_OFFLINE) and -0x7f000001
+ else -> rh.gac(context, R.attr.therapyEvent_Default)
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationStore.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationStore.kt
index 5e66ac8260..6fefe0c3ad 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationStore.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationStore.kt
@@ -17,12 +17,12 @@ import info.nightscout.androidaps.databinding.OverviewNotificationItemBinding
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.IconsProvider
import info.nightscout.androidaps.interfaces.NotificationHolder
-import info.nightscout.shared.logging.AAPSLogger
-import info.nightscout.shared.logging.LTag
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.plugins.general.overview.events.EventUpdateOverviewNotification
import info.nightscout.androidaps.services.AlarmSoundServiceHelper
import info.nightscout.androidaps.utils.DateUtil
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import java.util.*
import javax.inject.Inject
@@ -68,7 +68,7 @@ class NotificationStore @Inject constructor(
store.add(n)
if (sp.getBoolean(R.string.key_raise_notifications_as_android_notifications, true) && n !is NotificationWithAction)
raiseSystemNotification(n)
- if (n.soundId != null && n.soundId != 0) alarmSoundServiceHelper.startAlarm(context, n.soundId!!)
+ if (n.soundId != null && n.soundId != 0) alarmSoundServiceHelper.startAlarm(context, n.soundId!!, n.text)
Collections.sort(store, NotificationComparator())
return true
}
@@ -77,7 +77,7 @@ class NotificationStore @Inject constructor(
fun remove(id: Int): Boolean {
for (i in store.indices) {
if (store[i].id == id) {
- if (store[i].soundId != null) alarmSoundServiceHelper.stopService(context)
+ if (store[i].soundId != null) alarmSoundServiceHelper.stopService(context, "Removed " + store[i].text)
aapsLogger.debug(LTag.NOTIFICATION, "Notification removed: " + store[i].text)
store.removeAt(i)
return true
@@ -92,7 +92,7 @@ class NotificationStore @Inject constructor(
while (i < store.size) {
val n = store[i]
if (n.validTo != 0L && n.validTo < System.currentTimeMillis()) {
- if (store[i].soundId != null) alarmSoundServiceHelper.stopService(context)
+ if (store[i].soundId != null) alarmSoundServiceHelper.stopService(context, "Expired " + store[i].text)
aapsLogger.debug(LTag.NOTIFICATION, "Notification expired: " + store[i].text)
store.removeAt(i)
i--
@@ -164,11 +164,11 @@ class NotificationStore @Inject constructor(
@Suppress("SetTextI18n")
holder.binding.text.text = dateUtil.timeString(notification.date) + " " + notification.text
when (notification.level) {
- Notification.URGENT -> holder.binding.cv.setBackgroundColor(rh.gc(R.color.notificationUrgent))
- Notification.NORMAL -> holder.binding.cv.setBackgroundColor(rh.gc(R.color.notificationNormal))
- Notification.LOW -> holder.binding.cv.setBackgroundColor(rh.gc(R.color.notificationLow))
- Notification.INFO -> holder.binding.cv.setBackgroundColor(rh.gc(R.color.notificationInfo))
- Notification.ANNOUNCEMENT -> holder.binding.cv.setBackgroundColor(rh.gc(R.color.notificationAnnouncement))
+ Notification.URGENT -> holder.binding.cv.setBackgroundColor(rh.gac(R.attr.notificationUrgent))
+ Notification.NORMAL -> holder.binding.cv.setBackgroundColor(rh.gac(R.attr.notificationNormal))
+ Notification.LOW -> holder.binding.cv.setBackgroundColor(rh.gac(R.attr.notificationLow))
+ Notification.INFO -> holder.binding.cv.setBackgroundColor(rh.gac(R.attr.notificationInfo))
+ Notification.ANNOUNCEMENT -> holder.binding.cv.setBackgroundColor(rh.gac(R.attr.notificationAnnouncement))
}
}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationWithAction.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationWithAction.kt
index 1c5e520aee..2591bafc7c 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationWithAction.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationWithAction.kt
@@ -8,7 +8,7 @@ import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin
import info.nightscout.androidaps.plugins.general.nsclient.data.NSAlarm
import info.nightscout.androidaps.utils.DefaultValueHelper
import info.nightscout.androidaps.utils.T
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import javax.inject.Inject
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/persistentNotification/DummyService.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/persistentNotification/DummyService.kt
index fe0d90116a..402daed73e 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/persistentNotification/DummyService.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/persistentNotification/DummyService.kt
@@ -13,7 +13,7 @@ import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.rx.AapsSchedulers
-import io.reactivex.disposables.CompositeDisposable
+import io.reactivex.rxjava3.disposables.CompositeDisposable
import javax.inject.Inject
/**
@@ -39,8 +39,6 @@ class DummyService : DaggerService() {
override fun onCreate() {
super.onCreate()
- // TODO: I guess this was moved here in order to adhere to the 5 seconds rule to call "startForeground" after a Service was called as Foreground service?
- // As onCreate() is not called every time a service is started, copied to onStartCommand().
try {
aapsLogger.debug("Starting DummyService with ID ${notificationHolder.notificationID} notification ${notificationHolder.notification}")
startForeground(notificationHolder.notificationID, notificationHolder.notification)
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/persistentNotification/PersistentNotificationPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/persistentNotification/PersistentNotificationPlugin.kt
index 555dba3239..0a4072250b 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/persistentNotification/PersistentNotificationPlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/persistentNotification/PersistentNotificationPlugin.kt
@@ -9,19 +9,22 @@ import androidx.core.app.RemoteInput
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R
-import info.nightscout.androidaps.events.*
+import info.nightscout.androidaps.events.EventAutosensCalculationFinished
+import info.nightscout.androidaps.events.EventInitializationChanged
+import info.nightscout.androidaps.events.EventPreferenceChange
+import info.nightscout.androidaps.events.EventRefreshOverview
import info.nightscout.androidaps.extensions.toStringShort
import info.nightscout.androidaps.extensions.valueToUnitsString
import info.nightscout.androidaps.interfaces.*
-import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProvider
import info.nightscout.androidaps.utils.DecimalFormatter
import info.nightscout.androidaps.utils.FabricPrivacy
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
-import io.reactivex.disposables.CompositeDisposable
-import io.reactivex.rxkotlin.plusAssign
+import info.nightscout.shared.logging.AAPSLogger
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.kotlin.plusAssign
import javax.inject.Inject
import javax.inject.Singleton
@@ -72,26 +75,10 @@ class PersistentNotificationPlugin @Inject constructor(
.toObservable(EventRefreshOverview::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ triggerNotificationUpdate() }, fabricPrivacy::logException)
- disposable += rxBus
- .toObservable(EventExtendedBolusChange::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({ triggerNotificationUpdate() }, fabricPrivacy::logException)
- disposable += rxBus
- .toObservable(EventTempBasalChange::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({ triggerNotificationUpdate() }, fabricPrivacy::logException)
- disposable += rxBus
- .toObservable(EventTreatmentChange::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({ triggerNotificationUpdate() }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventInitializationChanged::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ triggerNotificationUpdate() }, fabricPrivacy::logException)
- disposable += rxBus
- .toObservable(EventEffectiveProfileSwitchChanged::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({ triggerNotificationUpdate() }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventAutosensCalculationFinished::class.java)
.observeOn(aapsSchedulers.io)
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/AuthRequest.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/AuthRequest.kt
index 910bb76618..f4c41c42c4 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/AuthRequest.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/AuthRequest.kt
@@ -11,7 +11,7 @@ import info.nightscout.androidaps.plugins.general.smsCommunicator.otp.OneTimePas
import info.nightscout.androidaps.plugins.general.smsCommunicator.otp.OneTimePasswordValidationResult
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.T
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import javax.inject.Inject
class AuthRequest internal constructor(
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorFragment.kt
index 6f422dad47..666097d017 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorFragment.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorFragment.kt
@@ -11,9 +11,9 @@ import info.nightscout.androidaps.plugins.general.smsCommunicator.events.EventSm
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.HtmlHelper
-import io.reactivex.rxkotlin.plusAssign
+import io.reactivex.rxjava3.kotlin.plusAssign
import info.nightscout.androidaps.utils.rx.AapsSchedulers
-import io.reactivex.disposables.CompositeDisposable
+import io.reactivex.rxjava3.disposables.CompositeDisposable
import java.util.*
import javax.inject.Inject
import kotlin.math.max
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorPlugin.kt
index 4f733dded3..311b1999fa 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorPlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorPlugin.kt
@@ -43,13 +43,13 @@ import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProv
import info.nightscout.androidaps.queue.Callback
import info.nightscout.androidaps.receivers.DataWorker
import info.nightscout.androidaps.utils.*
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.androidaps.utils.textValidator.ValidatingEditTextPreference
import info.nightscout.shared.SafeParse
-import io.reactivex.disposables.CompositeDisposable
-import io.reactivex.rxkotlin.plusAssign
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.kotlin.plusAssign
import org.apache.commons.lang3.StringUtils
import org.joda.time.DateTime
import java.text.Normalizer
@@ -67,7 +67,7 @@ class SmsCommunicatorPlugin @Inject constructor(
injector: HasAndroidInjector,
aapsLogger: AAPSLogger,
rh: ResourceHelper,
- private val smsManager: SmsManager,
+ private val smsManager: SmsManager?,
private val aapsSchedulers: AapsSchedulers,
private val sp: SP,
private val constraintChecker: ConstraintChecker,
@@ -165,7 +165,7 @@ class SmsCommunicatorPlugin @Inject constructor(
override fun updatePreferenceSummary(pref: Preference) {
super.updatePreferenceSummary(pref)
if (pref is EditTextPreference) {
- if (pref.getKey().contains("smscommunicator_allowednumbers") && (pref.text == null || TextUtils.isEmpty(pref.text.trim { it <= ' ' }))) {
+ if (pref.getKey().contains("smscommunicator_allowednumbers") && (TextUtils.isEmpty(pref.text?.trim { it <= ' ' }))) {
pref.setSummary(rh.gs(R.string.smscommunicator_allowednumbers_summary))
}
}
@@ -328,7 +328,7 @@ class SmsCommunicatorPlugin @Inject constructor(
} else if (lastBG != null) {
val agoMilliseconds = dateUtil.now() - lastBG.timestamp
val agoMin = (agoMilliseconds / 60.0 / 1000.0).toInt()
- reply = rh.gs(R.string.sms_lastbg) + " " + lastBG.valueToUnitsString(units) + " " + String.format(rh.gs(R.string.sms_minago), agoMin) + ", "
+ reply = rh.gs(R.string.sms_lastbg) + " " + lastBG.valueToUnitsString(units) + " " + rh.gs(R.string.sms_minago, agoMin) + ", "
}
val glucoseStatus = glucoseStatusProvider.glucoseStatusData
if (glucoseStatus != null) reply += rh.gs(R.string.sms_delta) + " " + Profile.toUnitsString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units) + " " + units + ", "
@@ -348,7 +348,7 @@ class SmsCommunicatorPlugin @Inject constructor(
"DISABLE", "STOP" -> {
if (loop.enabled) {
val passCode = generatePassCode()
- val reply = String.format(rh.gs(R.string.smscommunicator_loopdisablereplywithcode), passCode)
+ val reply = rh.gs(R.string.smscommunicator_loopdisablereplywithcode, passCode)
receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = false) {
override fun run() {
@@ -372,7 +372,7 @@ class SmsCommunicatorPlugin @Inject constructor(
"ENABLE", "START" -> {
if (!loop.enabled) {
val passCode = generatePassCode()
- val reply = String.format(rh.gs(R.string.smscommunicator_loopenablereplywithcode), passCode)
+ val reply = rh.gs(R.string.smscommunicator_loopenablereplywithcode, passCode)
receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = false) {
override fun run() {
@@ -389,7 +389,7 @@ class SmsCommunicatorPlugin @Inject constructor(
"STATUS" -> {
val reply = if (loop.enabled) {
- if (loop.isSuspended) String.format(rh.gs(R.string.loopsuspendedfor), loop.minutesToEndOfSuspend())
+ if (loop.isSuspended) rh.gs(R.string.loopsuspendedfor, loop.minutesToEndOfSuspend())
else rh.gs(R.string.smscommunicator_loopisenabled)
} else
rh.gs(R.string.loopisdisabled)
@@ -399,7 +399,7 @@ class SmsCommunicatorPlugin @Inject constructor(
"RESUME" -> {
val passCode = generatePassCode()
- val reply = String.format(rh.gs(R.string.smscommunicator_loopresumereplywithcode), passCode)
+ val reply = rh.gs(R.string.smscommunicator_loopresumereplywithcode, passCode)
receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = true) {
override fun run() {
@@ -436,7 +436,7 @@ class SmsCommunicatorPlugin @Inject constructor(
return
} else {
val passCode = generatePassCode()
- val reply = String.format(rh.gs(R.string.smscommunicator_suspendreplywithcode), duration, passCode)
+ val reply = rh.gs(R.string.smscommunicator_suspendreplywithcode, duration, passCode)
receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = true, duration) {
override fun run() {
@@ -515,7 +515,7 @@ class SmsCommunicatorPlugin @Inject constructor(
receivedSms.processed = true
} else if ((divided.size == 2) && (divided[1].equals("CONNECT", ignoreCase = true))) {
val passCode = generatePassCode()
- val reply = String.format(rh.gs(R.string.smscommunicator_pumpconnectwithcode), passCode)
+ val reply = rh.gs(R.string.smscommunicator_pumpconnectwithcode, passCode)
receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = true) {
override fun run() {
@@ -548,7 +548,7 @@ class SmsCommunicatorPlugin @Inject constructor(
return
} else {
val passCode = generatePassCode()
- val reply = String.format(rh.gs(R.string.smscommunicator_pumpdisconnectwithcode), duration, passCode)
+ val reply = rh.gs(R.string.smscommunicator_pumpdisconnectwithcode, duration, passCode)
receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = true) {
override fun run() {
@@ -601,7 +601,7 @@ class SmsCommunicatorPlugin @Inject constructor(
if (profile == null) sendSMS(Sms(receivedSms.phoneNumber, rh.gs(R.string.noprofile)))
else {
val passCode = generatePassCode()
- val reply = String.format(rh.gs(R.string.smscommunicator_profilereplywithcode), list[pIndex - 1], percentage, passCode)
+ val reply = rh.gs(R.string.smscommunicator_profilereplywithcode, list[pIndex - 1], percentage, passCode)
receivedSms.processed = true
val finalPercentage = percentage
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = true, list[pIndex - 1] as String, finalPercentage) {
@@ -627,7 +627,7 @@ class SmsCommunicatorPlugin @Inject constructor(
private fun processBASAL(divided: Array, receivedSms: Sms) {
if (divided[1].uppercase(Locale.getDefault()) == "CANCEL" || divided[1].uppercase(Locale.getDefault()) == "STOP") {
val passCode = generatePassCode()
- val reply = String.format(rh.gs(R.string.smscommunicator_basalstopreplywithcode), passCode)
+ val reply = rh.gs(R.string.smscommunicator_basalstopreplywithcode, passCode)
receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = true) {
override fun run() {
@@ -662,14 +662,14 @@ class SmsCommunicatorPlugin @Inject constructor(
else {
tempBasalPct = constraintChecker.applyBasalPercentConstraints(Constraint(tempBasalPct), profile).value()
val passCode = generatePassCode()
- val reply = String.format(rh.gs(R.string.smscommunicator_basalpctreplywithcode), tempBasalPct, duration, passCode)
+ val reply = rh.gs(R.string.smscommunicator_basalpctreplywithcode, tempBasalPct, duration, passCode)
receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = true, tempBasalPct, duration) {
override fun run() {
commandQueue.tempBasalPercent(anInteger(), secondInteger(), true, profile, PumpSync.TemporaryBasalType.NORMAL, object : Callback() {
override fun run() {
if (result.success) {
- var replyText = if (result.isPercent) String.format(rh.gs(R.string.smscommunicator_tempbasalset_percent), result.percent, result.duration) else String.format(rh.gs(R.string.smscommunicator_tempbasalset), result.absolute, result.duration)
+ var replyText = if (result.isPercent) rh.gs(R.string.smscommunicator_tempbasalset_percent, result.percent, result.duration) else rh.gs(R.string.smscommunicator_tempbasalset, result.absolute, result.duration)
replyText += "\n" + activePlugin.activePump.shortStatus(true)
sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, replyText))
if (result.isPercent)
@@ -706,15 +706,15 @@ class SmsCommunicatorPlugin @Inject constructor(
else {
tempBasal = constraintChecker.applyBasalConstraints(Constraint(tempBasal), profile).value()
val passCode = generatePassCode()
- val reply = String.format(rh.gs(R.string.smscommunicator_basalreplywithcode), tempBasal, duration, passCode)
+ val reply = rh.gs(R.string.smscommunicator_basalreplywithcode, tempBasal, duration, passCode)
receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = true, tempBasal, duration) {
override fun run() {
commandQueue.tempBasalAbsolute(aDouble(), secondInteger(), true, profile, PumpSync.TemporaryBasalType.NORMAL, object : Callback() {
override fun run() {
if (result.success) {
- var replyText = if (result.isPercent) String.format(rh.gs(R.string.smscommunicator_tempbasalset_percent), result.percent, result.duration)
- else String.format(rh.gs(R.string.smscommunicator_tempbasalset), result.absolute, result.duration)
+ var replyText = if (result.isPercent) rh.gs(R.string.smscommunicator_tempbasalset_percent, result.percent, result.duration)
+ else rh.gs(R.string.smscommunicator_tempbasalset, result.absolute, result.duration)
replyText += "\n" + activePlugin.activePump.shortStatus(true)
sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, replyText))
if (result.isPercent)
@@ -743,7 +743,7 @@ class SmsCommunicatorPlugin @Inject constructor(
private fun processEXTENDED(divided: Array, receivedSms: Sms) {
if (divided[1].uppercase(Locale.getDefault()) == "CANCEL" || divided[1].uppercase(Locale.getDefault()) == "STOP") {
val passCode = generatePassCode()
- val reply = String.format(rh.gs(R.string.smscommunicator_extendedstopreplywithcode), passCode)
+ val reply = rh.gs(R.string.smscommunicator_extendedstopreplywithcode, passCode)
receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = true) {
override fun run() {
@@ -773,14 +773,14 @@ class SmsCommunicatorPlugin @Inject constructor(
if (extended == 0.0 || duration == 0) sendSMS(Sms(receivedSms.phoneNumber, rh.gs(R.string.wrongformat)))
else {
val passCode = generatePassCode()
- val reply = String.format(rh.gs(R.string.smscommunicator_extendedreplywithcode), extended, duration, passCode)
+ val reply = rh.gs(R.string.smscommunicator_extendedreplywithcode, extended, duration, passCode)
receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = true, extended, duration) {
override fun run() {
commandQueue.extendedBolus(aDouble(), secondInteger(), object : Callback() {
override fun run() {
if (result.success) {
- var replyText = String.format(rh.gs(R.string.smscommunicator_extendedset), aDouble, duration)
+ var replyText = rh.gs(R.string.smscommunicator_extendedset, aDouble, duration)
if (config.APS) replyText += "\n" + rh.gs(R.string.loopsuspended)
replyText += "\n" + activePlugin.activePump.shortStatus(true)
sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, replyText))
@@ -817,9 +817,9 @@ class SmsCommunicatorPlugin @Inject constructor(
} else if (bolus > 0.0) {
val passCode = generatePassCode()
val reply = if (isMeal)
- String.format(rh.gs(R.string.smscommunicator_mealbolusreplywithcode), bolus, passCode)
+ rh.gs(R.string.smscommunicator_mealbolusreplywithcode, bolus, passCode)
else
- String.format(rh.gs(R.string.smscommunicator_bolusreplywithcode), bolus, passCode)
+ rh.gs(R.string.smscommunicator_bolusreplywithcode, bolus, passCode)
receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = true, bolus) {
override fun run() {
@@ -833,9 +833,9 @@ class SmsCommunicatorPlugin @Inject constructor(
override fun run() {
if (resultSuccess) {
var replyText = if (isMeal)
- String.format(rh.gs(R.string.smscommunicator_mealbolusdelivered), resultBolusDelivered)
+ rh.gs(R.string.smscommunicator_mealbolusdelivered, resultBolusDelivered)
else
- String.format(rh.gs(R.string.smscommunicator_bolusdelivered), resultBolusDelivered)
+ rh.gs(R.string.smscommunicator_bolusdelivered, resultBolusDelivered)
replyText += "\n" + activePlugin.activePump.shortStatus(true)
lastRemoteBolusTime = dateUtil.now()
if (isMeal) {
@@ -866,7 +866,7 @@ class SmsCommunicatorPlugin @Inject constructor(
val tt = if (currentProfile.units == GlucoseUnit.MMOL) {
DecimalFormatter.to1Decimal(eatingSoonTT)
} else DecimalFormatter.to0Decimal(eatingSoonTT)
- replyText += "\n" + String.format(rh.gs(R.string.smscommunicator_mealbolusdelivered_tt), tt, eatingSoonTTDuration)
+ replyText += "\n" + rh.gs(R.string.smscommunicator_mealbolusdelivered_tt, tt, eatingSoonTTDuration)
}
}
sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, replyText))
@@ -920,7 +920,7 @@ class SmsCommunicatorPlugin @Inject constructor(
if (grams == 0) sendSMS(Sms(receivedSms.phoneNumber, rh.gs(R.string.wrongformat)))
else {
val passCode = generatePassCode()
- val reply = String.format(rh.gs(R.string.smscommunicator_carbsreplywithcode), grams, dateUtil.timeString(time), passCode)
+ val reply = rh.gs(R.string.smscommunicator_carbsreplywithcode, grams, dateUtil.timeString(time), passCode)
receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = true, grams, time) {
override fun run() {
@@ -930,7 +930,7 @@ class SmsCommunicatorPlugin @Inject constructor(
commandQueue.bolus(detailedBolusInfo, object : Callback() {
override fun run() {
if (result.success) {
- var replyText = String.format(rh.gs(R.string.smscommunicator_carbsset), anInteger)
+ var replyText = rh.gs(R.string.smscommunicator_carbsset, anInteger)
replyText += "\n" + activePlugin.activePump.shortStatus(true)
sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, replyText))
uel.log(Action.CARBS, Sources.SMS, activePlugin.activePump.shortStatus(true) + ": " + rh.gs(R.string.smscommunicator_carbsset, anInteger),
@@ -956,7 +956,7 @@ class SmsCommunicatorPlugin @Inject constructor(
val isStop = divided[1].equals("STOP", ignoreCase = true) || divided[1].equals("CANCEL", ignoreCase = true)
if (isMeal || isActivity || isHypo) {
val passCode = generatePassCode()
- val reply = String.format(rh.gs(R.string.smscommunicator_temptargetwithcode), divided[1].uppercase(Locale.getDefault()), passCode)
+ val reply = rh.gs(R.string.smscommunicator_temptargetwithcode, divided[1].uppercase(Locale.getDefault()), passCode)
receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = false) {
override fun run() {
@@ -966,6 +966,7 @@ class SmsCommunicatorPlugin @Inject constructor(
var keyTarget = 0
var defaultTargetMMOL = 0.0
var defaultTargetMGDL = 0.0
+ var reason = TemporaryTarget.Reason.EATING_SOON
when {
isMeal -> {
keyDuration = R.string.key_eatingsoon_duration
@@ -973,6 +974,7 @@ class SmsCommunicatorPlugin @Inject constructor(
keyTarget = R.string.key_eatingsoon_target
defaultTargetMMOL = Constants.defaultEatingSoonTTmmol
defaultTargetMGDL = Constants.defaultEatingSoonTTmgdl
+ reason = TemporaryTarget.Reason.EATING_SOON
}
isActivity -> {
@@ -981,6 +983,7 @@ class SmsCommunicatorPlugin @Inject constructor(
keyTarget = R.string.key_activity_target
defaultTargetMMOL = Constants.defaultActivityTTmmol
defaultTargetMGDL = Constants.defaultActivityTTmgdl
+ reason = TemporaryTarget.Reason.ACTIVITY
}
isHypo -> {
@@ -989,6 +992,7 @@ class SmsCommunicatorPlugin @Inject constructor(
keyTarget = R.string.key_hypo_target
defaultTargetMMOL = Constants.defaultHypoTTmmol
defaultTargetMGDL = Constants.defaultHypoTTmgdl
+ reason = TemporaryTarget.Reason.HYPOGLYCEMIA
}
}
var ttDuration = sp.getInt(keyDuration, defaultTargetDuration)
@@ -999,7 +1003,7 @@ class SmsCommunicatorPlugin @Inject constructor(
disposable += repository.runTransactionForResult(InsertAndCancelCurrentTemporaryTargetTransaction(
timestamp = dateUtil.now(),
duration = TimeUnit.MINUTES.toMillis(ttDuration.toLong()),
- reason = TemporaryTarget.Reason.EATING_SOON,
+ reason = reason,
lowTarget = Profile.toMgdl(tt, profileFunction.getUnits()),
highTarget = Profile.toMgdl(tt, profileFunction.getUnits())
)).subscribe({ result ->
@@ -1009,7 +1013,7 @@ class SmsCommunicatorPlugin @Inject constructor(
aapsLogger.error(LTag.DATABASE, "Error while saving temporary target", it)
})
val ttString = if (units == GlucoseUnit.MMOL) DecimalFormatter.to1Decimal(tt) else DecimalFormatter.to0Decimal(tt)
- val replyText = String.format(rh.gs(R.string.smscommunicator_tt_set), ttString, ttDuration)
+ val replyText = rh.gs(R.string.smscommunicator_tt_set, ttString, ttDuration)
sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, replyText))
uel.log(Action.TT, Sources.SMS,
ValueWithUnit.fromGlucoseUnit(tt, units.asText),
@@ -1018,7 +1022,7 @@ class SmsCommunicatorPlugin @Inject constructor(
})
} else if (isStop) {
val passCode = generatePassCode()
- val reply = String.format(rh.gs(R.string.smscommunicator_temptargetcancel), passCode)
+ val reply = rh.gs(R.string.smscommunicator_temptargetcancel, passCode)
receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = false) {
override fun run() {
@@ -1028,7 +1032,7 @@ class SmsCommunicatorPlugin @Inject constructor(
}, {
aapsLogger.error(LTag.DATABASE, "Error while saving temporary target", it)
})
- val replyText = String.format(rh.gs(R.string.smscommunicator_tt_canceled))
+ val replyText = rh.gs(R.string.smscommunicator_tt_canceled)
sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, replyText))
uel.log(Action.CANCEL_TT, Sources.SMS, rh.gs(R.string.smscommunicator_tt_canceled),
ValueWithUnit.SimpleString(rh.gsNotLocalised(R.string.smscommunicator_tt_canceled)))
@@ -1043,12 +1047,12 @@ class SmsCommunicatorPlugin @Inject constructor(
|| divided[1].equals("DISABLE", ignoreCase = true))
if (isStop) {
val passCode = generatePassCode()
- val reply = String.format(rh.gs(R.string.smscommunicator_stopsmswithcode), passCode)
+ val reply = rh.gs(R.string.smscommunicator_stopsmswithcode, passCode)
receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = false) {
override fun run() {
sp.putBoolean(R.string.key_smscommunicator_remotecommandsallowed, false)
- val replyText = String.format(rh.gs(R.string.smscommunicator_stoppedsms))
+ val replyText = rh.gs(R.string.smscommunicator_stoppedsms)
sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, replyText))
uel.log(Action.STOP_SMS, Sources.SMS, rh.gs(R.string.smscommunicator_stoppedsms),
ValueWithUnit.SimpleString(rh.gsNotLocalised(R.string.smscommunicator_stoppedsms)))
@@ -1061,7 +1065,7 @@ class SmsCommunicatorPlugin @Inject constructor(
val cal = SafeParse.stringToDouble(divided[1])
if (cal > 0.0) {
val passCode = generatePassCode()
- val reply = String.format(rh.gs(R.string.smscommunicator_calibrationreplywithcode), cal, passCode)
+ val reply = rh.gs(R.string.smscommunicator_calibrationreplywithcode, cal, passCode)
receivedSms.processed = true
messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = false, cal) {
override fun run() {
@@ -1100,10 +1104,10 @@ class SmsCommunicatorPlugin @Inject constructor(
sms.text = stripAccents(sms.text)
try {
aapsLogger.debug(LTag.SMS, "Sending SMS to " + sms.phoneNumber + ": " + sms.text)
- if (sms.text.toByteArray().size <= 140) smsManager.sendTextMessage(sms.phoneNumber, null, sms.text, null, null)
+ if (sms.text.toByteArray().size <= 140) smsManager?.sendTextMessage(sms.phoneNumber, null, sms.text, null, null)
else {
- val parts = smsManager.divideMessage(sms.text)
- smsManager.sendMultipartTextMessage(sms.phoneNumber, null, parts,
+ val parts = smsManager?.divideMessage(sms.text)
+ smsManager?.sendMultipartTextMessage(sms.phoneNumber, null, parts,
null, null)
}
messages.add(sms)
@@ -1127,7 +1131,7 @@ class SmsCommunicatorPlugin @Inject constructor(
}
private fun generatePassCode(): String =
- String.format(rh.gs(R.string.smscommunicator_code_from_authenticator_for), otp.name())
+ rh.gs(R.string.smscommunicator_code_from_authenticator_for, otp.name())
private fun stripAccents(str: String): String {
var s = str
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/activities/SmsCommunicatorOtpActivity.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/activities/SmsCommunicatorOtpActivity.kt
index f3f7b150bb..c286fc2c64 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/activities/SmsCommunicatorOtpActivity.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/activities/SmsCommunicatorOtpActivity.kt
@@ -18,7 +18,6 @@ import info.nightscout.androidaps.database.entities.UserEntry.Action
import info.nightscout.androidaps.database.entities.UserEntry.Sources
import info.nightscout.androidaps.databinding.ActivitySmscommunicatorOtpBinding
import info.nightscout.androidaps.logging.UserEntryLogger
-import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin
import info.nightscout.androidaps.plugins.general.smsCommunicator.otp.OneTimePassword
import info.nightscout.androidaps.plugins.general.smsCommunicator.otp.OneTimePasswordValidationResult
@@ -31,7 +30,6 @@ import javax.inject.Inject
class SmsCommunicatorOtpActivity : NoSplashAppCompatActivity() {
@Inject lateinit var fabricPrivacy: FabricPrivacy
- @Inject lateinit var rxBus: RxBus
@Inject lateinit var smsCommunicatorPlugin: SmsCommunicatorPlugin
@Inject lateinit var otp: OneTimePassword
@Inject lateinit var uel: UserEntryLogger
@@ -56,12 +54,14 @@ class SmsCommunicatorOtpActivity : NoSplashAppCompatActivity() {
OneTimePasswordValidationResult.ERROR_WRONG_OTP -> "WRONG OTP"
}
- binding.otpVerifyLabel.setTextColor(when (checkResult) {
- OneTimePasswordValidationResult.OK -> Color.GREEN
- OneTimePasswordValidationResult.ERROR_WRONG_LENGTH -> Color.YELLOW
- OneTimePasswordValidationResult.ERROR_WRONG_PIN -> Color.RED
- OneTimePasswordValidationResult.ERROR_WRONG_OTP -> Color.RED
- })
+ binding.otpVerifyLabel.setTextColor(
+ when (checkResult) {
+ OneTimePasswordValidationResult.OK -> Color.GREEN
+ OneTimePasswordValidationResult.ERROR_WRONG_LENGTH -> Color.YELLOW
+ OneTimePasswordValidationResult.ERROR_WRONG_PIN -> Color.RED
+ OneTimePasswordValidationResult.ERROR_WRONG_OTP -> Color.RED
+ }
+ )
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
@@ -70,7 +70,8 @@ class SmsCommunicatorOtpActivity : NoSplashAppCompatActivity() {
})
binding.otpReset.setOnClickListener {
- OKDialog.showConfirmation(this,
+ OKDialog.showConfirmation(
+ this,
rh.gs(R.string.smscommunicator_otp_reset_title),
rh.gs(R.string.smscommunicator_otp_reset_prompt),
Runnable {
@@ -82,7 +83,8 @@ class SmsCommunicatorOtpActivity : NoSplashAppCompatActivity() {
}
binding.otpProvisioning.setOnLongClickListener {
- OKDialog.showConfirmation(this,
+ OKDialog.showConfirmation(
+ this,
rh.gs(R.string.smscommunicator_otp_export_title),
rh.gs(R.string.smscommunicator_otp_export_prompt),
Runnable {
@@ -119,7 +121,5 @@ class SmsCommunicatorOtpActivity : NoSplashAppCompatActivity() {
} else {
binding.otpProvisioning.visibility = View.GONE
}
-
- binding.otpVerifyEdit.text = binding.otpVerifyEdit.text
}
}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/otp/OneTimePassword.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/otp/OneTimePassword.kt
index 9a8034ac00..b6f97584c9 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/otp/OneTimePassword.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/otp/OneTimePassword.kt
@@ -7,7 +7,7 @@ import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R
import info.nightscout.androidaps.annotations.OpenForTesting
import info.nightscout.androidaps.utils.DateUtil
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import java.net.URLEncoder
import javax.crypto.KeyGenerator
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/themes/ThemeSwitcherPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/themes/ThemeSwitcherPlugin.kt
new file mode 100644
index 0000000000..c02ea27396
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/themes/ThemeSwitcherPlugin.kt
@@ -0,0 +1,61 @@
+package info.nightscout.androidaps.plugins.general.themes
+
+import androidx.appcompat.app.AppCompatDelegate
+import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
+import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES
+import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO
+import dagger.android.HasAndroidInjector
+import info.nightscout.androidaps.R
+import info.nightscout.androidaps.events.EventPreferenceChange
+import info.nightscout.androidaps.events.EventThemeSwitch
+import info.nightscout.androidaps.interfaces.PluginBase
+import info.nightscout.androidaps.interfaces.PluginDescription
+import info.nightscout.androidaps.interfaces.PluginType
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.androidaps.plugins.bus.RxBus
+import info.nightscout.androidaps.interfaces.ResourceHelper
+import info.nightscout.shared.sharedPreferences.SP
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class ThemeSwitcherPlugin @Inject constructor(
+ injector: HasAndroidInjector,
+ aapsLogger: AAPSLogger,
+ rh: ResourceHelper,
+ private val sp: SP,
+ private val rxBus: RxBus,
+) : PluginBase(PluginDescription()
+ .mainType(PluginType.GENERAL)
+ .neverVisible(true)
+ .alwaysEnabled(true)
+ .showInList(false)
+ .pluginName(R.string.dst_plugin_name),
+ aapsLogger, rh, injector
+) {
+
+ private val compositeDisposable = CompositeDisposable()
+
+ override fun onStart() {
+ compositeDisposable.add(rxBus.toObservable(EventPreferenceChange::class.java).subscribe {
+ if (it.isChanged(rh, id = R.string.key_use_dark_mode)) {
+ setThemeMode()
+ rxBus.send(EventThemeSwitch())
+ }
+ })
+ }
+
+ fun setThemeMode() {
+ val mode = when (sp.getString(R.string.key_use_dark_mode, "dark")) {
+ sp.getString(R.string.value_dark_theme, "dark") -> MODE_NIGHT_YES
+ sp.getString(R.string.value_light_theme, "light") -> MODE_NIGHT_NO
+ else -> MODE_NIGHT_FOLLOW_SYSTEM
+ }
+ AppCompatDelegate.setDefaultNightMode(mode)
+ }
+
+ override fun onStop() {
+ compositeDisposable.dispose()
+ }
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolFragment.kt
index 1daf65abc4..db3ba0ba7f 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolFragment.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolFragment.kt
@@ -14,10 +14,10 @@ import info.nightscout.androidaps.plugins.general.tidepool.events.EventTidepoolD
import info.nightscout.androidaps.plugins.general.tidepool.events.EventTidepoolResetData
import info.nightscout.androidaps.plugins.general.tidepool.events.EventTidepoolUpdateGUI
import info.nightscout.androidaps.utils.FabricPrivacy
-import io.reactivex.rxkotlin.plusAssign
+import io.reactivex.rxjava3.kotlin.plusAssign
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.shared.sharedPreferences.SP
-import io.reactivex.disposables.CompositeDisposable
+import io.reactivex.rxjava3.disposables.CompositeDisposable
import javax.inject.Inject
class TidepoolFragment : DaggerFragment() {
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.kt
index 045f638e6a..e6e58e9264 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.kt
@@ -30,11 +30,11 @@ import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.HtmlHelper
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.ToastUtils
-import io.reactivex.rxkotlin.plusAssign
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import io.reactivex.rxjava3.kotlin.plusAssign
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.shared.sharedPreferences.SP
-import io.reactivex.disposables.CompositeDisposable
+import io.reactivex.rxjava3.disposables.CompositeDisposable
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
@@ -68,6 +68,7 @@ class TidepoolPlugin @Inject constructor(
private val listLog = ArrayList()
var textLog: Spanned = HtmlHelper.fromHtml("")
+ @Suppress("RECEIVER_NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS", "NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS", "UNNECESSARY_NOT_NULL_ASSERTION")
override fun onStart() {
super.onStart()
disposable += rxBus
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/Session.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/Session.kt
index 7067ddd073..5fb6b34605 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/Session.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/Session.kt
@@ -16,7 +16,6 @@ class Session(val authHeader: String?,
@Volatile
internal var iterations: Int = 0
-
fun populateHeaders(headers: Headers) {
if (this.token == null) {
this.token = headers.get(sessionTokenHeader)
@@ -39,4 +38,4 @@ class Session(val authHeader: String?,
datasetReply = obj
}
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/TidepoolUploader.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/TidepoolUploader.kt
index 9d1ca14687..45ad50857b 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/TidepoolUploader.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/TidepoolUploader.kt
@@ -18,7 +18,7 @@ import info.nightscout.androidaps.plugins.general.tidepool.messages.UploadReplyM
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/elements/BloodGlucoseElement.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/elements/BloodGlucoseElement.kt
index 2d1f575138..58f209f6dd 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/elements/BloodGlucoseElement.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/elements/BloodGlucoseElement.kt
@@ -3,7 +3,6 @@ package info.nightscout.androidaps.plugins.general.tidepool.elements
import com.google.gson.annotations.Expose
import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.database.entities.TherapyEvent
-import info.nightscout.androidaps.extensions.toConstant
import info.nightscout.androidaps.extensions.toMainUnit
import info.nightscout.androidaps.utils.DateUtil
import java.util.*
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/elements/ProfileElement.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/elements/ProfileElement.kt
index c4f47e5aa3..12c7b55fd7 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/elements/ProfileElement.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/elements/ProfileElement.kt
@@ -95,5 +95,3 @@ class ProfileElement(ps: EffectiveProfileSwitch, serialNumber: String, dateUtil:
internal var amount: Double
)
}
-
-
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/events/EventTidepoolResetData.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/events/EventTidepoolResetData.kt
index 833353e6b0..8ea3460801 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/events/EventTidepoolResetData.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/events/EventTidepoolResetData.kt
@@ -2,4 +2,4 @@ package info.nightscout.androidaps.plugins.general.tidepool.events
import info.nightscout.androidaps.events.Event
-class EventTidepoolResetData :Event()
\ No newline at end of file
+class EventTidepoolResetData : Event()
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/messages/DatasetReplyMessage.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/messages/DatasetReplyMessage.kt
index f19070624b..9db4595c16 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/messages/DatasetReplyMessage.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/messages/DatasetReplyMessage.kt
@@ -1,6 +1,5 @@
package info.nightscout.androidaps.plugins.general.tidepool.messages
-
class DatasetReplyMessage {
internal var data: Data? = null
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
deleted file mode 100644
index f463a10339..0000000000
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/ActionStringHandler.kt
+++ /dev/null
@@ -1,668 +0,0 @@
-package info.nightscout.androidaps.plugins.general.wear
-
-import android.app.NotificationManager
-import android.content.Context
-import dagger.android.HasAndroidInjector
-import info.nightscout.androidaps.Constants
-import info.nightscout.androidaps.R
-import info.nightscout.androidaps.dana.DanaPump
-import info.nightscout.androidaps.danaRKorean.DanaRKoreanPlugin
-import info.nightscout.androidaps.danaRv2.DanaRv2Plugin
-import info.nightscout.androidaps.danar.DanaRPlugin
-import info.nightscout.androidaps.danars.DanaRSPlugin
-import info.nightscout.androidaps.data.DetailedBolusInfo
-import info.nightscout.androidaps.database.AppRepository
-import info.nightscout.androidaps.database.ValueWrapper
-import info.nightscout.androidaps.database.entities.TemporaryTarget
-import info.nightscout.androidaps.database.entities.TotalDailyDose
-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.interfaces.end
-import info.nightscout.androidaps.database.transactions.CancelCurrentTemporaryTargetIfAnyTransaction
-import info.nightscout.androidaps.database.transactions.InsertAndCancelCurrentTemporaryTargetTransaction
-import info.nightscout.androidaps.extensions.total
-import info.nightscout.androidaps.extensions.valueToUnits
-import info.nightscout.androidaps.interfaces.*
-import info.nightscout.shared.logging.AAPSLogger
-import info.nightscout.shared.logging.LTag
-import info.nightscout.androidaps.logging.UserEntryLogger
-import info.nightscout.androidaps.plugins.bus.RxBus
-import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
-import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification
-import info.nightscout.androidaps.plugins.general.wear.events.EventWearConfirmAction
-import info.nightscout.androidaps.plugins.general.wear.events.EventWearInitiateAction
-import info.nightscout.androidaps.plugins.pump.insight.LocalInsightPlugin
-import info.nightscout.androidaps.queue.Callback
-import info.nightscout.androidaps.utils.*
-import info.nightscout.androidaps.utils.resources.ResourceHelper
-import info.nightscout.androidaps.utils.rx.AapsSchedulers
-import info.nightscout.shared.sharedPreferences.SP
-import info.nightscout.androidaps.utils.wizard.BolusWizard
-import info.nightscout.shared.SafeParse
-import io.reactivex.disposables.CompositeDisposable
-import io.reactivex.rxkotlin.plusAssign
-import java.text.DateFormat
-import java.text.DecimalFormat
-import java.text.SimpleDateFormat
-import java.util.*
-import java.util.concurrent.TimeUnit
-import javax.inject.Inject
-import javax.inject.Singleton
-import kotlin.math.abs
-import kotlin.math.min
-
-@Suppress("SpellCheckingInspection")
-@Singleton
-class ActionStringHandler @Inject constructor(
- private val sp: SP,
- private val rxBus: RxBus,
- private val aapsLogger: AAPSLogger,
- private val aapsSchedulers: AapsSchedulers,
- private val rh: ResourceHelper,
- private val injector: HasAndroidInjector,
- private val context: Context,
- private val constraintChecker: ConstraintChecker,
- private val profileFunction: ProfileFunction,
- private val loop: Loop,
- private val wearPlugin: WearPlugin,
- private val fabricPrivacy: FabricPrivacy,
- private val commandQueue: CommandQueue,
- private val activePlugin: ActivePlugin,
- private val iobCobCalculator: IobCobCalculator,
- private val localInsightPlugin: LocalInsightPlugin,
- private val danaRPlugin: DanaRPlugin,
- private val danaRKoreanPlugin: DanaRKoreanPlugin,
- private val danaRv2Plugin: DanaRv2Plugin,
- private val danaRSPlugin: DanaRSPlugin,
- private val danaPump: DanaPump,
- private val dateUtil: DateUtil,
- private val config: Config,
- private val repository: AppRepository,
- private val uel: UserEntryLogger
-) {
-
- private val timeout = 65 * 1000
- private var lastSentTimestamp: Long = 0
- private var lastConfirmActionString: String? = null
- private var lastBolusWizard: BolusWizard? = null
-
- private val disposable = CompositeDisposable()
-
- fun setup() {
- disposable += rxBus
- .toObservable(EventWearInitiateAction::class.java)
- .observeOn(aapsSchedulers.main)
- .subscribe({ handleInitiate(it.action) }, fabricPrivacy::logException)
-
- disposable += rxBus
- .toObservable(EventWearConfirmAction::class.java)
- .observeOn(aapsSchedulers.main)
- .subscribe({ handleConfirmation(it.action) }, fabricPrivacy::logException)
- }
-
- fun tearDown() {
- disposable.clear()
- }
-
- @Synchronized
- private fun handleInitiate(actionString: String) {
- if (!sp.getBoolean(R.string.key_wear_control, false)) return
- lastBolusWizard = null
- var rTitle = "CONFIRM" //TODO: i18n
- var rMessage = ""
- var rAction = ""
- // do the parsing and check constraints
- val act = actionString.split("\\s+".toRegex()).toTypedArray()
- if ("fillpreset" == act[0]) { ///////////////////////////////////// PRIME/FILL
- val amount: Double = when {
- "1" == act[1] -> sp.getDouble("fill_button1", 0.3)
- "2" == act[1] -> sp.getDouble("fill_button2", 0.0)
- "3" == act[1] -> sp.getDouble("fill_button3", 0.0)
- else -> return
- }
- val insulinAfterConstraints = constraintChecker.applyBolusConstraints(Constraint(amount)).value()
- rMessage += rh.gs(R.string.primefill) + ": " + insulinAfterConstraints + "U"
- if (insulinAfterConstraints - amount != 0.0) rMessage += "\n" + rh.gs(R.string.constraintapllied)
- rAction += "fill $insulinAfterConstraints"
- } else if ("fill" == act[0]) { ////////////////////////////////////////////// PRIME/FILL
- val amount = SafeParse.stringToDouble(act[1])
- val insulinAfterConstraints = constraintChecker.applyBolusConstraints(Constraint(amount)).value()
- rMessage += rh.gs(R.string.primefill) + ": " + insulinAfterConstraints + "U"
- if (insulinAfterConstraints - amount != 0.0) rMessage += "\n" + rh.gs(R.string.constraintapllied)
- rAction += "fill $insulinAfterConstraints"
- } else if ("bolus" == act[0]) { ////////////////////////////////////////////// BOLUS
- val insulin = SafeParse.stringToDouble(act[1])
- val carbs = SafeParse.stringToInt(act[2])
- val insulinAfterConstraints = constraintChecker.applyBolusConstraints(Constraint(insulin)).value()
- val carbsAfterConstraints = constraintChecker.applyCarbsConstraints(Constraint(carbs)).value()
- rMessage += rh.gs(R.string.bolus) + ": " + insulinAfterConstraints + "U\n"
- rMessage += rh.gs(R.string.carbs) + ": " + carbsAfterConstraints + "g"
- if (insulinAfterConstraints - insulin != 0.0 || carbsAfterConstraints - carbs != 0) {
- rMessage += "\n" + rh.gs(R.string.constraintapllied)
- }
- rAction += "bolus $insulinAfterConstraints $carbsAfterConstraints"
- } else if ("temptarget" == act[0]) { ///////////////////////////////////////////////////////// TEMPTARGET
- val isMGDL = java.lang.Boolean.parseBoolean(act[1])
- if (profileFunction.getUnits() == GlucoseUnit.MGDL != isMGDL) {
- sendError("Different units used on watch and phone!")
- return
- }
- val duration = SafeParse.stringToInt(act[2])
- if (duration == 0) {
- rMessage += "Zero-Temp-Target - cancelling running Temp-Targets?"
- rAction = "temptarget true 0 0 0"
- } else {
- var low = SafeParse.stringToDouble(act[3])
- var high = SafeParse.stringToDouble(act[4])
- if (!isMGDL) {
- low *= Constants.MMOLL_TO_MGDL
- high *= Constants.MMOLL_TO_MGDL
- }
- if (low < HardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[0] || low > HardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[1]) {
- sendError("Min-BG out of range!")
- return
- }
- if (high < HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[0] || high > HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[1]) {
- sendError("Max-BG out of range!")
- return
- }
- rMessage += "Temptarget:\nMin: " + act[3] + "\nMax: " + act[4] + "\nDuration: " + act[2]
- rAction = actionString
- }
- } else if ("status" == act[0]) { ////////////////////////////////////////////// STATUS
- rTitle = "STATUS"
- rAction = "statusmessage"
- if ("pump" == act[1]) {
- rTitle += " PUMP"
- rMessage = pumpStatus
- } else if ("loop" == act[1]) {
- rTitle += " LOOP"
- rMessage = "TARGETS:\n$targetsStatus"
- rMessage += "\n\n" + loopStatus
- rMessage += "\n\nOAPS RESULT:\n$oAPSResultStatus"
- }
- } else if ("wizard" == act[0]) {
- sendError("Update APP on Watch!")
- return
- } else if ("wizard2" == act[0]) { ////////////////////////////////////////////// WIZARD
- val carbsBeforeConstraints = SafeParse.stringToInt(act[1])
- val carbsAfterConstraints = constraintChecker.applyCarbsConstraints(Constraint(carbsBeforeConstraints)).value()
- if (carbsAfterConstraints - carbsBeforeConstraints != 0) {
- sendError("Carb constraint violation!")
- return
- }
- val useBG = sp.getBoolean(R.string.key_wearwizard_bg, true)
- val useTT = sp.getBoolean(R.string.key_wearwizard_tt, false)
- val useBolusIOB = sp.getBoolean(R.string.key_wearwizard_bolusiob, true)
- val useBasalIOB = sp.getBoolean(R.string.key_wearwizard_basaliob, true)
- val useCOB = sp.getBoolean(R.string.key_wearwizard_cob, true)
- val useTrend = sp.getBoolean(R.string.key_wearwizard_trend, false)
- val percentage = act[2].toInt()
- val profile = profileFunction.getProfile()
- val profileName = profileFunction.getProfileName()
- if (profile == null) {
- sendError("No profile found!")
- return
- }
- val bgReading = iobCobCalculator.ads.actualBg()
- if (bgReading == null) {
- sendError("No recent BG to base calculation on!")
- return
- }
- val cobInfo = iobCobCalculator.getCobInfo(false, "Wizard wear")
- if (cobInfo.displayCob == null) {
- sendError("Unknown COB! BG reading missing or recent app restart?")
- return
- }
- val format = DecimalFormat("0.00")
- val formatInt = DecimalFormat("0")
- val dbRecord = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet()
- val tempTarget = if (dbRecord is ValueWrapper.Existing) dbRecord.value else null
-
- val bolusWizard = BolusWizard(injector).doCalc(profile, profileName, tempTarget,
- carbsAfterConstraints, if (cobInfo.displayCob != null) cobInfo.displayCob!! else 0.0, bgReading.valueToUnits(profileFunction.getUnits()),
- 0.0, percentage, useBG, useCOB, useBolusIOB, useBasalIOB, false, useTT, useTrend, false)
- if (abs(bolusWizard.insulinAfterConstraints - bolusWizard.calculatedTotalInsulin) >= 0.01) {
- sendError("Insulin constraint violation!" +
- "\nCannot deliver " + format.format(bolusWizard.calculatedTotalInsulin) + "!")
- return
- }
- if (bolusWizard.calculatedTotalInsulin <= 0 && bolusWizard.carbs <= 0) {
- rAction = "info"
- rTitle = "INFO"
- } else {
- rAction = actionString
- }
- rMessage += "Carbs: " + bolusWizard.carbs + "g"
- rMessage += "\nBolus: " + format.format(bolusWizard.calculatedTotalInsulin) + "U"
- rMessage += "\n_____________"
- rMessage += "\nCalc (IC:" + DecimalFormatter.to1Decimal(bolusWizard.ic) + ", " + "ISF:" + DecimalFormatter.to1Decimal(bolusWizard.sens) + "): "
- rMessage += "\nFrom Carbs: " + format.format(bolusWizard.insulinFromCarbs) + "U"
- 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.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"
- }
- lastBolusWizard = bolusWizard
- } else if ("opencpp" == act[0]) {
- val activeProfileSwitch = repository.getEffectiveProfileSwitchActiveAt(dateUtil.now()).blockingGet()
- if (activeProfileSwitch is ValueWrapper.Existing) { // read CPP values
- rTitle = "opencpp"
- rMessage = "opencpp"
- rAction = "opencpp" + " " + activeProfileSwitch.value.originalPercentage + " " + activeProfileSwitch.value.originalTimeshift
- } else {
- sendError("No active profile switch!")
- return
- }
- } else if ("cppset" == act[0]) {
- val activeProfileSwitch = repository.getEffectiveProfileSwitchActiveAt(dateUtil.now()).blockingGet()
- if (activeProfileSwitch is ValueWrapper.Existing) {
- rMessage = "CPP:" + "\n\n" +
- "Timeshift: " + act[1] + "\n" +
- "Percentage: " + act[2] + "%"
- rAction = actionString
- } else { // read CPP values
- sendError("No active profile switch!")
- return
- }
- } else if ("tddstats" == act[0]) {
- val activePump = activePlugin.activePump
- // check if DB up to date
- val dummies: MutableList = LinkedList()
- val historyList = getTDDList(dummies)
- if (isOldData(historyList)) {
- rTitle = "TDD"
- rAction = "statusmessage"
- rMessage = "OLD DATA - "
- //if pump is not busy: try to fetch data
- if (activePump.isBusy()) {
- rMessage += rh.gs(R.string.pumpbusy)
- } else {
- rMessage += "trying to fetch data from pump."
- commandQueue.loadTDDs(object : Callback() {
- override fun run() {
- val dummies1: MutableList = LinkedList()
- val historyList1 = getTDDList(dummies1)
- if (isOldData(historyList1)) {
- sendStatusMessage("TDD: Still old data! Cannot load from pump.\n" + generateTDDMessage(historyList1, dummies1))
- } else {
- sendStatusMessage(generateTDDMessage(historyList1, dummies1))
- }
- }
- })
- }
- } else { // if up to date: prepare, send (check if CPP is activated -> add CPP stats)
- rTitle = "TDD"
- rAction = "statusmessage"
- rMessage = generateTDDMessage(historyList, dummies)
- }
- } else if ("ecarbs" == act[0]) { ////////////////////////////////////////////// ECARBS
- val carbs = SafeParse.stringToInt(act[1])
- val starttime = SafeParse.stringToInt(act[2])
- val duration = SafeParse.stringToInt(act[3])
- val starttimestamp = System.currentTimeMillis() + starttime * 60 * 1000
- val carbsAfterConstraints = constraintChecker.applyCarbsConstraints(Constraint(carbs)).value()
- rMessage += rh.gs(R.string.carbs) + ": " + carbsAfterConstraints + "g"
- rMessage += "\n" + rh.gs(R.string.time) + ": " + dateUtil.timeString(starttimestamp)
- rMessage += "\n" + rh.gs(R.string.duration) + ": " + duration + "h"
- if (carbsAfterConstraints - carbs != 0) {
- rMessage += "\n" + rh.gs(R.string.constraintapllied)
- }
- if (carbsAfterConstraints <= 0) {
- sendError("Carbs = 0! No action taken!")
- return
- }
- rAction += "ecarbs $carbsAfterConstraints $starttimestamp $duration"
- } else if ("changeRequest" == act[0]) { ////////////////////////////////////////////// CHANGE REQUEST
- rTitle = rh.gs(R.string.openloop_newsuggestion)
- rAction = "changeRequest"
- loop.lastRun?.let {
- rMessage += it.constraintsProcessed
- wearPlugin.requestChangeConfirmation(rTitle, rMessage, rAction)
- lastSentTimestamp = System.currentTimeMillis()
- lastConfirmActionString = rAction
- }
- return
- } else if ("cancelChangeRequest" == act[0]) { ////////////////////////////////////////////// CANCEL CHANGE REQUEST NOTIFICATION
- rAction = "cancelChangeRequest"
- wearPlugin.requestNotificationCancel(rAction)
- return
- } else return
- // send result
- wearPlugin.requestActionConfirmation(rTitle, rMessage, rAction)
- lastSentTimestamp = System.currentTimeMillis()
- lastConfirmActionString = rAction
- }
-
- private fun generateTDDMessage(historyList: MutableList, dummies: MutableList): String {
- val profile = profileFunction.getProfile() ?: return "No profile loaded :("
- if (historyList.isEmpty()) {
- return "No history data!"
- }
- val df: DateFormat = SimpleDateFormat("dd.MM.", Locale.getDefault())
- var message = ""
- val refTDD = profile.baseBasalSum() * 2
- val pump = activePlugin.activePump
- if (df.format(Date(historyList[0].timestamp)) == df.format(Date())) {
- val tdd = historyList[0].total
- historyList.removeAt(0)
- message += "Today: " + DecimalFormatter.to2Decimal(tdd) + "U " + (DecimalFormatter.to0Decimal(100 * tdd / refTDD) + "%") + "\n"
- message += "\n"
- } else if (pump is DanaRPlugin) {
- val tdd = danaPump.dailyTotalUnits
- message += "Today: " + DecimalFormatter.to2Decimal(tdd) + "U " + (DecimalFormatter.to0Decimal(100 * tdd / refTDD) + "%") + "\n"
- message += "\n"
- }
- var weighted03 = 0.0
- var weighted05 = 0.0
- var weighted07 = 0.0
- historyList.reverse()
- for ((i, record) in historyList.withIndex()) {
- val tdd = record.total
- if (i == 0) {
- weighted03 = tdd
- weighted05 = tdd
- weighted07 = tdd
- } else {
- weighted07 = weighted07 * 0.3 + tdd * 0.7
- weighted05 = weighted05 * 0.5 + tdd * 0.5
- weighted03 = weighted03 * 0.7 + tdd * 0.3
- }
- }
- message += "weighted:\n"
- message += "0.3: " + DecimalFormatter.to2Decimal(weighted03) + "U " + (DecimalFormatter.to0Decimal(100 * weighted03 / refTDD) + "%") + "\n"
- message += "0.5: " + DecimalFormatter.to2Decimal(weighted05) + "U " + (DecimalFormatter.to0Decimal(100 * weighted05 / refTDD) + "%") + "\n"
- message += "0.7: " + DecimalFormatter.to2Decimal(weighted07) + "U " + (DecimalFormatter.to0Decimal(100 * weighted07 / refTDD) + "%") + "\n"
- message += "\n"
- historyList.reverse()
- //add TDDs:
- for (record in historyList) {
- val tdd = record.total
- message += df.format(Date(record.timestamp)) + " " + DecimalFormatter.to2Decimal(tdd) + "U " + (DecimalFormatter.to0Decimal(100 * tdd / refTDD) + "%") + (if (dummies.contains(record)) "x" else "") + "\n"
- }
- return message
- }
-
- private fun isOldData(historyList: List): Boolean {
- val activePump = activePlugin.activePump
- val startsYesterday = activePump === danaRPlugin || activePump === danaRSPlugin || activePump === danaRv2Plugin || activePump === danaRKoreanPlugin || activePump === localInsightPlugin
- val df: DateFormat = SimpleDateFormat("dd.MM.", Locale.getDefault())
- return historyList.size < 3 || df.format(Date(historyList[0].timestamp)) != df.format(Date(System.currentTimeMillis() - if (startsYesterday) 1000 * 60 * 60 * 24 else 0))
- }
-
- private fun getTDDList(returnDummies: MutableList): MutableList {
- var historyList = repository.getLastTotalDailyDoses(10, false).blockingGet().toMutableList()
- //var historyList = databaseHelper.getTDDs().toMutableList()
- historyList = historyList.subList(0, min(10, historyList.size))
- //fill single gaps - only needed for Dana*R data
- val dummies: MutableList = returnDummies
- val df: DateFormat = SimpleDateFormat("dd.MM.", Locale.getDefault())
- for (i in 0 until historyList.size - 1) {
- val elem1 = historyList[i]
- val elem2 = historyList[i + 1]
- if (df.format(Date(elem1.timestamp)) != df.format(Date(elem2.timestamp + 25 * 60 * 60 * 1000))) {
- val dummy = TotalDailyDose(timestamp = elem1.timestamp - T.hours(24).msecs(), bolusAmount = elem1.bolusAmount / 2, basalAmount = elem1.basalAmount / 2)
- dummies.add(dummy)
- elem1.basalAmount /= 2.0
- elem1.bolusAmount /= 2.0
- }
- }
- historyList.addAll(dummies)
- historyList.sortWith { lhs, rhs -> (rhs.timestamp - lhs.timestamp).toInt() }
- return historyList
- }
-
- private val pumpStatus: String
- get() = activePlugin.activePump.shortStatus(false)
-
- // decide if enabled/disabled closed/open; what Plugin as APS?
- private val loopStatus: String
- get() {
- var ret = ""
- // decide if enabled/disabled closed/open; what Plugin as APS?
- if ((loop as PluginBase).isEnabled()) {
- ret += if (constraintChecker.isClosedLoopAllowed().value()) {
- "CLOSED LOOP\n"
- } else {
- "OPEN LOOP\n"
- }
- val aps = activePlugin.activeAPS
- ret += "APS: " + (aps as PluginBase).name
- val lastRun = loop.lastRun
- if (lastRun != null) {
- ret += "\nLast Run: " + dateUtil.timeString(lastRun.lastAPSRun)
- if (lastRun.lastTBREnact != 0L) ret += "\nLast Enact: " + dateUtil.timeString(lastRun.lastTBREnact)
- }
- } else {
- ret += "LOOP DISABLED\n"
- }
- return ret
- }
-
- //Check for Temp-Target:
- private val targetsStatus: String
- get() {
- var ret = ""
- if (!config.APS) {
- return "Targets only apply in APS mode!"
- }
- val profile = profileFunction.getProfile() ?: return "No profile set :("
- //Check for Temp-Target:
- val tempTarget = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet()
- if (tempTarget is ValueWrapper.Existing) {
- ret += "Temp Target: " + Profile.toTargetRangeString(tempTarget.value.lowTarget, tempTarget.value.lowTarget, GlucoseUnit.MGDL, profileFunction.getUnits())
- ret += "\nuntil: " + dateUtil.timeString(tempTarget.value.end)
- ret += "\n\n"
- }
- ret += "DEFAULT RANGE: "
- ret += Profile.fromMgdlToUnits(profile.getTargetLowMgdl(), profileFunction.getUnits()).toString() + " - " + Profile.fromMgdlToUnits(profile.getTargetHighMgdl(), profileFunction.getUnits())
- ret += " target: " + Profile.fromMgdlToUnits(profile.getTargetMgdl(), profileFunction.getUnits())
- return ret
- }
-
- private val oAPSResultStatus: String
- get() {
- var ret = ""
- if (!config.APS)
- return "Only apply in APS mode!"
- val usedAPS = activePlugin.activeAPS
- val result = usedAPS.lastAPSResult ?: return "Last result not available!"
- ret += if (!result.isChangeRequested) {
- rh.gs(R.string.nochangerequested) + "\n"
- } else if (result.rate == 0.0 && result.duration == 0) {
- rh.gs(R.string.canceltemp) + "\n"
- } else {
- rh.gs(R.string.rate) + ": " + DecimalFormatter.to2Decimal(result.rate) + " U/h " +
- "(" + DecimalFormatter.to2Decimal(result.rate / activePlugin.activePump.baseBasalRate * 100) + "%)\n" +
- rh.gs(R.string.duration) + ": " + DecimalFormatter.to0Decimal(result.duration.toDouble()) + " min\n"
- }
- ret += "\n" + rh.gs(R.string.reason) + ": " + result.reason
- return ret
- }
-
- @Synchronized
- fun handleConfirmation(actionString: String) {
- if (!sp.getBoolean(R.string.key_wear_control, false)) return
- //Guard from old or duplicate confirmations
- if (lastConfirmActionString == null) return
- if (lastConfirmActionString != actionString) return
- if (System.currentTimeMillis() - lastSentTimestamp > timeout) return
- lastConfirmActionString = null
- // do the parsing, check constraints and enact!
- val act = actionString.split("\\s+".toRegex()).toTypedArray()
- if ("fill" == act[0]) {
- val amount = SafeParse.stringToDouble(act[1])
- val insulinAfterConstraints = constraintChecker.applyBolusConstraints(Constraint(amount)).value()
- if (amount - insulinAfterConstraints != 0.0) {
- ToastUtils.showToastInUiThread(context, "aborting: previously applied constraint changed")
- sendError("aborting: previously applied constraint changed")
- return
- }
- doFillBolus(amount)
- } else if ("temptarget" == act[0]) {
- val duration = SafeParse.stringToInt(act[2])
- val low = SafeParse.stringToDouble(act[3])
- val high = SafeParse.stringToDouble(act[4])
- generateTempTarget(duration, low, high)
- } else if ("wizard2" == act[0]) {
- if (lastBolusWizard != null) { //use last calculation as confirmed string matches
- doBolus(lastBolusWizard!!.calculatedTotalInsulin, lastBolusWizard!!.carbs, null, 0)
- lastBolusWizard = null
- }
- } else if ("bolus" == act[0]) {
- val insulin = SafeParse.stringToDouble(act[1])
- val carbs = SafeParse.stringToInt(act[2])
- doBolus(insulin, carbs, null, 0)
- } else if ("cppset" == act[0]) {
- val timeshift = SafeParse.stringToInt(act[1])
- val percentage = SafeParse.stringToInt(act[2])
- setCPP(timeshift, percentage)
- } else if ("ecarbs" == act[0]) {
- val carbs = SafeParse.stringToInt(act[1])
- val starttime = SafeParse.stringToLong(act[2])
- val duration = SafeParse.stringToInt(act[3])
- doECarbs(carbs, starttime, duration)
- } else if ("dismissoverviewnotification" == act[0]) {
- rxBus.send(EventDismissNotification(SafeParse.stringToInt(act[1])))
- } else if ("changeRequest" == act[0]) {
- loop.acceptChangeRequest()
- val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
- notificationManager.cancel(Constants.notificationID)
- }
- lastBolusWizard = null
- }
-
- private fun setCPP(timeshift: Int, percentage: Int) {
- var msg = ""
- //check for validity
- if (percentage < Constants.CPP_MIN_PERCENTAGE || percentage > Constants.CPP_MAX_PERCENTAGE) {
- msg += String.format(rh.gs(R.string.valueoutofrange), "Profile-Percentage") + "\n"
- }
- if (timeshift < 0 || timeshift > 23) {
- msg += String.format(rh.gs(R.string.valueoutofrange), "Profile-Timeshift") + "\n"
- }
- val profile = profileFunction.getProfile()
- if (profile == null) {
- msg += rh.gs(R.string.notloadedplugins) + "\n"
- }
- if ("" != msg) {
- msg += rh.gs(R.string.valuesnotstored)
- val rTitle = "STATUS"
- val rAction = "statusmessage"
- wearPlugin.requestActionConfirmation(rTitle, msg, rAction)
- lastSentTimestamp = System.currentTimeMillis()
- lastConfirmActionString = rAction
- return
- }
- //send profile to pump
- uel.log(Action.PROFILE_SWITCH, Sources.Wear,
- ValueWithUnit.Percent(percentage),
- ValueWithUnit.Hour(timeshift).takeIf { timeshift != 0 })
- profileFunction.createProfileSwitch(0, percentage, timeshift)
- }
-
- private fun generateTempTarget(duration: Int, low: Double, high: Double) {
- if (duration != 0) {
- disposable += repository.runTransactionForResult(InsertAndCancelCurrentTemporaryTargetTransaction(
- timestamp = System.currentTimeMillis(),
- duration = TimeUnit.MINUTES.toMillis(duration.toLong()),
- reason = TemporaryTarget.Reason.WEAR,
- lowTarget = Profile.toMgdl(low, profileFunction.getUnits()),
- highTarget = Profile.toMgdl(high, 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.Wear,
- ValueWithUnit.TherapyEventTTReason(TemporaryTarget.Reason.WEAR),
- ValueWithUnit.fromGlucoseUnit(low, profileFunction.getUnits().asText),
- ValueWithUnit.fromGlucoseUnit(high, profileFunction.getUnits().asText).takeIf { low != high },
- ValueWithUnit.Minute(duration))
- } else {
- disposable += repository.runTransactionForResult(CancelCurrentTemporaryTargetIfAnyTransaction(System.currentTimeMillis()))
- .subscribe({ result ->
- result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated temp target $it") }
- }, {
- aapsLogger.error(LTag.DATABASE, "Error while saving temporary target", it)
- })
- uel.log(Action.CANCEL_TT, Sources.Wear,
- ValueWithUnit.TherapyEventTTReason(TemporaryTarget.Reason.WEAR))
- }
- }
-
- private fun doFillBolus(amount: Double) {
- val detailedBolusInfo = DetailedBolusInfo()
- detailedBolusInfo.insulin = amount
- detailedBolusInfo.bolusType = DetailedBolusInfo.BolusType.PRIMING
- uel.log(Action.PRIME_BOLUS, Sources.Wear,
- ValueWithUnit.Insulin(amount).takeIf { amount != 0.0 })
- commandQueue.bolus(detailedBolusInfo, object : Callback() {
- override fun run() {
- if (!result.success) {
- sendError(rh.gs(R.string.treatmentdeliveryerror) +
- "\n" +
- result.comment)
- }
- }
- })
- }
-
- private fun doECarbs(carbs: Int, time: Long, duration: Int) {
- uel.log(if (duration == 0) Action.CARBS else Action.EXTENDED_CARBS, Sources.Wear,
- ValueWithUnit.Timestamp(time),
- ValueWithUnit.Gram(carbs),
- ValueWithUnit.Hour(duration).takeIf { duration != 0 })
- doBolus(0.0, carbs, time, duration)
- }
-
- private fun doBolus(amount: Double, carbs: Int, carbsTime: Long?, carbsDuration: Int) {
- val detailedBolusInfo = DetailedBolusInfo()
- detailedBolusInfo.insulin = amount
- detailedBolusInfo.carbs = carbs.toDouble()
- detailedBolusInfo.bolusType = DetailedBolusInfo.BolusType.NORMAL
- detailedBolusInfo.carbsTimestamp = carbsTime
- detailedBolusInfo.carbsDuration = T.hours(carbsDuration.toLong()).msecs()
- if (detailedBolusInfo.insulin > 0 || detailedBolusInfo.carbs > 0) {
- val action = when {
- amount.equals(0.0) -> Action.CARBS
- carbs.equals(0) -> Action.BOLUS
- carbsDuration > 0 -> Action.EXTENDED_CARBS
- else -> Action.TREATMENT
- }
- uel.log(action, Sources.Wear,
- ValueWithUnit.Insulin(amount).takeIf { amount != 0.0 },
- ValueWithUnit.Gram(carbs).takeIf { carbs != 0 },
- ValueWithUnit.Hour(carbsDuration).takeIf { carbsDuration != 0 })
- commandQueue.bolus(detailedBolusInfo, object : Callback() {
- override fun run() {
- if (!result.success) {
- sendError(rh.gs(R.string.treatmentdeliveryerror) +
- "\n" +
- result.comment)
- }
- }
- })
- }
- }
-
- @Synchronized private fun sendError(errorMessage: String) {
- wearPlugin.requestActionConfirmation("ERROR", errorMessage, "error")
- lastSentTimestamp = System.currentTimeMillis()
- lastConfirmActionString = null
- lastBolusWizard = null
- }
-
- @Synchronized
- private fun sendStatusMessage(message: String) {
- wearPlugin.requestActionConfirmation("TDD", message, "statusmessage")
- lastSentTimestamp = System.currentTimeMillis()
- lastConfirmActionString = null
- lastBolusWizard = null
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/WearFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/WearFragment.kt
index 91ed047770..0cad0dcd55 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/WearFragment.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/WearFragment.kt
@@ -6,14 +6,29 @@ import android.view.View
import android.view.ViewGroup
import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.databinding.WearFragmentBinding
+import info.nightscout.androidaps.events.EventMobileToWear
+import info.nightscout.androidaps.plugins.bus.RxBus
+import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientUpdateGUI
+import info.nightscout.androidaps.utils.DateUtil
+import info.nightscout.androidaps.utils.FabricPrivacy
+import info.nightscout.androidaps.utils.rx.AapsSchedulers
+import info.nightscout.shared.weardata.EventData
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.kotlin.plusAssign
import javax.inject.Inject
class WearFragment : DaggerFragment() {
@Inject lateinit var wearPlugin: WearPlugin
+ @Inject lateinit var rxBus: RxBus
+ @Inject lateinit var aapsSchedulers: AapsSchedulers
+ @Inject lateinit var fabricPrivacy: FabricPrivacy
+ @Inject lateinit var dateUtil: DateUtil
private var _binding: WearFragmentBinding? = null
+ private val disposable = CompositeDisposable()
+
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
@@ -26,8 +41,22 @@ class WearFragment : DaggerFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- binding.resend.setOnClickListener { wearPlugin.resendDataToWatch() }
- binding.opensettings.setOnClickListener { wearPlugin.openSettings() }
+ binding.resend.setOnClickListener { rxBus.send(EventData.ActionResendData("WearFragment")) }
+ binding.openSettings.setOnClickListener { rxBus.send(EventMobileToWear(EventData.OpenSettings(dateUtil.now()))) }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ disposable += rxBus
+ .toObservable(EventNSClientUpdateGUI::class.java)
+ .observeOn(aapsSchedulers.main)
+ .subscribe({ updateGui() }, fabricPrivacy::logException)
+ updateGui()
+ }
+
+ override fun onPause() {
+ super.onPause()
+ disposable.clear()
}
@Synchronized
@@ -35,4 +64,9 @@ class WearFragment : DaggerFragment() {
super.onDestroyView()
_binding = null
}
+
+ fun updateGui() {
+ _binding ?: return
+ binding.connectedDevice.text = wearPlugin.connectedDevice
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/WearPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/WearPlugin.kt
index b43775cdaa..61f0558009 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/WearPlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/WearPlugin.kt
@@ -2,25 +2,28 @@ package info.nightscout.androidaps.plugins.general.wear
import android.content.Context
import android.content.Intent
-import dagger.Lazy
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R
-import info.nightscout.androidaps.events.*
-import info.nightscout.androidaps.interfaces.Loop
+import info.nightscout.androidaps.events.EventAutosensCalculationFinished
+import info.nightscout.androidaps.events.EventMobileToWear
+import info.nightscout.androidaps.events.EventPreferenceChange
import info.nightscout.androidaps.interfaces.PluginBase
import info.nightscout.androidaps.interfaces.PluginDescription
import info.nightscout.androidaps.interfaces.PluginType
-import info.nightscout.shared.logging.AAPSLogger
-import info.nightscout.androidaps.plugins.aps.events.EventOpenAPSUpdateGui
+import info.nightscout.androidaps.plugins.aps.loop.events.EventLoopUpdateGui
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.overview.events.EventDismissBolusProgressIfRunning
import info.nightscout.androidaps.plugins.general.overview.events.EventOverviewBolusProgress
-import info.nightscout.androidaps.plugins.general.wear.wearintegration.WatchUpdaterService
+import info.nightscout.androidaps.plugins.general.wear.wearintegration.DataHandlerMobile
+import info.nightscout.androidaps.plugins.general.wear.wearintegration.DataLayerListenerServiceMobile
import info.nightscout.androidaps.utils.FabricPrivacy
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
+import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.sharedPreferences.SP
-import io.reactivex.disposables.CompositeDisposable
+import info.nightscout.shared.weardata.EventData
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.kotlin.plusAssign
import javax.inject.Inject
import javax.inject.Singleton
@@ -31,157 +34,66 @@ class WearPlugin @Inject constructor(
rh: ResourceHelper,
private val aapsSchedulers: AapsSchedulers,
private val sp: SP,
- private val ctx: Context,
private val fabricPrivacy: FabricPrivacy,
- private val loop: Loop,
private val rxBus: RxBus,
- private val actionStringHandler: Lazy
+ private val context: Context,
+ private val dataHandlerMobile: DataHandlerMobile
-) : PluginBase(PluginDescription()
- .mainType(PluginType.GENERAL)
- .fragmentClass(WearFragment::class.java.name)
- .pluginIcon(R.drawable.ic_watch)
- .pluginName(R.string.wear)
- .shortName(R.string.wear_shortname)
- .preferencesId(R.xml.pref_wear)
- .description(R.string.description_wear),
+) : PluginBase(
+ PluginDescription()
+ .mainType(PluginType.GENERAL)
+ .fragmentClass(WearFragment::class.java.name)
+ .pluginIcon(R.drawable.ic_watch)
+ .pluginName(R.string.wear)
+ .shortName(R.string.wear_shortname)
+ .preferencesId(R.xml.pref_wear)
+ .description(R.string.description_wear),
aapsLogger, rh, injector
) {
private val disposable = CompositeDisposable()
+
+ var connectedDevice = "---"
+
override fun onStart() {
super.onStart()
- disposable.add(rxBus
- .toObservable(EventOpenAPSUpdateGui::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({ sendDataToWatch(status = true, basals = true, bgValue = false) }, fabricPrivacy::logException))
- disposable.add(rxBus
- .toObservable(EventExtendedBolusChange::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({ sendDataToWatch(status = true, basals = true, bgValue = false) }, fabricPrivacy::logException))
- disposable.add(rxBus
- .toObservable(EventTempBasalChange::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({ sendDataToWatch(status = true, basals = true, bgValue = false) }, fabricPrivacy::logException))
- disposable.add(rxBus
- .toObservable(EventTreatmentChange::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({ sendDataToWatch(status = true, basals = true, bgValue = false) }, fabricPrivacy::logException))
- disposable.add(rxBus
- .toObservable(EventEffectiveProfileSwitchChanged::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({ sendDataToWatch(status = false, basals = true, bgValue = false) }, fabricPrivacy::logException))
- disposable.add(rxBus
- .toObservable(EventAutosensCalculationFinished::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({ sendDataToWatch(status = true, basals = true, bgValue = true) }, fabricPrivacy::logException))
- disposable.add(rxBus
- .toObservable(EventPreferenceChange::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({
- // possibly new high or low mark
- resendDataToWatch()
- // status may be formatted differently
- sendDataToWatch(status = true, basals = false, bgValue = false)
- }, fabricPrivacy::logException))
- disposable.add(rxBus
- .toObservable(EventRefreshOverview::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({
- if (WatchUpdaterService.shouldReportLoopStatus((loop as PluginBase).isEnabled()))
- sendDataToWatch(status = true, basals = false, bgValue = false)
- }, fabricPrivacy::logException))
- disposable.add(rxBus
- .toObservable(EventBolusRequested::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe({ event: EventBolusRequested ->
- val status = String.format(rh.gs(R.string.bolusrequested), event.amount)
- val intent = Intent(ctx, WatchUpdaterService::class.java).setAction(WatchUpdaterService.ACTION_SEND_BOLUSPROGRESS)
- intent.putExtra("progresspercent", 0)
- intent.putExtra("progressstatus", status)
- ctx.startService(intent)
- }, fabricPrivacy::logException))
- disposable.add(rxBus
+ context.startService(Intent(context, DataLayerListenerServiceMobile::class.java))
+ disposable += rxBus
.toObservable(EventDismissBolusProgressIfRunning::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ event: EventDismissBolusProgressIfRunning ->
- if (event.result == null) return@subscribe
- val status: String = if (event.result!!.success) {
- rh.gs(R.string.success)
- } else {
- rh.gs(R.string.nosuccess)
- }
- val intent = Intent(ctx, WatchUpdaterService::class.java).setAction(WatchUpdaterService.ACTION_SEND_BOLUSPROGRESS)
- intent.putExtra("progresspercent", 100)
- intent.putExtra("progressstatus", status)
- ctx.startService(intent)
- }, fabricPrivacy::logException))
- disposable.add(rxBus
+ event.result?.let {
+ val status =
+ if (it.success) rh.gs(R.string.success)
+ else rh.gs(R.string.nosuccess)
+ if (isEnabled()) rxBus.send(EventMobileToWear(EventData.BolusProgress(percent = 100, status = status)))
+ }
+ }, fabricPrivacy::logException)
+ disposable += rxBus
.toObservable(EventOverviewBolusProgress::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ event: EventOverviewBolusProgress ->
- if (!event.isSMB() || sp.getBoolean("wear_notifySMB", true)) {
- val intent = Intent(ctx, WatchUpdaterService::class.java).setAction(WatchUpdaterService.ACTION_SEND_BOLUSPROGRESS)
- intent.putExtra("progresspercent", event.percent)
- intent.putExtra("progressstatus", event.status)
- ctx.startService(intent)
- }
- }, fabricPrivacy::logException))
- actionStringHandler.get().setup()
+ if (!event.isSMB() || sp.getBoolean("wear_notifySMB", true)) {
+ if (isEnabled()) rxBus.send(EventMobileToWear(EventData.BolusProgress(percent = event.percent, status = event.status)))
+ }
+ }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventPreferenceChange::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({ dataHandlerMobile.resendData("EventPreferenceChange") }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventAutosensCalculationFinished::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({ dataHandlerMobile.resendData("EventAutosensCalculationFinished") }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventLoopUpdateGui::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({ dataHandlerMobile.resendData("EventLoopUpdateGui") }, fabricPrivacy::logException)
}
override fun onStop() {
disposable.clear()
super.onStop()
- actionStringHandler.get().tearDown()
- }
-
- private fun sendDataToWatch(status: Boolean, basals: Boolean, bgValue: Boolean) {
- //Log.d(TAG, "WR: WearPlugin:sendDataToWatch (status=" + status + ",basals=" + basals + ",bgValue=" + bgValue + ")");
- if (isEnabled(getType())) {
- // only start service when this plugin is enabled
- if (bgValue) {
- ctx.startService(Intent(ctx, WatchUpdaterService::class.java))
- }
- if (basals) {
- ctx.startService(Intent(ctx, WatchUpdaterService::class.java).setAction(WatchUpdaterService.ACTION_SEND_BASALS))
- }
- if (status) {
- ctx.startService(Intent(ctx, WatchUpdaterService::class.java).setAction(WatchUpdaterService.ACTION_SEND_STATUS))
- }
- }
- }
-
- fun resendDataToWatch() {
- //Log.d(TAG, "WR: WearPlugin:resendDataToWatch");
- ctx.startService(Intent(ctx, WatchUpdaterService::class.java).setAction(WatchUpdaterService.ACTION_RESEND))
- }
-
- fun openSettings() {
- //Log.d(TAG, "WR: WearPlugin:openSettings");
- ctx.startService(Intent(ctx, WatchUpdaterService::class.java).setAction(WatchUpdaterService.ACTION_OPEN_SETTINGS))
- }
-
- fun requestNotificationCancel(actionString: String?) { //Log.d(TAG, "WR: WearPlugin:requestNotificationCancel");
- val intent = Intent(ctx, WatchUpdaterService::class.java)
- .setAction(WatchUpdaterService.ACTION_CANCEL_NOTIFICATION)
- intent.putExtra("actionstring", actionString)
- ctx.startService(intent)
- }
-
- fun requestActionConfirmation(title: String, message: String, actionString: String) {
- val intent = Intent(ctx, WatchUpdaterService::class.java).setAction(WatchUpdaterService.ACTION_SEND_ACTIONCONFIRMATIONREQUEST)
- intent.putExtra("title", title)
- intent.putExtra("message", message)
- intent.putExtra("actionstring", actionString)
- ctx.startService(intent)
- }
-
- fun requestChangeConfirmation(title: String, message: String, actionString: String) {
- val intent = Intent(ctx, WatchUpdaterService::class.java).setAction(WatchUpdaterService.ACTION_SEND_CHANGECONFIRMATIONREQUEST)
- intent.putExtra("title", title)
- intent.putExtra("message", message)
- intent.putExtra("actionstring", actionString)
- ctx.startService(intent)
+ context.stopService(Intent(context, DataLayerListenerServiceMobile::class.java))
}
}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/events/EventWearInitiateAction.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/events/EventWearInitiateAction.kt
deleted file mode 100644
index 9661c4af38..0000000000
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/events/EventWearInitiateAction.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package info.nightscout.androidaps.plugins.general.wear.events
-
-import info.nightscout.androidaps.events.Event
-
-class EventWearInitiateAction(val action: String) : Event()
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/events/EventWearConfirmAction.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/events/EventWearUpdateGui.kt
similarity index 65%
rename from app/src/main/java/info/nightscout/androidaps/plugins/general/wear/events/EventWearConfirmAction.kt
rename to app/src/main/java/info/nightscout/androidaps/plugins/general/wear/events/EventWearUpdateGui.kt
index 211836d27d..4736566059 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/events/EventWearConfirmAction.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/events/EventWearUpdateGui.kt
@@ -2,4 +2,4 @@ package info.nightscout.androidaps.plugins.general.wear.events
import info.nightscout.androidaps.events.Event
-class EventWearConfirmAction(val action: String) : Event()
\ No newline at end of file
+class EventWearUpdateGui : Event()
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/DataHandlerMobile.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/DataHandlerMobile.kt
new file mode 100644
index 0000000000..64623dbf12
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/DataHandlerMobile.kt
@@ -0,0 +1,1201 @@
+package info.nightscout.androidaps.plugins.general.wear.wearintegration
+
+import android.app.NotificationManager
+import android.content.Context
+import dagger.android.HasAndroidInjector
+import info.nightscout.androidaps.Constants
+import info.nightscout.androidaps.R
+import info.nightscout.androidaps.data.DetailedBolusInfo
+import info.nightscout.androidaps.database.AppRepository
+import info.nightscout.androidaps.database.ValueWrapper
+import info.nightscout.androidaps.database.entities.*
+import info.nightscout.androidaps.database.interfaces.end
+import info.nightscout.androidaps.database.transactions.CancelCurrentTemporaryTargetIfAnyTransaction
+import info.nightscout.androidaps.database.transactions.InsertAndCancelCurrentTemporaryTargetTransaction
+import info.nightscout.androidaps.dialogs.CarbsDialog
+import info.nightscout.androidaps.dialogs.InsulinDialog
+import info.nightscout.androidaps.events.EventMobileToWear
+import info.nightscout.androidaps.extensions.convertedToAbsolute
+import info.nightscout.androidaps.extensions.toStringShort
+import info.nightscout.androidaps.extensions.total
+import info.nightscout.androidaps.extensions.valueToUnits
+import info.nightscout.androidaps.extensions.valueToUnitsString
+import info.nightscout.androidaps.interfaces.*
+import info.nightscout.androidaps.logging.UserEntryLogger
+import info.nightscout.androidaps.plugins.bus.RxBus
+import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
+import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus
+import info.nightscout.androidaps.plugins.general.overview.graphExtensions.GlucoseValueDataPoint
+import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProvider
+import info.nightscout.androidaps.queue.Callback
+import info.nightscout.androidaps.receivers.ReceiverStatusStore
+import info.nightscout.androidaps.services.AlarmSoundServiceHelper
+import info.nightscout.androidaps.utils.*
+import info.nightscout.androidaps.utils.rx.AapsSchedulers
+import info.nightscout.androidaps.utils.wizard.BolusWizard
+import info.nightscout.androidaps.utils.wizard.QuickWizard
+import info.nightscout.androidaps.utils.wizard.QuickWizardEntry
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.shared.logging.LTag
+import info.nightscout.shared.sharedPreferences.SP
+import info.nightscout.shared.weardata.EventData
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.kotlin.plusAssign
+import java.text.DateFormat
+import java.text.SimpleDateFormat
+import java.util.*
+import java.util.concurrent.TimeUnit
+import java.util.stream.Collectors
+import javax.inject.Inject
+import javax.inject.Singleton
+import kotlin.math.abs
+import kotlin.math.min
+
+@Singleton
+class DataHandlerMobile @Inject constructor(
+ aapsSchedulers: AapsSchedulers,
+ private val injector: HasAndroidInjector,
+ private val context: Context,
+ private val rxBus: RxBus,
+ private val aapsLogger: AAPSLogger,
+ private val rh: ResourceHelper,
+ private val sp: SP,
+ private val config: Config,
+ private val iobCobCalculator: IobCobCalculator,
+ private val repository: AppRepository,
+ private val glucoseStatusProvider: GlucoseStatusProvider,
+ private val profileFunction: ProfileFunction,
+ private val loop: Loop,
+ private val nsDeviceStatus: NSDeviceStatus,
+ private val receiverStatusStore: ReceiverStatusStore,
+ private val quickWizard: QuickWizard,
+ private val defaultValueHelper: DefaultValueHelper,
+ private val trendCalculator: TrendCalculator,
+ private val dateUtil: DateUtil,
+ private val constraintChecker: ConstraintChecker,
+ private val uel: UserEntryLogger,
+ private val activePlugin: ActivePlugin,
+ private val commandQueue: CommandQueue,
+ private val fabricPrivacy: FabricPrivacy,
+ private val alarmSoundServiceHelper: AlarmSoundServiceHelper
+) {
+
+ private val disposable = CompositeDisposable()
+
+ private var lastBolusWizard: BolusWizard? = null
+
+ init {
+ // From Wear
+ disposable += rxBus
+ .toObservable(EventData.ActionPong::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({
+ aapsLogger.debug(LTag.WEAR, "Pong received from ${it.sourceNodeId}")
+ fabricPrivacy.logCustom("WearOS_${it.apiLevel}")
+ }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventData.CancelBolus::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({
+ aapsLogger.debug(LTag.WEAR, "CancelBolus received from ${it.sourceNodeId}")
+ activePlugin.activePump.stopBolusDelivering()
+ }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventData.OpenLoopRequestConfirmed::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({
+ aapsLogger.debug(LTag.WEAR, "OpenLoopRequestConfirmed received from ${it.sourceNodeId}")
+ loop.acceptChangeRequest()
+ (context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).cancel(Constants.notificationID)
+ }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventData.ActionResendData::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({
+ aapsLogger.debug(LTag.WEAR, "ResendData received from ${it.sourceNodeId}")
+ resendData(it.from)
+ }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventData.ActionPumpStatus::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({
+ aapsLogger.debug(LTag.WEAR, "ActionPumpStatus received from ${it.sourceNodeId}")
+ rxBus.send(
+ EventMobileToWear(
+ EventData.ConfirmAction(
+ rh.gs(R.string.medtronic_pump_status).uppercase(),
+ activePlugin.activePump.shortStatus(false),
+ returnCommand = null
+ )
+ )
+ )
+ }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventData.ActionLoopStatus::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({
+ aapsLogger.debug(LTag.WEAR, "ActionLoopStatus received from ${it.sourceNodeId}")
+ rxBus.send(
+ EventMobileToWear(
+ EventData.ConfirmAction(
+ rh.gs(R.string.loop_status).uppercase(),
+ "TARGETS:\n$targetsStatus\n\n$loopStatus\n\nOAPS RESULT:\n$oAPSResultStatus",
+ returnCommand = null
+ )
+ )
+ )
+ }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventData.ActionTddStatus::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe {
+ aapsLogger.debug(LTag.WEAR, "ActionTddStatus received from ${it.sourceNodeId}")
+ handleTddStatus()
+ }
+ disposable += rxBus
+ .toObservable(EventData.ActionProfileSwitchSendInitialData::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({
+ aapsLogger.debug(LTag.WEAR, "ActionProfileSwitchSendInitialData received $it from ${it.sourceNodeId}")
+ handleProfileSwitchSendInitialData()
+ }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventData.ActionProfileSwitchPreCheck::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({
+ aapsLogger.debug(LTag.WEAR, "ActionProfileSwitchPreCheck received $it from ${it.sourceNodeId}")
+ handleProfileSwitchPreCheck(it)
+ }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventData.ActionProfileSwitchConfirmed::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({
+ aapsLogger.debug(LTag.WEAR, "ActionProfileSwitchConfirmed received $it from ${it.sourceNodeId}")
+ doProfileSwitch(it)
+ }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventData.ActionTempTargetPreCheck::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({
+ aapsLogger.debug(LTag.WEAR, "ActionTempTargetPreCheck received $it from ${it.sourceNodeId}")
+ handleTempTargetPreCheck(it)
+ }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventData.ActionTempTargetConfirmed::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({
+ aapsLogger.debug(LTag.WEAR, "ActionTempTargetConfirmed received $it from ${it.sourceNodeId}")
+ doTempTarget(it)
+ }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventData.ActionBolusPreCheck::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({
+ aapsLogger.debug(LTag.WEAR, "ActionBolusPreCheck received $it from ${it.sourceNodeId}")
+ handleBolusPreCheck(it)
+ }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventData.ActionBolusConfirmed::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({
+ aapsLogger.debug(LTag.WEAR, "ActionBolusConfirmed received $it from ${it.sourceNodeId}")
+ doBolus(it.insulin, it.carbs, null, 0)
+ }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventData.ActionECarbsPreCheck::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({
+ aapsLogger.debug(LTag.WEAR, "ActionECarbsPreCheck received $it from ${it.sourceNodeId}")
+ handleECarbsPreCheck(it)
+ }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventData.ActionECarbsConfirmed::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({
+ aapsLogger.debug(LTag.WEAR, "ActionECarbsConfirmed received $it from ${it.sourceNodeId}")
+ doECarbs(it.carbs, it.carbsTime, it.duration)
+ }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventData.ActionFillPresetPreCheck::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({
+ aapsLogger.debug(LTag.WEAR, "ActionFillPresetPreCheck received $it from ${it.sourceNodeId}")
+ handleFillPresetPreCheck(it)
+ }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventData.ActionFillPreCheck::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({
+ aapsLogger.debug(LTag.WEAR, "ActionFillPreCheck received $it from ${it.sourceNodeId}")
+ handleFillPreCheck(it)
+ }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventData.ActionFillConfirmed::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({
+ aapsLogger.debug(LTag.WEAR, "ActionFillConfirmed received $it from ${it.sourceNodeId}")
+ if (constraintChecker.applyBolusConstraints(Constraint(it.insulin)).value() - it.insulin != 0.0) {
+ ToastUtils.showToastInUiThread(context, "aborting: previously applied constraint changed")
+ sendError("aborting: previously applied constraint changed")
+ } else
+ doFillBolus(it.insulin)
+ }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventData.ActionQuickWizardPreCheck::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({
+ aapsLogger.debug(LTag.WEAR, "ActionQuickWizardPreCheck received $it from ${it.sourceNodeId}")
+ handleQuickWizardPreCheck(it)
+ }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventData.ActionWizardPreCheck::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({
+ aapsLogger.debug(LTag.WEAR, "ActionWizardPreCheck received $it from ${it.sourceNodeId}")
+ handleWizardPreCheck(it)
+ }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventData.ActionWizardConfirmed::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({
+ aapsLogger.debug(LTag.WEAR, "ActionWizardConfirmed received $it from ${it.sourceNodeId}")
+ if (lastBolusWizard?.timeStamp == it.timeStamp) { //use last calculation as confirmed string matches
+ doBolus(lastBolusWizard!!.calculatedTotalInsulin, lastBolusWizard!!.carbs, null, 0)
+ }
+ lastBolusWizard = null
+ }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventData.SnoozeAlert::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({
+ aapsLogger.debug(LTag.WEAR, "SnoozeAlert received $it from ${it.sourceNodeId}")
+ alarmSoundServiceHelper.stopService(context, "Muted from wear")
+ }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventData.WearException::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({
+ aapsLogger.debug(LTag.WEAR, "WearException received $it from ${it.sourceNodeId}")
+ fabricPrivacy.logWearException(it)
+ }, fabricPrivacy::logException)
+ }
+
+ private fun handleTddStatus() {
+ val activePump = activePlugin.activePump
+ var message: String
+ // check if DB up to date
+ val dummies: MutableList = LinkedList()
+ val historyList = getTDDList(dummies)
+ if (isOldData(historyList)) {
+ message = "OLD DATA - "
+ //if pump is not busy: try to fetch data
+ if (activePump.isBusy()) {
+ message += rh.gs(R.string.pumpbusy)
+ } else {
+ message += "trying to fetch data from pump."
+ commandQueue.loadTDDs(object : Callback() {
+ override fun run() {
+ val dummies1: MutableList = LinkedList()
+ val historyList1 = getTDDList(dummies1)
+ val reloadMessage =
+ if (isOldData(historyList1))
+ "TDD: Still old data! Cannot load from pump.\n" + generateTDDMessage(historyList1, dummies1)
+ else
+ generateTDDMessage(historyList1, dummies1)
+ rxBus.send(
+ EventMobileToWear(
+ EventData.ConfirmAction(
+ rh.gs(R.string.tdd),
+ reloadMessage,
+ returnCommand = null
+ )
+ )
+ )
+ }
+ })
+ }
+ } else { // if up to date: prepare, send (check if CPP is activated -> add CPP stats)
+ message = generateTDDMessage(historyList, dummies)
+ }
+ rxBus.send(
+ EventMobileToWear(
+ EventData.ConfirmAction(
+ rh.gs(R.string.tdd),
+ message,
+ returnCommand = null
+ )
+ )
+ )
+ }
+
+ private fun handleWizardPreCheck(command: EventData.ActionWizardPreCheck) {
+ val pump = activePlugin.activePump
+ if (!pump.isInitialized() || pump.isSuspended() || loop.isDisconnected) {
+ sendError(rh.gs(R.string.wizard_pump_not_available))
+ return
+ }
+ val carbsBeforeConstraints = command.carbs
+ val carbsAfterConstraints = constraintChecker.applyCarbsConstraints(Constraint(carbsBeforeConstraints)).value()
+ if (carbsAfterConstraints - carbsBeforeConstraints != 0) {
+ sendError(rh.gs(R.string.wizard_carbs_constraint))
+ return
+ }
+ val percentage = command.percentage
+ val profile = profileFunction.getProfile()
+ val profileName = profileFunction.getProfileName()
+ if (profile == null) {
+ sendError(rh.gs(R.string.wizard_no_active_profile))
+ return
+ }
+ val bgReading = iobCobCalculator.ads.actualBg()
+ if (bgReading == null) {
+ sendError(rh.gs(R.string.wizard_no_actual_bg))
+ return
+ }
+ val cobInfo = iobCobCalculator.getCobInfo(false, "Wizard wear")
+ if (cobInfo.displayCob == null) {
+ sendError(rh.gs(R.string.wizard_no_cob))
+ return
+ }
+ val dbRecord = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet()
+ val tempTarget = if (dbRecord is ValueWrapper.Existing) dbRecord.value else null
+
+ val bolusWizard = BolusWizard(injector).doCalc(
+ profile = profile,
+ profileName = profileName,
+ tempTarget = tempTarget,
+ carbs = carbsAfterConstraints,
+ cob = cobInfo.displayCob!!,
+ bg = bgReading.valueToUnits(profileFunction.getUnits()),
+ correction = 0.0,
+ percentageCorrection = percentage,
+ useBg = sp.getBoolean(R.string.key_wearwizard_bg, true),
+ useCob = sp.getBoolean(R.string.key_wearwizard_cob, true),
+ includeBolusIOB = sp.getBoolean(R.string.key_wearwizard_iob, true),
+ includeBasalIOB = sp.getBoolean(R.string.key_wearwizard_iob, true),
+ useSuperBolus = false,
+ useTT = sp.getBoolean(R.string.key_wearwizard_tt, false),
+ useTrend = sp.getBoolean(R.string.key_wearwizard_trend, false),
+ useAlarm = false
+ )
+ val insulinAfterConstraints = bolusWizard.insulinAfterConstraints
+ val minStep = pump.pumpDescription.pumpType.determineCorrectBolusStepSize(insulinAfterConstraints)
+ if (abs(insulinAfterConstraints - bolusWizard.calculatedTotalInsulin) >= minStep) {
+ sendError(rh.gs(R.string.wizard_constraint_bolus_size, bolusWizard.calculatedTotalInsulin))
+ return
+ }
+ if (bolusWizard.calculatedTotalInsulin <= 0 && bolusWizard.carbs <= 0) {
+ sendError("No insulin required")
+ return
+ }
+ val message =
+ rh.gs(R.string.wizard_result, bolusWizard.calculatedTotalInsulin, bolusWizard.carbs) + "\n_____________\n" + bolusWizard.explainShort()
+ lastBolusWizard = bolusWizard
+ rxBus.send(
+ EventMobileToWear(
+ EventData.ConfirmAction(
+ rh.gs(R.string.confirm).uppercase(), message,
+ returnCommand = EventData.ActionWizardConfirmed(bolusWizard.timeStamp)
+ )
+ )
+ )
+ }
+
+ private fun handleQuickWizardPreCheck(command: EventData.ActionQuickWizardPreCheck) {
+ val actualBg = iobCobCalculator.ads.actualBg()
+ val profile = profileFunction.getProfile()
+ val profileName = profileFunction.getProfileName()
+ val quickWizardEntry = quickWizard.get(command.guid)
+ //Log.i("QuickWizard", "handleInitiate: quick_wizard " + quickWizardEntry?.buttonText() + " c " + quickWizardEntry?.carbs())
+ if (quickWizardEntry == null) {
+ sendError(rh.gs(R.string.quick_wizard_not_available))
+ return
+ }
+ if (actualBg == null) {
+ sendError(rh.gs(R.string.wizard_no_actual_bg))
+ return
+ }
+ if (profile == null) {
+ sendError(rh.gs(R.string.wizard_no_active_profile))
+ return
+ }
+ val cobInfo = iobCobCalculator.getCobInfo(false, "QuickWizard wear")
+ if (cobInfo.displayCob == null) {
+ sendError(rh.gs(R.string.wizard_no_cob))
+ return
+ }
+ val pump = activePlugin.activePump
+ if (!pump.isInitialized() || pump.isSuspended() || loop.isDisconnected) {
+ sendError(rh.gs(R.string.wizard_pump_not_available))
+ return
+ }
+
+ val wizard = quickWizardEntry.doCalc(profile, profileName, actualBg, true)
+
+ val carbsAfterConstraints = constraintChecker.applyCarbsConstraints(Constraint(quickWizardEntry.carbs())).value()
+ if (carbsAfterConstraints != quickWizardEntry.carbs()) {
+ sendError(rh.gs(R.string.wizard_carbs_constraint))
+ return
+ }
+ val insulinAfterConstraints = wizard.insulinAfterConstraints
+ val minStep = pump.pumpDescription.pumpType.determineCorrectBolusStepSize(insulinAfterConstraints)
+ if (abs(insulinAfterConstraints - wizard.calculatedTotalInsulin) >= minStep) {
+ sendError(rh.gs(R.string.wizard_constraint_bolus_size, wizard.calculatedTotalInsulin))
+ return
+ }
+
+ val message = rh.gs(R.string.quick_wizard_message, quickWizardEntry.buttonText(), wizard.calculatedTotalInsulin, quickWizardEntry.carbs()) +
+ "\n_____________\n" + wizard.explainShort()
+
+ rxBus.send(
+ EventMobileToWear(
+ EventData.ConfirmAction(
+ rh.gs(R.string.confirm).uppercase(), message,
+ returnCommand = EventData.ActionBolusConfirmed(insulinAfterConstraints, carbsAfterConstraints)
+ )
+ )
+ )
+ }
+
+ private fun handleBolusPreCheck(command: EventData.ActionBolusPreCheck) {
+ val insulinAfterConstraints = constraintChecker.applyBolusConstraints(Constraint(command.insulin)).value()
+ val carbsAfterConstraints = constraintChecker.applyCarbsConstraints(Constraint(command.carbs)).value()
+ val pump = activePlugin.activePump
+ if (insulinAfterConstraints > 0 && (!pump.isInitialized() || pump.isSuspended() || loop.isDisconnected)) {
+ sendError(rh.gs(R.string.wizard_pump_not_available))
+ return
+ }
+ var message = ""
+ message += rh.gs(R.string.bolus) + ": " + insulinAfterConstraints + "U\n"
+ message += rh.gs(R.string.carbs) + ": " + carbsAfterConstraints + "g"
+ if (insulinAfterConstraints - command.insulin != 0.0 || carbsAfterConstraints - command.carbs != 0)
+ message += "\n" + rh.gs(R.string.constraintapllied)
+ rxBus.send(
+ EventMobileToWear(
+ EventData.ConfirmAction(
+ rh.gs(R.string.confirm).uppercase(), message,
+ returnCommand = EventData.ActionBolusConfirmed(insulinAfterConstraints, carbsAfterConstraints)
+ )
+ )
+ )
+ }
+
+ private fun handleECarbsPreCheck(command: EventData.ActionECarbsPreCheck) {
+ val startTimeStamp = System.currentTimeMillis() + T.mins(command.carbsTimeShift.toLong()).msecs()
+ val carbsAfterConstraints = constraintChecker.applyCarbsConstraints(Constraint(command.carbs)).value()
+ var message = rh.gs(R.string.carbs) + ": " + carbsAfterConstraints + "g" +
+ "\n" + rh.gs(R.string.time) + ": " + dateUtil.timeString(startTimeStamp) +
+ "\n" + rh.gs(R.string.duration) + ": " + command.duration + "h"
+ if (carbsAfterConstraints - command.carbs != 0) {
+ message += "\n" + rh.gs(R.string.constraintapllied)
+ }
+ if (carbsAfterConstraints <= 0) {
+ sendError("Carbs = 0! No action taken!")
+ return
+ }
+ rxBus.send(
+ EventMobileToWear(
+ EventData.ConfirmAction(
+ rh.gs(R.string.confirm).uppercase(), message,
+ returnCommand = EventData.ActionECarbsConfirmed(carbsAfterConstraints, startTimeStamp, command.duration)
+ )
+ )
+ )
+ }
+
+ private fun handleFillPresetPreCheck(command: EventData.ActionFillPresetPreCheck) {
+ val amount: Double = when (command.button) {
+ 1 -> sp.getDouble("fill_button1", 0.3)
+ 2 -> sp.getDouble("fill_button2", 0.0)
+ 3 -> sp.getDouble("fill_button3", 0.0)
+ else -> return
+ }
+ val insulinAfterConstraints = constraintChecker.applyBolusConstraints(Constraint(amount)).value()
+ var message = rh.gs(R.string.primefill) + ": " + insulinAfterConstraints + "U"
+ if (insulinAfterConstraints - amount != 0.0) message += "\n" + rh.gs(R.string.constraintapllied)
+ rxBus.send(
+ EventMobileToWear(
+ EventData.ConfirmAction(
+ rh.gs(R.string.confirm).uppercase(), message,
+ returnCommand = EventData.ActionFillConfirmed(insulinAfterConstraints)
+ )
+ )
+ )
+ }
+
+ private fun handleFillPreCheck(command: EventData.ActionFillPreCheck) {
+ val insulinAfterConstraints = constraintChecker.applyBolusConstraints(Constraint(command.insulin)).value()
+ var message = rh.gs(R.string.primefill) + ": " + insulinAfterConstraints + "U"
+ if (insulinAfterConstraints - command.insulin != 0.0) message += "\n" + rh.gs(R.string.constraintapllied)
+ rxBus.send(
+ EventMobileToWear(
+ EventData.ConfirmAction(
+ rh.gs(R.string.confirm).uppercase(), message,
+ returnCommand = EventData.ActionFillConfirmed(insulinAfterConstraints)
+ )
+ )
+ )
+ }
+
+ private fun handleProfileSwitchSendInitialData() {
+ val activeProfileSwitch = repository.getEffectiveProfileSwitchActiveAt(dateUtil.now()).blockingGet()
+ if (activeProfileSwitch is ValueWrapper.Existing) { // read CPP values
+ rxBus.send(
+ EventMobileToWear(EventData.ActionProfileSwitchOpenActivity(T.msecs(activeProfileSwitch.value.originalTimeshift).hours().toInt(), activeProfileSwitch.value.originalPercentage))
+ )
+ } else {
+ sendError("No active profile switch!")
+ return
+ }
+
+ }
+
+ private fun handleProfileSwitchPreCheck(command: EventData.ActionProfileSwitchPreCheck) {
+ val activeProfileSwitch = repository.getEffectiveProfileSwitchActiveAt(dateUtil.now()).blockingGet()
+ if (activeProfileSwitch is ValueWrapper.Absent) {
+ sendError("No active profile switch!")
+ }
+ if (command.percentage < Constants.CPP_MIN_PERCENTAGE || command.percentage > Constants.CPP_MAX_PERCENTAGE) {
+ sendError(rh.gs(R.string.valueoutofrange, "Profile-Percentage"))
+ }
+ if (command.timeShift < 0 || command.timeShift > 23) {
+ sendError(rh.gs(R.string.valueoutofrange, "Profile-Timeshift"))
+ }
+ val message = "Profile:" + "\n\n" +
+ "Timeshift: " + command.timeShift + "\n" +
+ "Percentage: " + command.percentage + "%"
+ rxBus.send(
+ EventMobileToWear(
+ EventData.ConfirmAction(
+ rh.gs(R.string.confirm).uppercase(), message,
+ returnCommand = EventData.ActionProfileSwitchConfirmed(command.timeShift, command.percentage)
+ )
+ )
+ )
+ }
+
+ private fun handleTempTargetPreCheck(action: EventData.ActionTempTargetPreCheck) {
+ val title = rh.gs(R.string.confirm).uppercase()
+ var message = ""
+ val presetIsMGDL = profileFunction.getUnits() == GlucoseUnit.MGDL
+ when (action.command) {
+ EventData.ActionTempTargetPreCheck.TempTargetCommand.PRESET_ACTIVITY -> {
+ val activityTTDuration = defaultValueHelper.determineActivityTTDuration()
+ val activityTT = defaultValueHelper.determineActivityTT()
+ val reason = rh.gs(R.string.activity)
+ message += rh.gs(R.string.wear_action_tempt_preset_message, reason, activityTT, activityTTDuration)
+ rxBus.send(
+ EventMobileToWear(
+ EventData.ConfirmAction(
+ title, message,
+ returnCommand = EventData.ActionTempTargetConfirmed(presetIsMGDL, activityTTDuration, activityTT, activityTT)
+ )
+ )
+ )
+ }
+
+ EventData.ActionTempTargetPreCheck.TempTargetCommand.PRESET_HYPO -> {
+ val hypoTTDuration = defaultValueHelper.determineHypoTTDuration()
+ val hypoTT = defaultValueHelper.determineHypoTT()
+ val reason = rh.gs(R.string.hypo)
+ message += rh.gs(R.string.wear_action_tempt_preset_message, reason, hypoTT, hypoTTDuration)
+ rxBus.send(
+ EventMobileToWear(
+ EventData.ConfirmAction(
+ title, message,
+ returnCommand = EventData.ActionTempTargetConfirmed(presetIsMGDL, hypoTTDuration, hypoTT, hypoTT)
+ )
+ )
+ )
+ }
+
+ EventData.ActionTempTargetPreCheck.TempTargetCommand.PRESET_EATING -> {
+ val eatingSoonTTDuration = defaultValueHelper.determineEatingSoonTTDuration()
+ val eatingSoonTT = defaultValueHelper.determineEatingSoonTT()
+ val reason = rh.gs(R.string.eatingsoon)
+ message += rh.gs(R.string.wear_action_tempt_preset_message, reason, eatingSoonTT, eatingSoonTTDuration)
+ rxBus.send(
+ EventMobileToWear(
+ EventData.ConfirmAction(
+ title, message,
+ returnCommand = EventData.ActionTempTargetConfirmed(presetIsMGDL, eatingSoonTTDuration, eatingSoonTT, eatingSoonTT)
+ )
+ )
+ )
+ }
+
+ EventData.ActionTempTargetPreCheck.TempTargetCommand.CANCEL -> {
+ message += rh.gs(R.string.wear_action_tempt_cancel_message)
+ rxBus.send(
+ EventMobileToWear(
+ EventData.ConfirmAction(
+ title, message,
+ returnCommand = EventData.ActionTempTargetConfirmed(true, 0, 0.0, 0.0)
+ )
+ )
+ )
+ }
+
+ EventData.ActionTempTargetPreCheck.TempTargetCommand.MANUAL -> {
+ if (profileFunction.getUnits() == GlucoseUnit.MGDL != action.isMgdl) {
+ sendError(rh.gs(R.string.wear_action_tempt_unit_error))
+ return
+ }
+ if (action.duration == 0) {
+ message += rh.gs(R.string.wear_action_tempt_zero_message)
+ rxBus.send(
+ EventMobileToWear(
+ EventData.ConfirmAction(
+ title, message,
+ returnCommand = EventData.ActionTempTargetConfirmed(true, 0, 0.0, 0.0)
+ )
+ )
+ )
+ } else {
+ var low = action.low
+ var high = action.high
+ if (!action.isMgdl) {
+ low *= Constants.MMOLL_TO_MGDL
+ high *= Constants.MMOLL_TO_MGDL
+ }
+ if (low < HardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[0] || low > HardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[1]) {
+ sendError(rh.gs(R.string.wear_action_tempt_min_bg_error))
+ return
+ }
+ if (high < HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[0] || high > HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[1]) {
+ sendError(rh.gs(R.string.wear_action_tempt_max_bg_error))
+ return
+ }
+ message += if (low == high) rh.gs(R.string.wear_action_tempt_manual_message, action.low, action.duration)
+ else rh.gs(R.string.wear_action_tempt_manual_range_message, action.low, action.high, action.duration)
+ rxBus.send(
+ EventMobileToWear(
+ EventData.ConfirmAction(
+ title, message,
+ returnCommand = EventData.ActionTempTargetConfirmed(presetIsMGDL, action.duration, action.low, action.high)
+ )
+ )
+ )
+ }
+ }
+ }
+ }
+
+ private fun QuickWizardEntry.toWear(): EventData.QuickWizard.QuickWizardEntry =
+ EventData.QuickWizard.QuickWizardEntry(
+ guid = guid(),
+ buttonText = buttonText(),
+ carbs = carbs(),
+ validFrom = validFrom(),
+ validTo = validTo()
+ )
+
+ fun resendData(from: String) {
+ aapsLogger.debug(LTag.WEAR, "Sending data to wear from $from")
+ // SingleBg
+ iobCobCalculator.ads.lastBg()?.let { rxBus.send(EventMobileToWear(getSingleBG(it))) }
+ // Preferences
+ rxBus.send(
+ EventMobileToWear(
+ EventData.Preferences(
+ timeStamp = System.currentTimeMillis(),
+ wearControl = sp.getBoolean(R.string.key_wear_control, false),
+ unitsMgdl = profileFunction.getUnits() == GlucoseUnit.MGDL,
+ bolusPercentage = sp.getInt(R.string.key_boluswizard_percentage, 100),
+ maxCarbs = sp.getInt(R.string.key_treatmentssafety_maxcarbs, 48),
+ maxBolus = sp.getDouble(R.string.key_treatmentssafety_maxbolus, 3.0),
+ insulinButtonIncrement1 = sp.getDouble(R.string.key_insulin_button_increment_1, InsulinDialog.PLUS1_DEFAULT),
+ insulinButtonIncrement2 = sp.getDouble(R.string.key_insulin_button_increment_2, InsulinDialog.PLUS2_DEFAULT),
+ carbsButtonIncrement1 = sp.getInt(R.string.key_carbs_button_increment_1, CarbsDialog.FAV1_DEFAULT),
+ carbsButtonIncrement2 = sp.getInt(R.string.key_carbs_button_increment_2, CarbsDialog.FAV2_DEFAULT)
+ )
+ )
+ )
+ // QuickWizard
+ rxBus.send(
+ EventMobileToWear(
+ EventData.QuickWizard(
+ ArrayList(quickWizard.list().filter { it.forDevice(QuickWizardEntry.DEVICE_WATCH) }.map { it.toWear() })
+ )
+ )
+ )
+ // GraphData
+ val startTime = System.currentTimeMillis() - (60000 * 60 * 5.5).toLong()
+ rxBus.send(EventMobileToWear(EventData.GraphData(ArrayList(repository.compatGetBgReadingsDataFromTime(startTime, true).blockingGet().map { getSingleBG(it) }))))
+ // Treatments
+ sendTreatments()
+ // Status
+ // Keep status last. Wear start refreshing after status received
+ sendStatus()
+ }
+
+ private fun sendTreatments() {
+ val now = System.currentTimeMillis()
+ val startTimeWindow = now - (60000 * 60 * 5.5).toLong()
+ val basals = arrayListOf()
+ val temps = arrayListOf()
+ val boluses = arrayListOf()
+ val predictions = arrayListOf()
+ val profile = profileFunction.getProfile() ?: return
+ var beginBasalSegmentTime = startTimeWindow
+ var runningTime = startTimeWindow
+ var beginBasalValue = profile.getBasal(beginBasalSegmentTime)
+ var endBasalValue = beginBasalValue
+ var tb1 = iobCobCalculator.getTempBasalIncludingConvertedExtended(runningTime)
+ var tb2: TemporaryBasal?
+ var tbBefore = beginBasalValue
+ var tbAmount = beginBasalValue
+ var tbStart = runningTime
+ if (tb1 != null) {
+ val profileTB = profileFunction.getProfile(runningTime)
+ if (profileTB != null) {
+ tbAmount = tb1.convertedToAbsolute(runningTime, profileTB)
+ tbStart = runningTime
+ }
+ }
+ while (runningTime < now) {
+ val profileTB = profileFunction.getProfile(runningTime) ?: return
+ //basal rate
+ endBasalValue = profile.getBasal(runningTime)
+ if (endBasalValue != beginBasalValue) {
+ //push the segment we recently left
+ basals.add(EventData.TreatmentData.Basal(beginBasalSegmentTime, runningTime, beginBasalValue))
+
+ //begin new Basal segment
+ beginBasalSegmentTime = runningTime
+ beginBasalValue = endBasalValue
+ }
+
+ //temps
+ tb2 = iobCobCalculator.getTempBasalIncludingConvertedExtended(runningTime)
+ if (tb1 == null && tb2 == null) {
+ //no temp stays no temp
+ } else if (tb1 != null && tb2 == null) {
+ //temp is over -> push it
+ temps.add(EventData.TreatmentData.TempBasal(tbStart, tbBefore, runningTime, endBasalValue, tbAmount))
+ tb1 = null
+ } else if (tb1 == null && tb2 != null) {
+ //temp begins
+ tb1 = tb2
+ tbStart = runningTime
+ tbBefore = endBasalValue
+ tbAmount = tb1.convertedToAbsolute(runningTime, profileTB)
+ } else if (tb1 != null && tb2 != null) {
+ val currentAmount = tb2.convertedToAbsolute(runningTime, profileTB)
+ if (currentAmount != tbAmount) {
+ temps.add(EventData.TreatmentData.TempBasal(tbStart, tbBefore, runningTime, currentAmount, tbAmount))
+ tbStart = runningTime
+ tbBefore = tbAmount
+ tbAmount = currentAmount
+ tb1 = tb2
+ }
+ }
+ runningTime += (5 * 60 * 1000).toLong()
+ }
+ if (beginBasalSegmentTime != runningTime) {
+ //push the remaining segment
+ basals.add(EventData.TreatmentData.Basal(beginBasalSegmentTime, runningTime, beginBasalValue))
+ }
+ if (tb1 != null) {
+ tb2 = iobCobCalculator.getTempBasalIncludingConvertedExtended(now) //use "now" to express current situation
+ if (tb2 == null) {
+ //express the cancelled temp by painting it down one minute early
+ temps.add(EventData.TreatmentData.TempBasal(tbStart, tbBefore, now - 60 * 1000, endBasalValue, tbAmount))
+ } else {
+ //express currently running temp by painting it a bit into the future
+ val profileNow = profileFunction.getProfile(now)
+ val currentAmount = tb2.convertedToAbsolute(now, profileNow!!)
+ if (currentAmount != tbAmount) {
+ temps.add(EventData.TreatmentData.TempBasal(tbStart, tbBefore, now, tbAmount, tbAmount))
+ temps.add(EventData.TreatmentData.TempBasal(now, tbAmount, runningTime + 5 * 60 * 1000, currentAmount, currentAmount))
+ } else {
+ temps.add(EventData.TreatmentData.TempBasal(tbStart, tbBefore, runningTime + 5 * 60 * 1000, tbAmount, tbAmount))
+ }
+ }
+ } else {
+ tb2 = iobCobCalculator.getTempBasalIncludingConvertedExtended(now) //use "now" to express current situation
+ if (tb2 != null) {
+ //onset at the end
+ val profileTB = profileFunction.getProfile(runningTime)
+ val currentAmount = tb2.convertedToAbsolute(runningTime, profileTB!!)
+ temps.add(EventData.TreatmentData.TempBasal(now - 60 * 1000, endBasalValue, runningTime + 5 * 60 * 1000, currentAmount, currentAmount))
+ }
+ }
+ repository.getBolusesIncludingInvalidFromTime(startTimeWindow, true).blockingGet()
+ .stream()
+ .filter { (_, _, _, _, _, _, _, _, _, type) -> type !== Bolus.Type.PRIMING }
+ .forEach { (_, _, _, isValid, _, _, timestamp, _, amount, type) -> boluses.add(EventData.TreatmentData.Treatment(timestamp, amount, 0.0, type === Bolus.Type.SMB, isValid)) }
+ repository.getCarbsDataFromTimeExpanded(startTimeWindow, true).blockingGet()
+ .forEach { (_, _, _, isValid, _, _, timestamp, _, _, amount) -> boluses.add(EventData.TreatmentData.Treatment(timestamp, 0.0, amount, false, isValid)) }
+ val finalLastRun = loop.lastRun
+ if (sp.getBoolean("wear_predictions", true) && finalLastRun?.request?.hasPredictions == true && finalLastRun.constraintsProcessed != null) {
+ val predArray = finalLastRun.constraintsProcessed!!.predictions
+ .stream().map { bg: GlucoseValue -> GlucoseValueDataPoint(bg, defaultValueHelper, profileFunction, rh) }
+ .collect(Collectors.toList())
+ if (predArray.isNotEmpty())
+ for (bg in predArray) if (bg.data.value > 39)
+ predictions.add(
+ EventData.SingleBg(
+ timeStamp = bg.data.timestamp,
+ glucoseUnits = Constants.MGDL,
+ sgv = bg.data.value,
+ high = 0.0,
+ low = 0.0,
+ color = bg.color(null)
+ )
+ )
+ }
+ rxBus.send(EventMobileToWear(EventData.TreatmentData(temps, basals, boluses, predictions)))
+ }
+
+ private fun sendStatus() {
+ val profile = profileFunction.getProfile()
+ var status = rh.gs(R.string.noprofile)
+ var iobSum = ""
+ var iobDetail = ""
+ var cobString = ""
+ var currentBasal = ""
+ var bgiString = ""
+ if (profile != null) {
+ val bolusIob = iobCobCalculator.calculateIobFromBolus().round()
+ val basalIob = iobCobCalculator.calculateIobFromTempBasalsIncludingConvertedExtended().round()
+ iobSum = DecimalFormatter.to2Decimal(bolusIob.iob + basalIob.basaliob)
+ iobDetail = "(${DecimalFormatter.to2Decimal(bolusIob.iob)}|${DecimalFormatter.to2Decimal(basalIob.basaliob)})"
+ cobString = iobCobCalculator.getCobInfo(false, "WatcherUpdaterService").generateCOBString()
+ currentBasal = iobCobCalculator.getTempBasalIncludingConvertedExtended(System.currentTimeMillis())?.toStringShort() ?: rh.gs(R.string.pump_basebasalrate, profile.getBasal())
+
+ //bgi
+ val bgi = -(bolusIob.activity + basalIob.activity) * 5 * Profile.fromMgdlToUnits(profile.getIsfMgdl(), profileFunction.getUnits())
+ bgiString = "" + (if (bgi >= 0) "+" else "") + DecimalFormatter.to1Decimal(bgi)
+ status = generateStatusString(profile, currentBasal, iobSum, iobDetail, bgiString)
+ }
+
+ //batteries
+ val phoneBattery = receiverStatusStore.batteryLevel
+ val rigBattery = nsDeviceStatus.uploaderStatus.trim { it <= ' ' }
+ //OpenAPS status
+ val openApsStatus =
+ if (config.APS) loop.lastRun?.let { if (it.lastTBREnact != 0L) it.lastTBREnact else -1 } ?: -1
+ else nsDeviceStatus.openApsTimestamp
+
+ rxBus.send(
+ EventMobileToWear(
+ EventData.Status(
+ externalStatus = status,
+ iobSum = iobSum,
+ iobDetail = iobDetail,
+ detailedIob = sp.getBoolean(R.string.key_wear_detailediob, false),
+ cob = cobString,
+ currentBasal = currentBasal,
+ battery = phoneBattery.toString(),
+ rigBattery = rigBattery,
+ openApsStatus = openApsStatus,
+ bgi = bgiString,
+ showBgi = sp.getBoolean(R.string.key_wear_showbgi, false),
+ batteryLevel = if (phoneBattery >= 30) 1 else 0
+ )
+ )
+ )
+ }
+
+ private fun deltaString(deltaMGDL: Double, deltaMMOL: Double, units: GlucoseUnit): String {
+ val detailed = sp.getBoolean(R.string.key_wear_detailed_delta, false)
+ var deltaString = if (deltaMGDL >= 0) "+" else "-"
+ deltaString += if (units == GlucoseUnit.MGDL) {
+ if (detailed) DecimalFormatter.to1Decimal(abs(deltaMGDL)) else DecimalFormatter.to0Decimal(abs(deltaMGDL))
+ } else {
+ if (detailed) DecimalFormatter.to2Decimal(abs(deltaMMOL)) else DecimalFormatter.to1Decimal(abs(deltaMMOL))
+ }
+ return deltaString
+ }
+
+ private fun getSingleBG(glucoseValue: GlucoseValue): EventData.SingleBg {
+ val glucoseStatus = glucoseStatusProvider.getGlucoseStatusData(true)
+ val units = profileFunction.getUnits()
+ val lowLine = Profile.toMgdl(defaultValueHelper.determineLowLine(), units)
+ val highLine = Profile.toMgdl(defaultValueHelper.determineHighLine(), units)
+
+ return EventData.SingleBg(
+ timeStamp = glucoseValue.timestamp,
+ sgvString = glucoseValue.valueToUnitsString(units),
+ glucoseUnits = units.asText,
+ slopeArrow = trendCalculator.getTrendArrow(glucoseValue).symbol,
+ delta = glucoseStatus?.let { deltaString(it.delta, it.delta * Constants.MGDL_TO_MMOLL, units) } ?: "--",
+ avgDelta = glucoseStatus?.let { deltaString(it.shortAvgDelta, it.shortAvgDelta * Constants.MGDL_TO_MMOLL, units) } ?: "--",
+ sgvLevel = if (glucoseValue.value > highLine) 1L else if (glucoseValue.value < lowLine) -1L else 0L,
+ sgv = glucoseValue.value,
+ high = highLine,
+ low = lowLine,
+ color = 0
+ )
+ }
+
+ //Check for Temp-Target:
+ private
+ val targetsStatus: String
+ get() {
+ var ret = ""
+ if (!config.APS) {
+ return "Targets only apply in APS mode!"
+ }
+ val profile = profileFunction.getProfile() ?: return "No profile set :("
+ //Check for Temp-Target:
+ val tempTarget = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet()
+ if (tempTarget is ValueWrapper.Existing) {
+ ret += "Temp Target: " + Profile.toTargetRangeString(tempTarget.value.lowTarget, tempTarget.value.lowTarget, GlucoseUnit.MGDL, profileFunction.getUnits())
+ ret += "\nuntil: " + dateUtil.timeString(tempTarget.value.end)
+ ret += "\n\n"
+ }
+ ret += "DEFAULT RANGE: "
+ ret += Profile.fromMgdlToUnits(profile.getTargetLowMgdl(), profileFunction.getUnits()).toString() + " - " + Profile.fromMgdlToUnits(
+ profile.getTargetHighMgdl(),
+ profileFunction.getUnits()
+ )
+ ret += " target: " + Profile.fromMgdlToUnits(profile.getTargetMgdl(), profileFunction.getUnits())
+ return ret
+ }
+
+ private
+ val oAPSResultStatus: String
+ get() {
+ var ret = ""
+ if (!config.APS)
+ return "Only apply in APS mode!"
+ val usedAPS = activePlugin.activeAPS
+ val result = usedAPS.lastAPSResult ?: return "Last result not available!"
+ ret += if (!result.isChangeRequested) {
+ rh.gs(R.string.nochangerequested) + "\n"
+ } else if (result.rate == 0.0 && result.duration == 0) {
+ rh.gs(R.string.canceltemp) + "\n"
+ } else {
+ rh.gs(R.string.rate) + ": " + DecimalFormatter.to2Decimal(result.rate) + " U/h " +
+ "(" + DecimalFormatter.to2Decimal(result.rate / activePlugin.activePump.baseBasalRate * 100) + "%)\n" +
+ rh.gs(R.string.duration) + ": " + DecimalFormatter.to0Decimal(result.duration.toDouble()) + " min\n"
+ }
+ ret += "\n" + rh.gs(R.string.reason) + ": " + result.reason
+ return ret
+ }
+
+ // decide if enabled/disabled closed/open; what Plugin as APS?
+ private
+ val loopStatus: String
+ get() {
+ var ret = ""
+ // decide if enabled/disabled closed/open; what Plugin as APS?
+ if ((loop as PluginBase).isEnabled()) {
+ ret += if (constraintChecker.isClosedLoopAllowed().value()) {
+ "CLOSED LOOP\n"
+ } else {
+ "OPEN LOOP\n"
+ }
+ val aps = activePlugin.activeAPS
+ ret += "APS: " + (aps as PluginBase).name
+ val lastRun = loop.lastRun
+ if (lastRun != null) {
+ ret += "\nLast Run: " + dateUtil.timeString(lastRun.lastAPSRun)
+ if (lastRun.lastTBREnact != 0L) ret += "\nLast Enact: " + dateUtil.timeString(lastRun.lastTBREnact)
+ }
+ } else {
+ ret += "LOOP DISABLED\n"
+ }
+ return ret
+ }
+
+ private fun isOldData(historyList: List): Boolean {
+ val startsYesterday = activePlugin.activePump.pumpDescription.supportsTDDs
+ val df: DateFormat = SimpleDateFormat("dd.MM.", Locale.getDefault())
+ return historyList.size < 3 || df.format(Date(historyList[0].timestamp)) != df.format(Date(System.currentTimeMillis() - if (startsYesterday) 1000 * 60 * 60 * 24 else 0))
+ }
+
+ private fun getTDDList(returnDummies: MutableList): MutableList {
+ var historyList = repository.getLastTotalDailyDoses(10, false).blockingGet().toMutableList()
+ //var historyList = databaseHelper.getTDDs().toMutableList()
+ historyList = historyList.subList(0, min(10, historyList.size))
+ //fill single gaps - only needed for Dana*R data
+ val dummies: MutableList = returnDummies
+ val df: DateFormat = SimpleDateFormat("dd.MM.", Locale.getDefault())
+ for (i in 0 until historyList.size - 1) {
+ val elem1 = historyList[i]
+ val elem2 = historyList[i + 1]
+ if (df.format(Date(elem1.timestamp)) != df.format(Date(elem2.timestamp + 25 * 60 * 60 * 1000))) {
+ val dummy = TotalDailyDose(timestamp = elem1.timestamp - T.hours(24).msecs(), bolusAmount = elem1.bolusAmount / 2, basalAmount = elem1.basalAmount / 2)
+ dummies.add(dummy)
+ elem1.basalAmount /= 2.0
+ elem1.bolusAmount /= 2.0
+ }
+ }
+ historyList.addAll(dummies)
+ historyList.sortWith { lhs, rhs -> (rhs.timestamp - lhs.timestamp).toInt() }
+ return historyList
+ }
+
+ private fun generateTDDMessage(historyList: MutableList, dummies: MutableList): String {
+ val profile = profileFunction.getProfile() ?: return "No profile loaded :("
+ if (historyList.isEmpty()) {
+ return "No history data!"
+ }
+ val df: DateFormat = SimpleDateFormat("dd.MM.", Locale.getDefault())
+ var message = ""
+ val refTDD = profile.baseBasalSum() * 2
+ if (df.format(Date(historyList[0].timestamp)) == df.format(Date())) {
+ val tdd = historyList[0].total
+ historyList.removeAt(0)
+ message += "Today: " + DecimalFormatter.to2Decimal(tdd) + "U " + (DecimalFormatter.to0Decimal(100 * tdd / refTDD) + "%") + "\n"
+ message += "\n"
+ }
+ var weighted03 = 0.0
+ var weighted05 = 0.0
+ var weighted07 = 0.0
+ historyList.reverse()
+ for ((i, record) in historyList.withIndex()) {
+ val tdd = record.total
+ if (i == 0) {
+ weighted03 = tdd
+ weighted05 = tdd
+ weighted07 = tdd
+ } else {
+ weighted07 = weighted07 * 0.3 + tdd * 0.7
+ weighted05 = weighted05 * 0.5 + tdd * 0.5
+ weighted03 = weighted03 * 0.7 + tdd * 0.3
+ }
+ }
+ message += "weighted:\n"
+ message += "0.3: " + DecimalFormatter.to2Decimal(weighted03) + "U " + (DecimalFormatter.to0Decimal(100 * weighted03 / refTDD) + "%") + "\n"
+ message += "0.5: " + DecimalFormatter.to2Decimal(weighted05) + "U " + (DecimalFormatter.to0Decimal(100 * weighted05 / refTDD) + "%") + "\n"
+ message += "0.7: " + DecimalFormatter.to2Decimal(weighted07) + "U " + (DecimalFormatter.to0Decimal(100 * weighted07 / refTDD) + "%") + "\n"
+ message += "\n"
+ historyList.reverse()
+ //add TDDs:
+ for (record in historyList) {
+ val tdd = record.total
+ message += df.format(Date(record.timestamp)) + " " + DecimalFormatter.to2Decimal(tdd) + "U " + (DecimalFormatter.to0Decimal(100 * tdd / refTDD) + "%") + (if (dummies.contains(
+ record
+ )
+ ) "x" else "") + "\n"
+ }
+ return message
+ }
+
+ private fun generateStatusString(profile: Profile?, currentBasal: String, iobSum: String, iobDetail: String, bgiString: String): String {
+ var status = ""
+ profile ?: return rh.gs(R.string.noprofile)
+ if (!(loop as PluginBase).isEnabled()) status += rh.gs(R.string.disabledloop) + "\n"
+
+ val iobString =
+ if (sp.getBoolean(R.string.key_wear_detailediob, false)) "$iobSum $iobDetail"
+ else iobSum + "U"
+
+ status += "$currentBasal $iobString"
+
+ //add BGI if shown, otherwise return
+ if (sp.getBoolean(R.string.key_wear_showbgi, false)) status += " $bgiString"
+ return status
+ }
+
+ private fun doTempTarget(command: EventData.ActionTempTargetConfirmed) {
+ if (command.duration != 0) {
+ disposable += repository.runTransactionForResult(
+ InsertAndCancelCurrentTemporaryTargetTransaction(
+ timestamp = System.currentTimeMillis(),
+ duration = TimeUnit.MINUTES.toMillis(command.duration.toLong()),
+ reason = TemporaryTarget.Reason.WEAR,
+ lowTarget = Profile.toMgdl(command.low, profileFunction.getUnits()),
+ highTarget = Profile.toMgdl(command.high, 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(
+ UserEntry.Action.TT, UserEntry.Sources.Wear,
+ ValueWithUnit.TherapyEventTTReason(TemporaryTarget.Reason.WEAR),
+ ValueWithUnit.fromGlucoseUnit(command.low, profileFunction.getUnits().asText),
+ ValueWithUnit.fromGlucoseUnit(command.high, profileFunction.getUnits().asText).takeIf { command.low != command.high },
+ ValueWithUnit.Minute(command.duration)
+ )
+ } else {
+ disposable += repository.runTransactionForResult(CancelCurrentTemporaryTargetIfAnyTransaction(System.currentTimeMillis()))
+ .subscribe({ result ->
+ result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated temp target $it") }
+ }, {
+ aapsLogger.error(LTag.DATABASE, "Error while saving temporary target", it)
+ })
+ uel.log(
+ UserEntry.Action.CANCEL_TT, UserEntry.Sources.Wear,
+ ValueWithUnit.TherapyEventTTReason(TemporaryTarget.Reason.WEAR)
+ )
+ }
+ }
+
+ private fun doBolus(amount: Double, carbs: Int, carbsTime: Long?, carbsDuration: Int) {
+ val detailedBolusInfo = DetailedBolusInfo()
+ detailedBolusInfo.insulin = amount
+ detailedBolusInfo.carbs = carbs.toDouble()
+ detailedBolusInfo.bolusType = DetailedBolusInfo.BolusType.NORMAL
+ detailedBolusInfo.carbsTimestamp = carbsTime
+ detailedBolusInfo.carbsDuration = T.hours(carbsDuration.toLong()).msecs()
+ if (detailedBolusInfo.insulin > 0 || detailedBolusInfo.carbs > 0) {
+ val action = when {
+ amount == 0.0 -> UserEntry.Action.CARBS
+ carbs == 0 -> UserEntry.Action.BOLUS
+ carbsDuration > 0 -> UserEntry.Action.EXTENDED_CARBS
+ else -> UserEntry.Action.TREATMENT
+ }
+ uel.log(action, UserEntry.Sources.Wear,
+ ValueWithUnit.Insulin(amount).takeIf { amount != 0.0 },
+ ValueWithUnit.Gram(carbs).takeIf { carbs != 0 },
+ ValueWithUnit.Hour(carbsDuration).takeIf { carbsDuration != 0 })
+ commandQueue.bolus(detailedBolusInfo, object : Callback() {
+ override fun run() {
+ if (!result.success)
+ sendError(rh.gs(R.string.treatmentdeliveryerror) + "\n" + result.comment)
+ }
+ })
+ }
+ }
+
+ private fun doFillBolus(amount: Double) {
+ val detailedBolusInfo = DetailedBolusInfo()
+ detailedBolusInfo.insulin = amount
+ detailedBolusInfo.bolusType = DetailedBolusInfo.BolusType.PRIMING
+ uel.log(
+ UserEntry.Action.PRIME_BOLUS, UserEntry.Sources.Wear,
+ ValueWithUnit.Insulin(amount).takeIf { amount != 0.0 })
+ commandQueue.bolus(detailedBolusInfo, object : Callback() {
+ override fun run() {
+ if (!result.success) {
+ sendError(rh.gs(R.string.treatmentdeliveryerror) + "\n" + result.comment)
+ }
+ }
+ })
+ }
+
+ private fun doECarbs(carbs: Int, carbsTime: Long, duration: Int) {
+ uel.log(if (duration == 0) UserEntry.Action.CARBS else UserEntry.Action.EXTENDED_CARBS, UserEntry.Sources.Wear,
+ ValueWithUnit.Timestamp(carbsTime),
+ ValueWithUnit.Gram(carbs),
+ ValueWithUnit.Hour(duration).takeIf { duration != 0 })
+ doBolus(0.0, carbs, carbsTime, duration)
+ }
+
+ private fun doProfileSwitch(command: EventData.ActionProfileSwitchConfirmed) {
+ //check for validity
+ if (command.percentage < Constants.CPP_MIN_PERCENTAGE || command.percentage > Constants.CPP_MAX_PERCENTAGE)
+ return
+ if (command.timeShift < 0 || command.timeShift > 23)
+ return
+ profileFunction.getProfile() ?: return
+ //send profile to pump
+ uel.log(
+ UserEntry.Action.PROFILE_SWITCH, UserEntry.Sources.Wear,
+ ValueWithUnit.Percent(command.percentage),
+ ValueWithUnit.Hour(command.timeShift).takeIf { command.timeShift != 0 })
+ profileFunction.createProfileSwitch(0, command.percentage, command.timeShift)
+ }
+
+ @Synchronized private fun sendError(errorMessage: String) {
+ rxBus.send(EventMobileToWear(EventData.ConfirmAction(rh.gs(R.string.error), errorMessage, returnCommand = EventData.Error(dateUtil.now())))) // ignore return path
+ }
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/DataLayerListenerServiceMobile.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/DataLayerListenerServiceMobile.kt
new file mode 100644
index 0000000000..4292307c59
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/DataLayerListenerServiceMobile.kt
@@ -0,0 +1,201 @@
+package info.nightscout.androidaps.plugins.general.wear.wearintegration
+
+import android.os.Handler
+import android.os.HandlerThread
+import com.google.android.gms.tasks.Tasks
+import com.google.android.gms.wearable.*
+import dagger.android.AndroidInjection
+import info.nightscout.androidaps.R
+import info.nightscout.androidaps.database.AppRepository
+import info.nightscout.androidaps.events.EventMobileToWear
+import info.nightscout.androidaps.interfaces.*
+import info.nightscout.androidaps.plugins.bus.RxBus
+import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus
+import info.nightscout.androidaps.plugins.general.wear.WearPlugin
+import info.nightscout.androidaps.plugins.general.wear.events.EventWearUpdateGui
+import info.nightscout.androidaps.receivers.ReceiverStatusStore
+import info.nightscout.androidaps.utils.DefaultValueHelper
+import info.nightscout.androidaps.utils.FabricPrivacy
+import info.nightscout.androidaps.utils.rx.AapsSchedulers
+import info.nightscout.androidaps.utils.wizard.QuickWizard
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.shared.logging.LTag
+import info.nightscout.shared.sharedPreferences.SP
+import info.nightscout.shared.weardata.EventData
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.kotlin.plusAssign
+import kotlinx.coroutines.*
+import kotlinx.coroutines.tasks.await
+import javax.inject.Inject
+
+class DataLayerListenerServiceMobile : WearableListenerService() {
+
+ @Inject lateinit var aapsLogger: AAPSLogger
+ @Inject lateinit var fabricPrivacy: FabricPrivacy
+ @Inject lateinit var profileFunction: ProfileFunction
+ @Inject lateinit var iobCobCalculator: IobCobCalculator
+ @Inject lateinit var rh: ResourceHelper
+ @Inject lateinit var loop: Loop
+ @Inject lateinit var wearPlugin: WearPlugin
+ @Inject lateinit var sp: SP
+ @Inject lateinit var quickWizard: QuickWizard
+ @Inject lateinit var config: Config
+ @Inject lateinit var nsDeviceStatus: NSDeviceStatus
+ @Inject lateinit var receiverStatusStore: ReceiverStatusStore
+ @Inject lateinit var repository: AppRepository
+ @Inject lateinit var defaultValueHelper: DefaultValueHelper
+ @Inject lateinit var activePlugin: ActivePlugin
+ @Inject lateinit var rxBus: RxBus
+ @Inject lateinit var aapsSchedulers: AapsSchedulers
+
+ private val dataClient by lazy { Wearable.getDataClient(this) }
+ private val messageClient by lazy { Wearable.getMessageClient(this) }
+ private val capabilityClient by lazy { Wearable.getCapabilityClient(this) }
+ //private val nodeClient by lazy { Wearable.getNodeClient(this) }
+
+ private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
+ private var handler = Handler(HandlerThread(this::class.simpleName + "Handler").also { it.start() }.looper)
+
+ private val disposable = CompositeDisposable()
+
+ private val rxPath get() = getString(R.string.path_rx_bridge)
+
+ override fun onCreate() {
+ AndroidInjection.inject(this)
+ super.onCreate()
+ aapsLogger.debug(LTag.WEAR, "onCreate")
+ handler.post { updateTranscriptionCapability() }
+ disposable += rxBus
+ .toObservable(EventMobileToWear::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe { sendMessage(rxPath, it.payload.serialize()) }
+ }
+
+ override fun onCapabilityChanged(p0: CapabilityInfo) {
+ super.onCapabilityChanged(p0)
+ handler.post { updateTranscriptionCapability() }
+ aapsLogger.debug(LTag.WEAR, "onCapabilityChanged: ${p0.name} ${p0.nodes.joinToString(", ") { it.displayName + "(" + it.id + ")" }}")
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ disposable.clear()
+ scope.cancel()
+ }
+
+ @Suppress("ControlFlowWithEmptyBody", "UNUSED_EXPRESSION")
+ override fun onDataChanged(dataEvents: DataEventBuffer) {
+ //aapsLogger.debug(LTag.WEAR, "onDataChanged")
+
+ if (wearPlugin.isEnabled()) {
+ dataEvents.forEach { event ->
+ if (event.type == DataEvent.TYPE_CHANGED) {
+ val path = event.dataItem.uri.path
+
+ aapsLogger.debug(LTag.WEAR, "onDataChanged: Path: $path, EventDataItem=${event.dataItem}")
+ try {
+ when (path) {
+ }
+ } catch (exception: Exception) {
+ aapsLogger.error(LTag.WEAR, "Message failed", exception)
+ }
+ }
+ }
+ }
+ super.onDataChanged(dataEvents)
+ }
+
+ override fun onMessageReceived(messageEvent: MessageEvent) {
+ super.onMessageReceived(messageEvent)
+
+ if (wearPlugin.isEnabled()) {
+ when (messageEvent.path) {
+ rxPath -> {
+ aapsLogger.debug(LTag.WEAR, "onMessageReceived rxPath: ${String(messageEvent.data)}")
+ val command = EventData.deserialize(String(messageEvent.data))
+ rxBus.send(command.also { it.sourceNodeId = messageEvent.sourceNodeId })
+ }
+ }
+ }
+ }
+
+ private var transcriptionNodeId: String? = null
+
+ private fun updateTranscriptionCapability() {
+ try {
+ val capabilityInfo: CapabilityInfo = Tasks.await(
+ capabilityClient.getCapability(WEAR_CAPABILITY, CapabilityClient.FILTER_REACHABLE)
+ )
+ aapsLogger.debug(LTag.WEAR, "Nodes: ${capabilityInfo.nodes.joinToString(", ") { it.displayName + "(" + it.id + ")" }}")
+ val bestNode = pickBestNodeId(capabilityInfo.nodes)
+ transcriptionNodeId = bestNode?.id
+ wearPlugin.connectedDevice = bestNode?.displayName ?: rh.gs(R.string.no_watch_connected)
+ rxBus.send(EventWearUpdateGui())
+ aapsLogger.debug(LTag.WEAR, "Selected node: ${bestNode?.displayName} $transcriptionNodeId")
+ rxBus.send(EventMobileToWear(EventData.ActionPing(System.currentTimeMillis())))
+ rxBus.send(EventData.ActionResendData("WatchUpdaterService"))
+ } catch (e: Exception) {
+ fabricPrivacy.logCustom("WearOS_unsupported")
+ }
+ }
+
+ // Find a nearby node or pick one arbitrarily
+ private fun pickBestNodeId(nodes: Set): Node? =
+ nodes.firstOrNull { it.isNearby } ?: nodes.firstOrNull()
+
+ @Suppress("unused")
+ private fun sendData(path: String, vararg params: DataMap) {
+ if (wearPlugin.isEnabled()) {
+ scope.launch {
+ try {
+ for (dm in params) {
+ val request = PutDataMapRequest.create(path).apply {
+ dataMap.putAll(dm)
+ }
+ .asPutDataRequest()
+ .setUrgent()
+
+ val result = dataClient.putDataItem(request).await()
+ aapsLogger.debug(LTag.WEAR, "sendData: ${result.uri} ${params.joinToString()}")
+ }
+ } catch (cancellationException: CancellationException) {
+ throw cancellationException
+ } catch (exception: Exception) {
+ aapsLogger.error(LTag.WEAR, "DataItem failed: $exception")
+ }
+ }
+ }
+ }
+
+ private fun sendMessage(path: String, data: String?) {
+ aapsLogger.debug(LTag.WEAR, "sendMessage: $path $data")
+ transcriptionNodeId?.also { nodeId ->
+ messageClient
+ .sendMessage(nodeId, path, data?.toByteArray() ?: byteArrayOf()).apply {
+ addOnSuccessListener { }
+ addOnFailureListener {
+ aapsLogger.debug(LTag.WEAR, "sendMessage: $path failure")
+ }
+ }
+ }
+ }
+
+ @Suppress("unused")
+ private fun sendMessage(path: String, data: ByteArray) {
+ aapsLogger.debug(LTag.WEAR, "sendMessage: $path")
+ transcriptionNodeId?.also { nodeId ->
+ messageClient
+ .sendMessage(nodeId, path, data).apply {
+ addOnSuccessListener { }
+ addOnFailureListener {
+ aapsLogger.debug(LTag.WEAR, "sendMessage: $path failure")
+ }
+ }
+ }
+ }
+
+ companion object {
+
+ const val WEAR_CAPABILITY = "androidaps_wear"
+ }
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/SendToDataLayerThread.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/SendToDataLayerThread.java
deleted file mode 100644
index ee4c30e0d1..0000000000
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/SendToDataLayerThread.java
+++ /dev/null
@@ -1,139 +0,0 @@
-package info.nightscout.androidaps.plugins.general.wear.wearintegration;
-
-import android.os.AsyncTask;
-import android.util.Log;
-
-import com.google.android.gms.common.api.GoogleApiClient;
-import com.google.android.gms.wearable.DataApi;
-import com.google.android.gms.wearable.DataMap;
-import com.google.android.gms.wearable.Node;
-import com.google.android.gms.wearable.NodeApi;
-import com.google.android.gms.wearable.PutDataMapRequest;
-import com.google.android.gms.wearable.PutDataRequest;
-import com.google.android.gms.wearable.Wearable;
-
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.ReentrantLock;
-
-/**
- * Created by emmablack on 12/26/14.
- */
-class SendToDataLayerThread extends AsyncTask {
- private final GoogleApiClient googleApiClient;
- private static final String TAG = "SendToDataLayerThread";
- private final String path;
- private final String logPrefix = ""; // "WR: ";
- private static int concurrency = 0;
- private static int state = 0;
- private static final ReentrantLock lock = new ReentrantLock();
- private static long lastlock = 0;
- private static final boolean testlockup = false; // always false in production
-
-
- SendToDataLayerThread(String path, GoogleApiClient pGoogleApiClient) {
- // Log.d(TAG, logPrefix + "SendToDataLayerThread: " + path);
- this.path = path;
- googleApiClient = pGoogleApiClient;
- }
-
-
- @Override
- protected void onPreExecute() {
- concurrency++;
- if ((concurrency > 12) || ((concurrency > 3 && (lastlock != 0) && (tsl() - lastlock) > 300000))) {
- // error if 9 concurrent threads or lock held for >5 minutes with concurrency of 4
- final String err = "Wear Integration deadlock detected!! " + ((lastlock != 0) ? "locked" : "") + " state:"
- + state + " @" + hourMinuteString(tsl());
- // Home.toaststaticnext(err);
- Log.e(TAG, logPrefix + err);
- }
- if (concurrency < 0)
- Log.d(TAG, logPrefix + "Wear Integration impossible concurrency!!");
-
- Log.d(TAG, logPrefix + "SendDataToLayerThread pre-execute concurrency: " + concurrency);
- }
-
-
- @Override
- protected Void doInBackground(DataMap... params) {
- if (testlockup) {
- try {
- Log.e(TAG, logPrefix + "WARNING RUNNING TEST LOCK UP CODE - NEVER FOR PRODUCTION");
- Thread.sleep(1000000); // DEEEBBUUGGGG
- } catch (Exception e) {
- }
- }
- sendToWear(params);
- concurrency--;
- Log.d(TAG, logPrefix + "SendDataToLayerThread post-execute concurrency: " + concurrency);
- return null;
- }
-
-
- // Debug function to expose where it might be locking up
- private synchronized void sendToWear(final DataMap... params) {
- if (!lock.tryLock()) {
- Log.d(TAG, logPrefix + "Concurrent access - waiting for thread unlock");
- lock.lock(); // enforce single threading
- Log.d(TAG, logPrefix + "Thread unlocked - proceeding");
- }
- lastlock = tsl();
- try {
- if (state != 0) {
- Log.e(TAG, logPrefix + "WEAR STATE ERROR: state=" + state);
- }
- state = 1;
- final NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(googleApiClient).await(15,
- TimeUnit.SECONDS);
-
- Log.d(TAG, logPrefix + "Nodes: " + nodes);
-
- state = 2;
- for (Node node : nodes.getNodes()) {
- state = 3;
- for (DataMap dataMap : params) {
- state = 4;
- PutDataMapRequest putDMR = PutDataMapRequest.create(path);
- state = 5;
- putDMR.getDataMap().putAll(dataMap);
- putDMR.setUrgent();
- state = 6;
- PutDataRequest request = putDMR.asPutDataRequest();
- state = 7;
- DataApi.DataItemResult result = Wearable.DataApi.putDataItem(googleApiClient, request).await(15,
- TimeUnit.SECONDS);
- state = 8;
- if (result.getStatus().isSuccess()) {
- Log.d(TAG, logPrefix + "DataMap: " + dataMap + " sent to: " + node.getDisplayName());
- } else {
- Log.e(TAG, logPrefix + "ERROR: failed to send DataMap");
- result = Wearable.DataApi.putDataItem(googleApiClient, request).await(30, TimeUnit.SECONDS);
- if (result.getStatus().isSuccess()) {
- Log.d(TAG, logPrefix + "DataMap retry: " + dataMap + " sent to: " + node.getDisplayName());
- } else {
- Log.e(TAG, logPrefix + "ERROR on retry: failed to send DataMap: "
- + result.getStatus().toString());
- }
- }
- state = 9;
- }
- }
- state = 0;
- } catch (Exception e) {
- Log.e(TAG, logPrefix + "Got exception in sendToWear: " + e.toString());
- } finally {
- lastlock = 0;
- lock.unlock();
- }
- }
-
-
- private static long tsl() {
- return System.currentTimeMillis();
- }
-
-
- private static String hourMinuteString(long timestamp) {
- return android.text.format.DateFormat.format("kk:mm", timestamp).toString();
- }
-}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/WatchUpdaterService.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/WatchUpdaterService.java
deleted file mode 100644
index 1ce5c509bb..0000000000
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/WatchUpdaterService.java
+++ /dev/null
@@ -1,806 +0,0 @@
-package info.nightscout.androidaps.plugins.general.wear.wearintegration;
-
-import android.content.Intent;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import com.google.android.gms.common.ConnectionResult;
-import com.google.android.gms.common.api.GoogleApiClient;
-import com.google.android.gms.wearable.CapabilityApi;
-import com.google.android.gms.wearable.CapabilityInfo;
-import com.google.android.gms.wearable.DataMap;
-import com.google.android.gms.wearable.MessageEvent;
-import com.google.android.gms.wearable.Node;
-import com.google.android.gms.wearable.PutDataMapRequest;
-import com.google.android.gms.wearable.PutDataRequest;
-import com.google.android.gms.wearable.Wearable;
-import com.google.android.gms.wearable.WearableListenerService;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-import javax.inject.Inject;
-
-import dagger.android.AndroidInjection;
-import info.nightscout.androidaps.Constants;
-import info.nightscout.androidaps.R;
-import info.nightscout.androidaps.data.IobTotal;
-import info.nightscout.androidaps.database.AppRepository;
-import info.nightscout.androidaps.database.entities.Bolus;
-import info.nightscout.androidaps.database.entities.GlucoseValue;
-import info.nightscout.androidaps.database.entities.TemporaryBasal;
-import info.nightscout.androidaps.extensions.GlucoseValueExtensionKt;
-import info.nightscout.androidaps.extensions.TemporaryBasalExtensionKt;
-import info.nightscout.androidaps.interfaces.ActivePlugin;
-import info.nightscout.androidaps.interfaces.Config;
-import info.nightscout.androidaps.interfaces.GlucoseUnit;
-import info.nightscout.androidaps.interfaces.IobCobCalculator;
-import info.nightscout.androidaps.interfaces.Loop;
-import info.nightscout.androidaps.interfaces.PluginBase;
-import info.nightscout.androidaps.interfaces.Profile;
-import info.nightscout.androidaps.interfaces.ProfileFunction;
-import info.nightscout.shared.logging.AAPSLogger;
-import info.nightscout.shared.logging.LTag;
-import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin;
-import info.nightscout.androidaps.plugins.bus.RxBus;
-import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus;
-import info.nightscout.androidaps.plugins.general.overview.graphExtensions.GlucoseValueDataPoint;
-import info.nightscout.androidaps.plugins.general.wear.WearPlugin;
-import info.nightscout.androidaps.plugins.general.wear.events.EventWearConfirmAction;
-import info.nightscout.androidaps.plugins.general.wear.events.EventWearInitiateAction;
-import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus;
-import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProvider;
-import info.nightscout.androidaps.receivers.ReceiverStatusStore;
-import info.nightscout.androidaps.utils.DecimalFormatter;
-import info.nightscout.androidaps.utils.DefaultValueHelper;
-import info.nightscout.androidaps.utils.TrendCalculator;
-import info.nightscout.androidaps.utils.resources.ResourceHelper;
-import info.nightscout.shared.sharedPreferences.SP;
-
-public class WatchUpdaterService extends WearableListenerService implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
- @Inject public GlucoseStatusProvider glucoseStatusProvider;
- @Inject public AAPSLogger aapsLogger;
- @Inject public WearPlugin wearPlugin;
- @Inject public ResourceHelper rh;
- @Inject public SP sp;
- @Inject public RxBus rxBus;
- @Inject public ProfileFunction profileFunction;
- @Inject public DefaultValueHelper defaultValueHelper;
- @Inject public NSDeviceStatus nsDeviceStatus;
- @Inject public ActivePlugin activePlugin;
- @Inject public Loop loop;
- @Inject public IobCobCalculator iobCobCalculator;
- @Inject public AppRepository repository;
- @Inject ReceiverStatusStore receiverStatusStore;
- @Inject Config config;
- @Inject public TrendCalculator trendCalculator;
-
- public static final String ACTION_RESEND = WatchUpdaterService.class.getName().concat(".Resend");
- public static final String ACTION_OPEN_SETTINGS = WatchUpdaterService.class.getName().concat(".OpenSettings");
- public static final String ACTION_SEND_STATUS = WatchUpdaterService.class.getName().concat(".SendStatus");
- public static final String ACTION_SEND_BASALS = WatchUpdaterService.class.getName().concat(".SendBasals");
- public static final String ACTION_SEND_BOLUSPROGRESS = WatchUpdaterService.class.getName().concat(".BolusProgress");
- public static final String ACTION_SEND_ACTIONCONFIRMATIONREQUEST = WatchUpdaterService.class.getName().concat(".ActionConfirmationRequest");
- public static final String ACTION_SEND_CHANGECONFIRMATIONREQUEST = WatchUpdaterService.class.getName().concat(".ChangeConfirmationRequest");
- public static final String ACTION_CANCEL_NOTIFICATION = WatchUpdaterService.class.getName().concat(".CancelNotification");
-
- private GoogleApiClient googleApiClient;
- public static final String WEARABLE_DATA_PATH = "/nightscout_watch_data";
- public static final String WEARABLE_RESEND_PATH = "/nightscout_watch_data_resend";
- private static final String WEARABLE_CANCELBOLUS_PATH = "/nightscout_watch_cancel_bolus";
- public static final String WEARABLE_CONFIRM_ACTIONSTRING_PATH = "/nightscout_watch_confirmactionstring";
- public static final String WEARABLE_INITIATE_ACTIONSTRING_PATH = "/nightscout_watch_initiateactionstring";
-
- private static final String OPEN_SETTINGS_PATH = "/openwearsettings";
- private static final String NEW_STATUS_PATH = "/sendstatustowear";
- private static final String NEW_PREFERENCES_PATH = "/sendpreferencestowear";
- public static final String BASAL_DATA_PATH = "/nightscout_watch_basal";
- public static final String BOLUS_PROGRESS_PATH = "/nightscout_watch_bolusprogress";
- public static final String ACTION_CONFIRMATION_REQUEST_PATH = "/nightscout_watch_actionconfirmationrequest";
- public static final String ACTION_CHANGECONFIRMATION_REQUEST_PATH = "/nightscout_watch_changeconfirmationrequest";
- public static final String ACTION_CANCELNOTIFICATION_REQUEST_PATH = "/nightscout_watch_cancelnotificationrequest";
-
-
- private static boolean lastLoopStatus;
-
- private Handler handler;
-
- // Phone
- private static final String CAPABILITY_PHONE_APP = "phone_app_sync_bgs";
- private static final String MESSAGE_PATH_PHONE = "/phone_message_path";
- // Wear
- private static final String CAPABILITY_WEAR_APP = "wear_app_sync_bgs";
- private static final String MESSAGE_PATH_WEAR = "/wear_message_path";
-
-
- @Override
- public void onCreate() {
- AndroidInjection.inject(this);
- if (wearIntegration()) {
- googleApiConnect();
- }
- if (handler == null) {
- HandlerThread handlerThread = new HandlerThread(this.getClass().getSimpleName() + "Handler");
- handlerThread.start();
- handler = new Handler(handlerThread.getLooper());
- }
- }
-
- private boolean wearIntegration() {
- return wearPlugin.isEnabled();
- }
-
- private void googleApiConnect() {
- if (googleApiClient != null && (googleApiClient.isConnected() || googleApiClient.isConnecting())) {
- googleApiClient.disconnect();
- }
- googleApiClient = new GoogleApiClient.Builder(this).addConnectionCallbacks(this)
- .addOnConnectionFailedListener(this).addApi(Wearable.API).build();
- Wearable.MessageApi.addListener(googleApiClient, this);
- if (googleApiClient.isConnected()) {
- aapsLogger.debug(LTag.WEAR, "API client is connected");
- } else {
- // Log.d("WatchUpdater", logPrefix + "API client is not connected and is trying to connect");
- googleApiClient.connect();
- }
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- String action = intent != null ? intent.getAction() : null;
-
- // Log.d(TAG, logPrefix + "onStartCommand: " + action);
-
- if (wearIntegration()) {
- handler.post(() -> {
- if (googleApiClient != null && googleApiClient.isConnected()) {
- if (ACTION_RESEND.equals(action)) {
- resendData();
- } else if (ACTION_OPEN_SETTINGS.equals(action)) {
- sendNotification();
- } else if (ACTION_SEND_STATUS.equals(action)) {
- sendStatus();
- } else if (ACTION_SEND_BASALS.equals(action)) {
- sendBasals();
- } else if (ACTION_SEND_BOLUSPROGRESS.equals(action)) {
- sendBolusProgress(intent.getIntExtra("progresspercent", 0), intent.hasExtra("progressstatus") ? intent.getStringExtra("progressstatus") : "");
- } else if (ACTION_SEND_ACTIONCONFIRMATIONREQUEST.equals(action)) {
- String title = intent.getStringExtra("title");
- String message = intent.getStringExtra("message");
- String actionstring = intent.getStringExtra("actionstring");
- sendActionConfirmationRequest(title, message, actionstring);
- } else if (ACTION_SEND_CHANGECONFIRMATIONREQUEST.equals(action)) {
- String title = intent.getStringExtra("title");
- String message = intent.getStringExtra("message");
- String actionstring = intent.getStringExtra("actionstring");
- sendChangeConfirmationRequest(title, message, actionstring);
- } else if (ACTION_CANCEL_NOTIFICATION.equals(action)) {
- String actionstring = intent.getStringExtra("actionstring");
- sendCancelNotificationRequest(actionstring);
- } else {
- sendData();
- }
- } else {
- if (googleApiClient != null) googleApiClient.connect();
- }
- });
- }
-
- return START_STICKY;
- }
-
-
- private void updateWearSyncBgsCapability(CapabilityInfo capabilityInfo) {
- Log.d("WatchUpdaterService", "CabilityInfo: " + capabilityInfo);
- Set connectedNodes = capabilityInfo.getNodes();
- String mWearNodeId = pickBestNodeId(connectedNodes);
- }
-
-
- private String pickBestNodeId(Set nodes) {
- String bestNodeId = null;
- // Find a nearby node or pick one arbitrarily
- for (Node node : nodes) {
- if (node.isNearby()) {
- return node.getId();
- }
- bestNodeId = node.getId();
- }
- return bestNodeId;
- }
-
-
- @Override
- public void onConnected(Bundle connectionHint) {
- CapabilityApi.CapabilityListener capabilityListener = capabilityInfo -> {
- updateWearSyncBgsCapability(capabilityInfo);
- // Log.d(TAG, logPrefix + "onConnected onCapabilityChanged mWearNodeID:" + mWearNodeId);
- // new CheckWearableConnected().execute();
- };
-
- Wearable.CapabilityApi.addCapabilityListener(googleApiClient, capabilityListener, CAPABILITY_WEAR_APP);
- sendData();
- }
-
-
- @Override
- public void onPeerConnected(com.google.android.gms.wearable.Node peer) {// KS
- super.onPeerConnected(peer);
- String id = peer.getId();
- String name = peer.getDisplayName();
- // Log.d(TAG, logPrefix + "onPeerConnected peer name & ID: " + name + "|" + id);
- }
-
-
- @Override
- public void onPeerDisconnected(com.google.android.gms.wearable.Node peer) {// KS
- super.onPeerDisconnected(peer);
- String id = peer.getId();
- String name = peer.getDisplayName();
- // Log.d(TAG, logPrefix + "onPeerDisconnected peer name & ID: " + name + "|" + id);
- }
-
-
- @Override
- public void onMessageReceived(MessageEvent event) {
-
- // Log.d(TAG, logPrefix + "onMessageRecieved: " + event);
-
- if (wearIntegration()) {
- if (event != null && event.getPath().equals(WEARABLE_RESEND_PATH)) {
- resendData();
- }
-
- if (event != null && event.getPath().equals(WEARABLE_CANCELBOLUS_PATH)) {
- cancelBolus();
- }
-
- if (event != null && event.getPath().equals(WEARABLE_INITIATE_ACTIONSTRING_PATH)) {
- String actionstring = new String(event.getData());
- aapsLogger.debug(LTag.WEAR, "Wear: " + actionstring);
- rxBus.send(new EventWearInitiateAction(actionstring));
- }
-
- if (event != null && event.getPath().equals(WEARABLE_CONFIRM_ACTIONSTRING_PATH)) {
- String actionstring = new String(event.getData());
- aapsLogger.debug(LTag.WEAR, "Wear Confirm: " + actionstring);
- rxBus.send(new EventWearConfirmAction(actionstring));
- }
- }
- }
-
- private void cancelBolus() {
- activePlugin.getActivePump().stopBolusDelivering();
- }
-
- private void sendData() {
-
- GlucoseValue lastBG = iobCobCalculator.getAds().lastBg();
- // Log.d(TAG, logPrefix + "LastBg=" + lastBG);
- if (lastBG != null) {
- GlucoseStatus glucoseStatus = glucoseStatusProvider.getGlucoseStatusData();
-
- if (googleApiClient != null && !googleApiClient.isConnected() && !googleApiClient.isConnecting()) {
- googleApiConnect();
- }
- if (wearIntegration()) {
-
- final DataMap dataMap = dataMapSingleBG(lastBG, glucoseStatus);
-
- (new SendToDataLayerThread(WEARABLE_DATA_PATH, googleApiClient)).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, dataMap);
- }
- }
- }
-
-
- private DataMap dataMapSingleBG(GlucoseValue lastBG, GlucoseStatus glucoseStatus) {
- GlucoseUnit units = profileFunction.getUnits();
- double convert2MGDL = 1.0;
- if (units.equals(GlucoseUnit.MMOL))
- convert2MGDL = Constants.MMOLL_TO_MGDL;
- double lowLine = defaultValueHelper.determineLowLine() * convert2MGDL;
- double highLine = defaultValueHelper.determineHighLine() * convert2MGDL;
-
- long sgvLevel = 0L;
- if (lastBG.getValue() > highLine) {
- sgvLevel = 1;
- } else if (lastBG.getValue() < lowLine) {
- sgvLevel = -1;
- }
-
- DataMap dataMap = new DataMap();
- dataMap.putString("sgvString", GlucoseValueExtensionKt.valueToUnitsString(lastBG, units));
- dataMap.putString("glucoseUnits", units.getAsText());
- dataMap.putLong("timestamp", lastBG.getTimestamp());
- if (glucoseStatus == null) {
- dataMap.putString("slopeArrow", "");
- dataMap.putString("delta", "--");
- dataMap.putString("avgDelta", "--");
- } else {
- dataMap.putString("slopeArrow", trendCalculator.getTrendArrow(lastBG).getSymbol());
- dataMap.putString("delta", deltastring(glucoseStatus.getDelta(), glucoseStatus.getDelta() * Constants.MGDL_TO_MMOLL, units));
- dataMap.putString("avgDelta", deltastring(glucoseStatus.getShortAvgDelta(), glucoseStatus.getShortAvgDelta() * Constants.MGDL_TO_MMOLL, units));
- }
- dataMap.putLong("sgvLevel", sgvLevel);
- dataMap.putDouble("sgvDouble", lastBG.getValue());
- dataMap.putDouble("high", highLine);
- dataMap.putDouble("low", lowLine);
- return dataMap;
- }
-
- private String deltastring(double deltaMGDL, double deltaMMOL, GlucoseUnit units) {
- String deltastring = "";
- if (deltaMGDL >= 0) {
- deltastring += "+";
- } else {
- deltastring += "-";
- }
-
- boolean detailed = sp.getBoolean(R.string.key_wear_detailed_delta, false);
- if (units.equals(GlucoseUnit.MGDL)) {
- if (detailed) {
- deltastring += DecimalFormatter.INSTANCE.to1Decimal(Math.abs(deltaMGDL));
- } else {
- deltastring += DecimalFormatter.INSTANCE.to0Decimal(Math.abs(deltaMGDL));
- }
- } else {
- if (detailed) {
- deltastring += DecimalFormatter.INSTANCE.to2Decimal(Math.abs(deltaMMOL));
- } else {
- deltastring += DecimalFormatter.INSTANCE.to1Decimal(Math.abs(deltaMMOL));
- }
- }
- return deltastring;
- }
-
- private void resendData() {
- if (googleApiClient != null && !googleApiClient.isConnected() && !googleApiClient.isConnecting()) {
- googleApiConnect();
- }
- long startTime = System.currentTimeMillis() - (long) (60000 * 60 * 5.5);
- GlucoseValue last_bg = iobCobCalculator.getAds().lastBg();
-
- if (last_bg == null) return;
-
- List graph_bgs = repository.compatGetBgReadingsDataFromTime(startTime, true).blockingGet();
- GlucoseStatus glucoseStatus = glucoseStatusProvider.getGlucoseStatusData(true);
-
- if (!graph_bgs.isEmpty()) {
- DataMap entries = dataMapSingleBG(last_bg, glucoseStatus);
- final ArrayList dataMaps = new ArrayList<>(graph_bgs.size());
- for (GlucoseValue bg : graph_bgs) {
- DataMap dataMap = dataMapSingleBG(bg, glucoseStatus);
- dataMaps.add(dataMap);
- }
- entries.putDataMapArrayList("entries", dataMaps);
- (new SendToDataLayerThread(WEARABLE_DATA_PATH, googleApiClient)).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, entries);
- }
- sendPreferences();
- sendBasals();
- sendStatus();
- }
-
- private void sendBasals() {
- if (googleApiClient != null && !googleApiClient.isConnected() && !googleApiClient.isConnecting()) {
- googleApiConnect();
- }
-
- long now = System.currentTimeMillis();
- final long startTimeWindow = now - (long) (60000 * 60 * 5.5);
-
-
- ArrayList basals = new ArrayList<>();
- ArrayList temps = new ArrayList<>();
- ArrayList boluses = new ArrayList<>();
- ArrayList predictions = new ArrayList<>();
-
-
- Profile profile = profileFunction.getProfile();
-
- if (profile == null) {
- return;
- }
-
- long beginBasalSegmentTime = startTimeWindow;
- long runningTime = startTimeWindow;
-
- double beginBasalValue = profile.getBasal(beginBasalSegmentTime);
- double endBasalValue = beginBasalValue;
-
- TemporaryBasal tb1 = iobCobCalculator.getTempBasalIncludingConvertedExtended(runningTime);
- TemporaryBasal tb2 = iobCobCalculator.getTempBasalIncludingConvertedExtended(runningTime); //TODO for Adrian ... what's the meaning?
- double tb_before = beginBasalValue;
- double tb_amount = beginBasalValue;
- long tb_start = runningTime;
-
- if (tb1 != null) {
- tb_before = beginBasalValue;
- Profile profileTB = profileFunction.getProfile(runningTime);
- if (profileTB != null) {
- tb_amount = TemporaryBasalExtensionKt.convertedToAbsolute(tb1, runningTime, profileTB);
- tb_start = runningTime;
- }
- }
-
-
- for (; runningTime < now; runningTime += 5 * 60 * 1000) {
- Profile profileTB = profileFunction.getProfile(runningTime);
- if (profileTB == null)
- return;
- //basal rate
- endBasalValue = profile.getBasal(runningTime);
- if (endBasalValue != beginBasalValue) {
- //push the segment we recently left
- basals.add(basalMap(beginBasalSegmentTime, runningTime, beginBasalValue));
-
- //begin new Basal segment
- beginBasalSegmentTime = runningTime;
- beginBasalValue = endBasalValue;
- }
-
- //temps
- tb2 = iobCobCalculator.getTempBasalIncludingConvertedExtended(runningTime);
-
- if (tb1 == null && tb2 == null) {
- //no temp stays no temp
-
- } else if (tb1 != null && tb2 == null) {
- //temp is over -> push it
- temps.add(tempDatamap(tb_start, tb_before, runningTime, endBasalValue, tb_amount));
- tb1 = null;
-
- } else if (tb1 == null && tb2 != null) {
- //temp begins
- tb1 = tb2;
- tb_start = runningTime;
- tb_before = endBasalValue;
- tb_amount = TemporaryBasalExtensionKt.convertedToAbsolute(tb1, runningTime, profileTB);
-
- } else if (tb1 != null && tb2 != null) {
- double currentAmount = TemporaryBasalExtensionKt.convertedToAbsolute(tb2, runningTime, profileTB);
- if (currentAmount != tb_amount) {
- temps.add(tempDatamap(tb_start, tb_before, runningTime, currentAmount, tb_amount));
- tb_start = runningTime;
- tb_before = tb_amount;
- tb_amount = currentAmount;
- tb1 = tb2;
- }
- }
- }
- if (beginBasalSegmentTime != runningTime) {
- //push the remaining segment
- basals.add(basalMap(beginBasalSegmentTime, runningTime, beginBasalValue));
- }
- if (tb1 != null) {
- tb2 = iobCobCalculator.getTempBasalIncludingConvertedExtended(now); //use "now" to express current situation
- if (tb2 == null) {
- //express the cancelled temp by painting it down one minute early
- temps.add(tempDatamap(tb_start, tb_before, now - 60 * 1000, endBasalValue, tb_amount));
- } else {
- //express currently running temp by painting it a bit into the future
- Profile profileNow = profileFunction.getProfile(now);
- double currentAmount = TemporaryBasalExtensionKt.convertedToAbsolute(tb2, now, profileNow);
- if (currentAmount != tb_amount) {
- temps.add(tempDatamap(tb_start, tb_before, now, tb_amount, tb_amount));
- temps.add(tempDatamap(now, tb_amount, runningTime + 5 * 60 * 1000, currentAmount, currentAmount));
- } else {
- temps.add(tempDatamap(tb_start, tb_before, runningTime + 5 * 60 * 1000, tb_amount, tb_amount));
- }
- }
- } else {
- tb2 = iobCobCalculator.getTempBasalIncludingConvertedExtended(now); //use "now" to express current situation
- if (tb2 != null) {
- //onset at the end
- Profile profileTB = profileFunction.getProfile(runningTime);
- double currentAmount = TemporaryBasalExtensionKt.convertedToAbsolute(tb2, runningTime, profileTB);
- temps.add(tempDatamap(now - 60 * 1000, endBasalValue, runningTime + 5 * 60 * 1000, currentAmount, currentAmount));
- }
- }
-
- repository.getBolusesIncludingInvalidFromTime(startTimeWindow, true).blockingGet()
- .stream()
- .filter(bolus -> bolus.getType() != Bolus.Type.PRIMING)
- .forEach(bolus -> boluses.add(treatmentMap(bolus.getTimestamp(), bolus.getAmount(), 0, bolus.getType() == Bolus.Type.SMB, bolus.isValid())));
- repository.getCarbsDataFromTimeExpanded(startTimeWindow, true).blockingGet()
- .forEach(carb -> boluses.add(treatmentMap(carb.getTimestamp(), 0, carb.getAmount(), false, carb.isValid())));
-
- final LoopPlugin.LastRun finalLastRun = loop.getLastRun();
- if (sp.getBoolean("wear_predictions", true) && finalLastRun != null && finalLastRun.getRequest().getHasPredictions() && finalLastRun.getConstraintsProcessed() != null) {
- List predArray =
- finalLastRun.getConstraintsProcessed().getPredictions()
- .stream().map(bg -> new GlucoseValueDataPoint(bg, defaultValueHelper, profileFunction, rh))
- .collect(Collectors.toList());
-
- if (!predArray.isEmpty()) {
- for (GlucoseValueDataPoint bg : predArray) {
- if (bg.getData().getValue() < 40) continue;
- predictions.add(predictionMap(bg.getData().getTimestamp(), bg.getData().getValue(), bg.getPredictionColor()));
- }
- }
- }
-
-
- DataMap dm = new DataMap();
- dm.putDataMapArrayList("basals", basals);
- dm.putDataMapArrayList("temps", temps);
- dm.putDataMapArrayList("boluses", boluses);
- dm.putDataMapArrayList("predictions", predictions);
- (new SendToDataLayerThread(BASAL_DATA_PATH, googleApiClient)).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, dm);
- }
-
- private DataMap tempDatamap(long startTime, double startBasal, long to, double toBasal, double amount) {
- DataMap dm = new DataMap();
- dm.putLong("starttime", startTime);
- dm.putDouble("startBasal", startBasal);
- dm.putLong("endtime", to);
- dm.putDouble("endbasal", toBasal);
- dm.putDouble("amount", amount);
- return dm;
- }
-
- private DataMap basalMap(long startTime, long endTime, double amount) {
- DataMap dm = new DataMap();
- dm.putLong("starttime", startTime);
- dm.putLong("endtime", endTime);
- dm.putDouble("amount", amount);
- return dm;
- }
-
- private DataMap treatmentMap(long date, double bolus, double carbs, boolean isSMB, boolean isValid) {
- DataMap dm = new DataMap();
- dm.putLong("date", date);
- dm.putDouble("bolus", bolus);
- dm.putDouble("carbs", carbs);
- dm.putBoolean("isSMB", isSMB);
- dm.putBoolean("isValid", isValid);
- return dm;
- }
-
- private DataMap predictionMap(long timestamp, double sgv, int color) {
- DataMap dm = new DataMap();
- dm.putLong("timestamp", timestamp);
- dm.putDouble("sgv", sgv);
- dm.putInt("color", color);
- return dm;
- }
-
-
- private void sendNotification() {
- if (googleApiClient != null && googleApiClient.isConnected()) {
- PutDataMapRequest dataMapRequest = PutDataMapRequest.create(OPEN_SETTINGS_PATH);
- //unique content
- dataMapRequest.getDataMap().putLong("timestamp", System.currentTimeMillis());
- dataMapRequest.getDataMap().putString("openSettings", "openSettings");
- PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
- Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
- } else {
- Log.e("OpenSettings", "No connection to wearable available!");
- }
- }
-
- private void sendBolusProgress(int progresspercent, String status) {
- if (googleApiClient != null && googleApiClient.isConnected()) {
- PutDataMapRequest dataMapRequest = PutDataMapRequest.create(BOLUS_PROGRESS_PATH);
- //unique content
- dataMapRequest.getDataMap().putLong("timestamp", System.currentTimeMillis());
- dataMapRequest.getDataMap().putString("bolusProgress", "bolusProgress");
- dataMapRequest.getDataMap().putString("progressstatus", status);
- dataMapRequest.getDataMap().putInt("progresspercent", progresspercent);
- PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
- Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
- } else {
- Log.e("BolusProgress", "No connection to wearable available!");
- }
- }
-
- private void sendActionConfirmationRequest(String title, String message, String actionstring) {
- if (googleApiClient != null && googleApiClient.isConnected()) {
- PutDataMapRequest dataMapRequest = PutDataMapRequest.create(ACTION_CONFIRMATION_REQUEST_PATH);
- //unique content
- dataMapRequest.getDataMap().putLong("timestamp", System.currentTimeMillis());
- dataMapRequest.getDataMap().putString("actionConfirmationRequest", "actionConfirmationRequest");
- dataMapRequest.getDataMap().putString("title", title);
- dataMapRequest.getDataMap().putString("message", message);
- dataMapRequest.getDataMap().putString("actionstring", actionstring);
-
- aapsLogger.debug(LTag.WEAR, "Requesting confirmation from wear: " + actionstring);
-
- PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
- Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
- } else {
- Log.e("confirmationRequest", "No connection to wearable available!");
- }
- }
-
- private void sendChangeConfirmationRequest(String title, String message, String actionstring) {
- if (googleApiClient != null && googleApiClient.isConnected()) {
- PutDataMapRequest dataMapRequest = PutDataMapRequest.create(ACTION_CHANGECONFIRMATION_REQUEST_PATH);
- //unique content
- dataMapRequest.getDataMap().putLong("timestamp", System.currentTimeMillis());
- dataMapRequest.getDataMap().putString("changeConfirmationRequest", "changeConfirmationRequest");
- dataMapRequest.getDataMap().putString("title", title);
- dataMapRequest.getDataMap().putString("message", message);
- dataMapRequest.getDataMap().putString("actionstring", actionstring);
-
- aapsLogger.debug(LTag.WEAR, "Requesting confirmation from wear: " + actionstring);
-
- PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
- Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
- } else {
- Log.e("changeConfirmRequest", "No connection to wearable available!");
- }
- }
-
- private void sendCancelNotificationRequest(String actionstring) {
- if (googleApiClient != null && googleApiClient.isConnected()) {
- PutDataMapRequest dataMapRequest = PutDataMapRequest.create(ACTION_CANCELNOTIFICATION_REQUEST_PATH);
- //unique content
- dataMapRequest.getDataMap().putLong("timestamp", System.currentTimeMillis());
- dataMapRequest.getDataMap().putString("cancelNotificationRequest", "cancelNotificationRequest");
- dataMapRequest.getDataMap().putString("actionstring", actionstring);
-
- aapsLogger.debug(LTag.WEAR, "Canceling notification on wear: " + actionstring);
-
- PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
- Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
- } else {
- Log.e("cancelNotificationReq", "No connection to wearable available!");
- }
- }
-
- private void sendStatus() {
-
- if (googleApiClient != null && googleApiClient.isConnected()) {
- Profile profile = profileFunction.getProfile();
- String status = rh.gs(R.string.noprofile);
- String iobSum, iobDetail, cobString, currentBasal, bgiString;
- iobSum = iobDetail = cobString = currentBasal = bgiString = "";
- if (profile != null) {
- IobTotal bolusIob = iobCobCalculator.calculateIobFromBolus().round();
- IobTotal basalIob = iobCobCalculator.calculateIobFromTempBasalsIncludingConvertedExtended().round();
-
- iobSum = DecimalFormatter.INSTANCE.to2Decimal(bolusIob.getIob() + basalIob.getBasaliob());
- iobDetail =
- "(" + DecimalFormatter.INSTANCE.to2Decimal(bolusIob.getIob()) + "|" + DecimalFormatter.INSTANCE.to2Decimal(basalIob.getBasaliob()) + ")";
- cobString = iobCobCalculator.getCobInfo(false, "WatcherUpdaterService").generateCOBString();
- currentBasal = generateBasalString();
-
- //bgi
- double bgi =
- -(bolusIob.getActivity() + basalIob.getActivity()) * 5 * Profile.Companion.fromMgdlToUnits(profile.getIsfMgdl(), profileFunction.getUnits());
- bgiString = "" + ((bgi >= 0) ? "+" : "") + DecimalFormatter.INSTANCE.to1Decimal(bgi);
-
- status = generateStatusString(profile, currentBasal, iobSum, iobDetail, bgiString);
- }
-
-
- //batteries
- int phoneBattery = receiverStatusStore.getBatteryLevel();
- String rigBattery = nsDeviceStatus.getUploaderStatus().trim();
-
-
- long openApsStatus;
- //OpenAPS status
- if (config.getAPS()) {
- //we are AndroidAPS
- openApsStatus = loop.getLastRun() != null && loop.getLastRun().getLastTBREnact() != 0 ? loop.getLastRun().getLastTBREnact() : -1;
- } else {
- //NSClient or remote
- openApsStatus = nsDeviceStatus.getOpenApsTimestamp();
- }
-
- PutDataMapRequest dataMapRequest = PutDataMapRequest.create(NEW_STATUS_PATH);
- //unique content
- dataMapRequest.getDataMap().putString("externalStatusString", status);
- dataMapRequest.getDataMap().putString("iobSum", iobSum);
- dataMapRequest.getDataMap().putString("iobDetail", iobDetail);
- dataMapRequest.getDataMap().putBoolean("detailedIob", sp.getBoolean(R.string.key_wear_detailediob, false));
- dataMapRequest.getDataMap().putString("cob", cobString);
- dataMapRequest.getDataMap().putString("currentBasal", currentBasal);
- dataMapRequest.getDataMap().putString("battery", "" + phoneBattery);
- dataMapRequest.getDataMap().putString("rigBattery", rigBattery);
- dataMapRequest.getDataMap().putLong("openApsStatus", openApsStatus);
- dataMapRequest.getDataMap().putString("bgi", bgiString);
- dataMapRequest.getDataMap().putBoolean("showBgi", sp.getBoolean(R.string.key_wear_showbgi, false));
- dataMapRequest.getDataMap().putInt("batteryLevel", (phoneBattery >= 30) ? 1 : 0);
- PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
- Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
- } else {
- Log.e("SendStatus", "No connection to wearable available!");
- }
- }
-
- private void sendPreferences() {
- if (googleApiClient != null && googleApiClient.isConnected()) {
-
- boolean wearcontrol = sp.getBoolean(R.string.key_wear_control, false);
-
- PutDataMapRequest dataMapRequest = PutDataMapRequest.create(NEW_PREFERENCES_PATH);
- //unique content
- dataMapRequest.getDataMap().putLong("timestamp", System.currentTimeMillis());
- dataMapRequest.getDataMap().putBoolean(rh.gs(R.string.key_wear_control), wearcontrol);
- PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
- Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
- } else {
- Log.e("SendStatus", "No connection to wearable available!");
- }
- }
-
- @NonNull
- private String generateStatusString(Profile profile, String currentBasal, String iobSum, String iobDetail, String bgiString) {
-
- String status = "";
-
- if (profile == null) {
- status = rh.gs(R.string.noprofile);
- return status;
- }
-
- if (!((PluginBase)loop).isEnabled()) {
- status += rh.gs(R.string.disabledloop) + "\n";
- lastLoopStatus = false;
- } else {
- lastLoopStatus = true;
- }
-
- String iobString;
- if (sp.getBoolean(R.string.key_wear_detailediob, false)) {
- iobString = iobSum + " " + iobDetail;
- } else {
- iobString = iobSum + "U";
- }
-
- status += currentBasal + " " + iobString;
-
- //add BGI if shown, otherwise return
- if (sp.getBoolean(R.string.key_wear_showbgi, false)) {
- status += " " + bgiString;
- }
-
- return status;
- }
-
- @NonNull
- private String generateBasalString() {
-
- String basalStringResult;
-
- Profile profile = profileFunction.getProfile();
- if (profile == null)
- return "";
-
- TemporaryBasal activeTemp = iobCobCalculator.getTempBasalIncludingConvertedExtended(System.currentTimeMillis());
- if (activeTemp != null) {
- basalStringResult = TemporaryBasalExtensionKt.toStringShort(activeTemp);
- } else {
- basalStringResult = DecimalFormatter.INSTANCE.to2Decimal(profile.getBasal()) + "U/h";
- }
- return basalStringResult;
- }
-
- @Override
- public void onDestroy() {
- if (googleApiClient != null && googleApiClient.isConnected()) {
- googleApiClient.disconnect();
- }
- }
-
- @Override
- public void onConnectionSuspended(int cause) {
- }
-
- @Override
- public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
- }
-
- public static boolean shouldReportLoopStatus(boolean enabled) {
- return (lastLoopStatus != enabled);
- }
-}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/xdripStatusline/StatusLinePlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/xdripStatusline/StatusLinePlugin.kt
index aa41940fd6..e0106a49d5 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/general/xdripStatusline/StatusLinePlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/xdripStatusline/StatusLinePlugin.kt
@@ -8,15 +8,14 @@ import info.nightscout.androidaps.R
import info.nightscout.androidaps.events.*
import info.nightscout.androidaps.extensions.toStringShort
import info.nightscout.androidaps.interfaces.*
-import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.utils.DecimalFormatter
import info.nightscout.androidaps.utils.FabricPrivacy
-import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
+import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.sharedPreferences.SP
-import io.reactivex.disposables.CompositeDisposable
-import io.reactivex.rxkotlin.plusAssign
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.kotlin.plusAssign
import javax.inject.Inject
import javax.inject.Singleton
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/insulin/ActivityGraph.kt b/app/src/main/java/info/nightscout/androidaps/plugins/insulin/ActivityGraph.kt
index c7f66ef4eb..7662100e49 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/insulin/ActivityGraph.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/insulin/ActivityGraph.kt
@@ -44,6 +44,9 @@ class ActivityGraph : GraphView {
viewport.isXAxisBoundsManual = true
viewport.setMinX(0.0)
viewport.setMaxX((hours * 60).toDouble())
+ viewport.isYAxisBoundsManual = true
+ viewport.setMinY(0.0)
+ viewport.setMaxY(0.01)
gridLabelRenderer.numHorizontalLabels = (hours + 1).toInt()
gridLabelRenderer.horizontalAxisTitle = "[min]"
secondScale.addSeries(LineGraphSeries(Array(iobArray.size) { i -> iobArray[i] }).also {
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/insulin/InsulinFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/insulin/InsulinFragment.kt
index 080fbd41fe..3302f21a51 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/insulin/InsulinFragment.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/insulin/InsulinFragment.kt
@@ -8,7 +8,7 @@ import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.R
import info.nightscout.androidaps.databinding.InsulinFragmentBinding
import info.nightscout.androidaps.interfaces.ActivePlugin
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import javax.inject.Inject
class InsulinFragment : DaggerFragment() {
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/insulin/InsulinLyumjevPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/insulin/InsulinLyumjevPlugin.kt
index 45b8e84309..e13bb47b6f 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/insulin/InsulinLyumjevPlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/insulin/InsulinLyumjevPlugin.kt
@@ -7,7 +7,8 @@ import info.nightscout.androidaps.interfaces.Insulin
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.androidaps.plugins.bus.RxBus
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
+import info.nightscout.androidaps.utils.HardLimits
import org.json.JSONObject
import javax.inject.Inject
import javax.inject.Singleton
@@ -19,8 +20,9 @@ class InsulinLyumjevPlugin @Inject constructor(
profileFunction: ProfileFunction,
rxBus: RxBus,
aapsLogger: AAPSLogger,
- config: Config
-) : InsulinOrefBasePlugin(injector, rh, profileFunction, rxBus, aapsLogger, config) {
+ config: Config,
+ hardLimits: HardLimits
+) : InsulinOrefBasePlugin(injector, rh, profileFunction, rxBus, aapsLogger, config, hardLimits) {
override val id get(): Insulin.InsulinType = Insulin.InsulinType.OREF_LYUMJEV
override val friendlyName get(): String = rh.gs(R.string.lyumjev)
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/insulin/InsulinOrefBasePlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/insulin/InsulinOrefBasePlugin.kt
index a0d3652240..f84d23f531 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/insulin/InsulinOrefBasePlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/insulin/InsulinOrefBasePlugin.kt
@@ -6,12 +6,12 @@ import info.nightscout.androidaps.data.Iob
import info.nightscout.androidaps.database.embedments.InsulinConfiguration
import info.nightscout.androidaps.database.entities.Bolus
import info.nightscout.androidaps.interfaces.*
-import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification
import info.nightscout.androidaps.plugins.general.overview.notifications.Notification
+import info.nightscout.androidaps.utils.HardLimits
import info.nightscout.androidaps.utils.T
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.shared.logging.AAPSLogger
import kotlin.math.exp
import kotlin.math.pow
@@ -27,7 +27,8 @@ abstract class InsulinOrefBasePlugin(
val profileFunction: ProfileFunction,
val rxBus: RxBus,
aapsLogger: AAPSLogger,
- config: Config
+ config: Config,
+ val hardLimits: HardLimits
) : PluginBase(
PluginDescription()
.mainType(PluginType.INSULIN)
@@ -43,18 +44,18 @@ abstract class InsulinOrefBasePlugin(
override val dia
get(): Double {
val dia = userDefinedDia
- return if (dia >= MIN_DIA) {
+ return if (dia >= hardLimits.minDia()) {
dia
} else {
sendShortDiaNotification(dia)
- MIN_DIA
+ hardLimits.minDia()
}
}
open fun sendShortDiaNotification(dia: Double) {
if (System.currentTimeMillis() - lastWarned > 60 * 1000) {
lastWarned = System.currentTimeMillis()
- val notification = Notification(Notification.SHORT_DIA, String.format(notificationPattern, dia, MIN_DIA), Notification.URGENT)
+ val notification = Notification(Notification.SHORT_DIA, String.format(notificationPattern, dia, hardLimits.minDia()), Notification.URGENT)
rxBus.send(EventNewNotification(notification))
}
}
@@ -65,12 +66,13 @@ abstract class InsulinOrefBasePlugin(
open val userDefinedDia: Double
get() {
val profile = profileFunction.getProfile()
- return profile?.dia ?: MIN_DIA
+ return profile?.dia ?: hardLimits.minDia()
}
override fun iobCalcForTreatment(bolus: Bolus, time: Long, dia: Double): Iob {
+ assert(dia != 0.0)
+ assert(peak != 0)
val result = Iob()
- val peak = peak
if (bolus.amount != 0.0) {
val bolusTime = bolus.timestamp
val t = (time - bolusTime) / 1000.0 / 60.0
@@ -80,9 +82,9 @@ abstract class InsulinOrefBasePlugin(
if (t < td) {
val tau = tp * (1 - tp / td) / (1 - 2 * tp / td)
val a = 2 * tau / td
- val S = 1 / (1 - a + (1 + a) * exp(-td / tau))
- result.activityContrib = bolus.amount * (S / tau.pow(2.0)) * t * (1 - t / td) * exp(-t / tau)
- result.iobContrib = bolus.amount * (1 - S * (1 - a) * ((t.pow(2.0) / (tau * td * (1 - a)) - t / tau - 1) * Math.exp(-t / tau) + 1))
+ val s = 1 / (1 - a + (1 + a) * exp(-td / tau))
+ result.activityContrib = bolus.amount * (s / tau.pow(2.0)) * t * (1 - t / td) * exp(-t / tau)
+ result.iobContrib = bolus.amount * (1 - s * (1 - a) * ((t.pow(2.0) / (tau * td * (1 - a)) - t / tau - 1) * exp(-t / tau) + 1))
}
}
return result
@@ -95,17 +97,12 @@ abstract class InsulinOrefBasePlugin(
get(): String {
var comment = commentStandardText()
val userDia = userDefinedDia
- if (userDia < MIN_DIA) {
- comment += "\n" + rh.gs(R.string.dia_too_short, userDia, MIN_DIA)
+ if (userDia < hardLimits.minDia()) {
+ comment += "\n" + rh.gs(R.string.dia_too_short, userDia, hardLimits.minDia())
}
return comment
}
- abstract val peak: Int
+ abstract override val peak: Int
abstract fun commentStandardText(): String
-
- companion object {
-
- const val MIN_DIA = 5.0
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/insulin/InsulinOrefFreePeakPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/insulin/InsulinOrefFreePeakPlugin.kt
index 4f07b5117c..3b3150cca7 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/insulin/InsulinOrefFreePeakPlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/insulin/InsulinOrefFreePeakPlugin.kt
@@ -9,7 +9,8 @@ import info.nightscout.androidaps.interfaces.Insulin
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.androidaps.plugins.bus.RxBus
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
+import info.nightscout.androidaps.utils.HardLimits
import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONObject
import javax.inject.Inject
@@ -26,8 +27,9 @@ class InsulinOrefFreePeakPlugin @Inject constructor(
profileFunction: ProfileFunction,
rxBus: RxBus,
aapsLogger: AAPSLogger,
- config: Config
-) : InsulinOrefBasePlugin(injector, rh, profileFunction, rxBus, aapsLogger, config) {
+ config: Config,
+ hardLimits: HardLimits
+) : InsulinOrefBasePlugin(injector, rh, profileFunction, rxBus, aapsLogger, config, hardLimits) {
override val id get(): Insulin.InsulinType = Insulin.InsulinType.OREF_FREE_PEAK
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/insulin/InsulinOrefRapidActingPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/insulin/InsulinOrefRapidActingPlugin.kt
index 1403530d66..76bceef9f2 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/insulin/InsulinOrefRapidActingPlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/insulin/InsulinOrefRapidActingPlugin.kt
@@ -7,7 +7,8 @@ import info.nightscout.androidaps.interfaces.Insulin
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.androidaps.plugins.bus.RxBus
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
+import info.nightscout.androidaps.utils.HardLimits
import org.json.JSONObject
import javax.inject.Inject
import javax.inject.Singleton
@@ -22,8 +23,9 @@ class InsulinOrefRapidActingPlugin @Inject constructor(
profileFunction: ProfileFunction,
rxBus: RxBus,
aapsLogger: AAPSLogger,
- config: Config
-) : InsulinOrefBasePlugin(injector, rh, profileFunction, rxBus, aapsLogger, config) {
+ config: Config,
+ hardLimits: HardLimits
+) : InsulinOrefBasePlugin(injector, rh, profileFunction, rxBus, aapsLogger, config, hardLimits) {
override val id get(): Insulin.InsulinType = Insulin.InsulinType.OREF_RAPID_ACTING
override val friendlyName get(): String = rh.gs(R.string.rapid_acting_oref)
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/insulin/InsulinOrefUltraRapidActingPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/insulin/InsulinOrefUltraRapidActingPlugin.kt
index 8a81c50608..631d495943 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/insulin/InsulinOrefUltraRapidActingPlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/insulin/InsulinOrefUltraRapidActingPlugin.kt
@@ -7,7 +7,8 @@ import info.nightscout.androidaps.interfaces.Insulin
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.androidaps.plugins.bus.RxBus
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
+import info.nightscout.androidaps.utils.HardLimits
import org.json.JSONObject
import javax.inject.Inject
import javax.inject.Singleton
@@ -22,8 +23,9 @@ class InsulinOrefUltraRapidActingPlugin @Inject constructor(
profileFunction: ProfileFunction,
rxBus: RxBus,
aapsLogger: AAPSLogger,
- config: Config
-) : InsulinOrefBasePlugin(injector, rh, profileFunction, rxBus, aapsLogger, config) {
+ config: Config,
+ hardLimits: HardLimits
+) : InsulinOrefBasePlugin(injector, rh, profileFunction, rxBus, aapsLogger, config, hardLimits) {
override val id get(): Insulin.InsulinType = Insulin.InsulinType.OREF_ULTRA_RAPID_ACTING
override val friendlyName get(): String = rh.gs(R.string.ultrarapid_oref)
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPlugin.kt
index 6f623266ca..30c05b1f32 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPlugin.kt
@@ -1,6 +1,5 @@
package info.nightscout.androidaps.plugins.iob.iobCobCalculator
-import android.os.SystemClock
import androidx.collection.LongSparseArray
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.Constants
@@ -19,23 +18,24 @@ import info.nightscout.androidaps.extensions.convertedToAbsolute
import info.nightscout.androidaps.extensions.iobCalc
import info.nightscout.androidaps.extensions.toTemporaryBasal
import info.nightscout.androidaps.interfaces.*
-import info.nightscout.shared.logging.AAPSLogger
-import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.plugins.bus.RxBus
+import info.nightscout.androidaps.plugins.general.overview.OverviewData
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.data.AutosensData
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventNewHistoryData
-import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin
-import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref1Plugin
-import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.DecimalFormatter
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.T
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
+import info.nightscout.androidaps.workflow.CalculationWorkflow
+import info.nightscout.androidaps.events.Event
+import info.nightscout.androidaps.utils.MidnightTime
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
-import io.reactivex.disposables.CompositeDisposable
-import io.reactivex.rxkotlin.plusAssign
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.kotlin.plusAssign
import org.json.JSONArray
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledFuture
@@ -57,12 +57,11 @@ class IobCobCalculatorPlugin @Inject constructor(
rh: ResourceHelper,
private val profileFunction: ProfileFunction,
private val activePlugin: ActivePlugin,
- private val sensitivityOref1Plugin: SensitivityOref1Plugin,
- private val sensitivityAAPSPlugin: SensitivityAAPSPlugin,
- private val sensitivityWeightedAveragePlugin: SensitivityWeightedAveragePlugin,
private val fabricPrivacy: FabricPrivacy,
private val dateUtil: DateUtil,
- private val repository: AppRepository
+ private val repository: AppRepository,
+ val overviewData: OverviewData,
+ private val calculationWorkflow: CalculationWorkflow
) : PluginBase(
PluginDescription()
.mainType(PluginType.GENERAL)
@@ -81,7 +80,6 @@ class IobCobCalculatorPlugin @Inject constructor(
override var ads: AutosensDataStore = AutosensDataStore()
private val dataLock = Any()
- var stopCalculationTrigger = false
private var thread: Thread? = null
override fun onStart() {
@@ -117,14 +115,6 @@ class IobCobCalculatorPlugin @Inject constructor(
resetDataAndRunCalculation("onEventPreferenceChange", event)
}
}, fabricPrivacy::logException)
- // EventAppInitialized
- disposable += rxBus
- .toObservable(EventAppInitialized::class.java)
- .observeOn(aapsSchedulers.io)
- .subscribe(
- { event -> runCalculation("onEventAppInitialized", System.currentTimeMillis(), bgDataReload = true, limitDataToOldestAvailable = true, cause = event) },
- fabricPrivacy::logException
- )
// EventNewHistoryData
disposable += rxBus
.toObservable(EventNewHistoryData::class.java)
@@ -138,10 +128,20 @@ class IobCobCalculatorPlugin @Inject constructor(
}
private fun resetDataAndRunCalculation(reason: String, event: Event?) {
- stopCalculation(reason)
+ calculationWorkflow.stopCalculation(CalculationWorkflow.MAIN_CALCULATION, reason)
clearCache()
ads.reset()
- runCalculation(reason, System.currentTimeMillis(), bgDataReload = false, limitDataToOldestAvailable = true, cause = event)
+ calculationWorkflow.runCalculation(
+ CalculationWorkflow.MAIN_CALCULATION,
+ this,
+ overviewData,
+ reason,
+ System.currentTimeMillis(),
+ bgDataReload = false,
+ limitDataToOldestAvailable = true,
+ cause = event,
+ runLoop = true
+ )
}
override fun clearCache() {
@@ -168,10 +168,9 @@ class IobCobCalculatorPlugin @Inject constructor(
return oldestTime
}
- fun calculateDetectionStart(from: Long, limitDataToOldestAvailable: Boolean): Long {
+ override fun calculateDetectionStart(from: Long, limitDataToOldestAvailable: Boolean): Long {
val profile = profileFunction.getProfile(from)
- var dia = Constants.defaultDIA
- if (profile != null) dia = profile.dia
+ val dia = profile?.dia ?: Constants.defaultDIA
val oldestDataAvailable = oldestDataAvailable()
val getBGDataFrom: Long
if (limitDataToOldestAvailable) {
@@ -302,11 +301,7 @@ class IobCobCalculatorPlugin @Inject constructor(
override fun getMealDataWithWaitingForCalculationFinish(): MealData {
val result = MealData()
val now = System.currentTimeMillis()
- val maxAbsorptionHours: Double = if (sensitivityAAPSPlugin.isEnabled() || sensitivityWeightedAveragePlugin.isEnabled()) {
- sp.getDouble(R.string.key_absorption_maxtime, Constants.DEFAULT_MAX_ABSORPTION_TIME)
- } else {
- sp.getDouble(R.string.key_absorption_cutoff, Constants.DEFAULT_MAX_ABSORPTION_TIME)
- }
+ val maxAbsorptionHours: Double = activePlugin.activeSensitivity.maxAbsorptionHours()
val absorptionTimeAgo = now - (maxAbsorptionHours * T.hours(1).msecs()).toLong()
repository.getCarbsDataFromTimeToTimeExpanded(absorptionTimeAgo + 1, now, true)
.blockingGet()
@@ -366,27 +361,6 @@ class IobCobCalculatorPlugin @Inject constructor(
return sb.toString()
}
- fun stopCalculation(from: String) {
- if (thread?.state != Thread.State.TERMINATED) {
- stopCalculationTrigger = true
- aapsLogger.debug(LTag.AUTOSENS, "Stopping calculation thread: $from")
- while (thread != null && thread?.state != Thread.State.TERMINATED) {
- SystemClock.sleep(100)
- }
- aapsLogger.debug(LTag.AUTOSENS, "Calculation thread stopped: $from")
- }
- }
-
- fun runCalculation(from: String, end: Long, bgDataReload: Boolean, limitDataToOldestAvailable: Boolean, cause: Event?) {
- aapsLogger.debug(LTag.AUTOSENS, "Starting calculation thread: " + from + " to " + dateUtil.dateAndTimeAndSecondsString(end))
- if (thread == null || thread?.state == Thread.State.TERMINATED) {
- thread =
- if (sensitivityOref1Plugin.isEnabled()) IobCobOref1Thread(injector, this, from, end, bgDataReload, limitDataToOldestAvailable, cause)
- else IobCobThread(injector, this, from, end, bgDataReload, limitDataToOldestAvailable, cause)
- thread?.start()
- }
- }
-
// Limit rate of EventNewHistoryData
private val historyWorker = Executors.newSingleThreadScheduledExecutor()
private var scheduledHistoryPost: ScheduledFuture<*>? = null
@@ -400,18 +374,21 @@ class IobCobCalculatorPlugin @Inject constructor(
scheduledHistoryPost?.cancel(false)
// prepare task for execution in 1 sec
scheduledEvent = event
- scheduledHistoryPost = historyWorker.schedule({
- synchronized(this) {
- aapsLogger.debug(LTag.AUTOSENS, "Running newHistoryData")
- newHistoryData(
- event.oldDataTimestamp,
- event.reloadBgData,
- if (event.newestGlucoseValue != null) EventNewBG(event.newestGlucoseValue) else event
- )
- scheduledEvent = null
- scheduledHistoryPost = null
- }
- }, 1L, TimeUnit.SECONDS)
+ scheduledHistoryPost = historyWorker.schedule(
+ {
+ synchronized(this) {
+ aapsLogger.debug(LTag.AUTOSENS, "Running newHistoryData")
+ repository.clearCachedData(MidnightTime.calc(event.oldDataTimestamp))
+ newHistoryData(
+ event.oldDataTimestamp,
+ event.reloadBgData,
+ if (event.newestGlucoseValue != null) EventNewBG(event.newestGlucoseValue) else event
+ )
+ scheduledEvent = null
+ scheduledHistoryPost = null
+ }
+ }, 1L, TimeUnit.SECONDS
+ )
} else {
// asked reload is newer -> adjust params only
scheduledEvent?.let {
@@ -428,7 +405,7 @@ class IobCobCalculatorPlugin @Inject constructor(
// When historical data is changed (coming from NS etc) finished calculations after this date must be invalidated
private fun newHistoryData(oldDataTimestamp: Long, bgDataReload: Boolean, event: Event) {
//log.debug("Locking onNewHistoryData");
- stopCalculation("onEventNewHistoryData")
+ calculationWorkflow.stopCalculation(CalculationWorkflow.MAIN_CALCULATION, "onEventNewHistoryData")
synchronized(dataLock) {
// clear up 5 min back for proper COB calculation
@@ -452,7 +429,7 @@ class IobCobCalculatorPlugin @Inject constructor(
}
ads.newHistoryData(time, aapsLogger, dateUtil)
}
- runCalculation(event.javaClass.simpleName, System.currentTimeMillis(), bgDataReload, true, event)
+ calculationWorkflow.runCalculation(CalculationWorkflow.MAIN_CALCULATION, this, overviewData, event.javaClass.simpleName, System.currentTimeMillis(), bgDataReload, true, event, runLoop = true)
//log.debug("Releasing onNewHistoryData");
}
@@ -504,6 +481,7 @@ class IobCobCalculatorPlugin @Inject constructor(
val profile = profileFunction.getProfile() ?: return total
val dia = profile.dia
val divisor = sp.getDouble(R.string.key_openapsama_bolussnooze_dia_divisor, 2.0)
+ assert(divisor > 0)
val boluses = repository.getBolusesDataFromTime(toTime - range(), true).blockingGet()
@@ -574,17 +552,17 @@ class IobCobCalculatorPlugin @Inject constructor(
override fun getTempBasalIncludingConvertedExtended(timestamp: Long): TemporaryBasal? {
val tb = repository.getTemporaryBasalActiveAt(timestamp).blockingGet()
if (tb is ValueWrapper.Existing) return tb.value
- return getConvertedExtended(timestamp);
+ return getConvertedExtended(timestamp)
}
override fun getTempBasalIncludingConvertedExtendedForRange(startTime: Long, endTime: Long, calculationStep: Long): Map {
- val tempBasals = HashMap();
+ val tempBasals = HashMap()
val tbs = repository.getTemporaryBasalsDataActiveBetweenTimeAndTime(startTime, endTime).blockingGet()
for (t in startTime until endTime step calculationStep) {
val tb = tbs.firstOrNull { basal -> basal.timestamp <= t && (basal.timestamp + basal.duration) > t }
tempBasals[t] = tb ?: getConvertedExtended(t)
}
- return tempBasals;
+ return tempBasals
}
override fun calculateAbsoluteIobFromBaseBasals(toTime: Long): IobTotal {
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.kt b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Worker.kt
similarity index 84%
rename from app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.kt
rename to app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Worker.kt
index 1e0abcd34c..4d157a874a 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Worker.kt
@@ -1,8 +1,10 @@
package info.nightscout.androidaps.plugins.iob.iobCobCalculator
import android.content.Context
-import android.os.PowerManager
import android.os.SystemClock
+import androidx.work.Worker
+import androidx.work.WorkerParameters
+import androidx.work.workDataOf
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R
@@ -12,9 +14,8 @@ import info.nightscout.androidaps.events.Event
import info.nightscout.androidaps.events.EventAutosensCalculationFinished
import info.nightscout.androidaps.extensions.target
import info.nightscout.androidaps.interfaces.ActivePlugin
+import info.nightscout.androidaps.interfaces.IobCobCalculator
import info.nightscout.androidaps.interfaces.ProfileFunction
-import info.nightscout.shared.logging.AAPSLogger
-import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification
@@ -23,13 +24,17 @@ import info.nightscout.androidaps.plugins.iob.iobCobCalculator.data.AutosensData
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress
import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin
import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin
+import info.nightscout.androidaps.receivers.DataWorker
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.DecimalFormatter
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.Profiler
import info.nightscout.androidaps.utils.T
-import info.nightscout.androidaps.utils.buildHelper.BuildHelper
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.BuildHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
+import info.nightscout.androidaps.workflow.CalculationWorkflow
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import java.util.*
import javax.inject.Inject
@@ -38,15 +43,10 @@ import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToLong
-class IobCobOref1Thread internal constructor(
- private val injector: HasAndroidInjector,
- private val iobCobCalculatorPlugin: IobCobCalculatorPlugin, // cannot be injected : HistoryBrowser uses different instance
- private val from: String,
- private val end: Long,
- private val bgDataReload: Boolean,
- private val limitDataToOldestAvailable: Boolean,
- private val cause: Event?
-) : Thread() {
+class IobCobOref1Worker(
+ context: Context,
+ params: WorkerParameters
+) : Worker(context, params) {
@Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var sp: SP
@@ -62,48 +62,53 @@ class IobCobOref1Thread internal constructor(
@Inject lateinit var fabricPrivacy: FabricPrivacy
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var repository: AppRepository
-
- private var mWakeLock: PowerManager.WakeLock? = null
+ @Inject lateinit var dataWorker: DataWorker
+ @Inject lateinit var calculationWorkflow: CalculationWorkflow
init {
- injector.androidInjector().inject(this)
- mWakeLock = (context.applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, rh.gs(R.string.app_name) + ":iobCobThread")
+ (context.applicationContext as HasAndroidInjector).androidInjector().inject(this)
}
- override fun run() {
+ class IobCobOref1WorkerData(
+ val injector: HasAndroidInjector,
+ val iobCobCalculator: IobCobCalculator, // cannot be injected : HistoryBrowser uses different instance
+ val from: String,
+ val end: Long,
+ val limitDataToOldestAvailable: Boolean,
+ val cause: Event?
+ )
+
+ override fun doWork(): Result {
+
+ val data = dataWorker.pickupObject(inputData.getLong(DataWorker.STORE_KEY, -1)) as IobCobOref1WorkerData?
+ ?: return Result.success(workDataOf("Error" to "missing input data"))
+
val start = dateUtil.now()
- mWakeLock?.acquire(T.mins(10).msecs())
try {
- aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread started: $from")
+ aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread started: ${data.from}")
if (!profileFunction.isProfileValid("IobCobThread")) {
- aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (No profile): $from")
- return // app still initializing
+ aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (No profile): ${data.from}")
+ return Result.success(workDataOf("Error" to "app still initializing"))
}
//log.debug("Locking calculateSensitivityData");
- val oldestTimeWithData = iobCobCalculatorPlugin.calculateDetectionStart(end, limitDataToOldestAvailable)
- if (bgDataReload) {
- iobCobCalculatorPlugin.ads.loadBgData(end, repository, aapsLogger, dateUtil, rxBus)
- iobCobCalculatorPlugin.clearCache()
- }
+ val oldestTimeWithData = data.iobCobCalculator.calculateDetectionStart(data.end, data.limitDataToOldestAvailable)
// work on local copy and set back when finished
- val ads = iobCobCalculatorPlugin.ads.clone()
+ val ads = data.iobCobCalculator.ads.clone()
val bucketedData = ads.bucketedData
val autosensDataTable = ads.autosensDataTable
if (bucketedData == null || bucketedData.size < 3) {
- aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (No bucketed data available): $from")
- return
+ aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (No bucketed data available): ${data.from}")
+ return Result.success(workDataOf("Error" to "Aborting calculation thread (No bucketed data available): ${data.from}"))
}
val prevDataTime = ads.roundUpTime(bucketedData[bucketedData.size - 3].timestamp)
aapsLogger.debug(LTag.AUTOSENS, "Prev data time: " + dateUtil.dateAndTimeString(prevDataTime))
var previous = autosensDataTable[prevDataTime]
// start from oldest to be able sub cob
for (i in bucketedData.size - 4 downTo 0) {
- val progress = i.toString() + if (buildHelper.isDev()) " ($from)" else ""
- rxBus.send(EventIobCalculationProgress(progress, cause))
- if (iobCobCalculatorPlugin.stopCalculationTrigger) {
- iobCobCalculatorPlugin.stopCalculationTrigger = false
- aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (trigger): $from")
- return
+ rxBus.send(EventIobCalculationProgress(CalculationWorkflow.ProgressData.IOB_COB_OREF, 100 - (100.0 * i / bucketedData.size).toInt(), data.cause))
+ if (isStopped) {
+ aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (trigger): ${data.from}")
+ return Result.failure(workDataOf("Error" to "Aborting calculation thread (trigger): ${data.from}"))
}
// check if data already exists
var bgTime = bucketedData[i].timestamp
@@ -116,12 +121,12 @@ class IobCobOref1Thread internal constructor(
}
val profile = profileFunction.getProfile(bgTime)
if (profile == null) {
- aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (no profile): $from")
+ aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (no profile): ${data.from}")
continue // profile not set yet
}
- aapsLogger.debug(LTag.AUTOSENS, "Processing calculation thread: " + from + " (" + i + "/" + bucketedData.size + ")")
+ aapsLogger.debug(LTag.AUTOSENS, "Processing calculation thread: ${data.from} ($i/${bucketedData.size})")
val sens = profile.getIsfMgdl(bgTime)
- val autosensData = AutosensData(injector)
+ val autosensData = AutosensData(data.injector)
autosensData.time = bgTime
if (previous != null) autosensData.activeCarbsList = previous.cloneCarbsList() else autosensData.activeCarbsList = ArrayList()
@@ -136,7 +141,7 @@ class IobCobOref1Thread internal constructor(
autosensData.bg = bg
delta = bg - bucketedData[i + 1].value
avgDelta = (bg - bucketedData[i + 3].value) / 3
- val iob = iobCobCalculatorPlugin.calculateFromTreatmentsAndTemps(bgTime, profile)
+ val iob = data.iobCobCalculator.calculateFromTreatmentsAndTemps(bgTime, profile)
val bgi = -iob.activity * sens * 5
val deviation = delta - bgi
val avgDeviation = ((avgDelta - bgi) * 1000).roundToLong() / 1000.0
@@ -312,22 +317,27 @@ class IobCobOref1Thread internal constructor(
if (min in 0..4 && hours % 2 == 0) autosensData.extraDeviation.add(0.0)
previous = autosensData
if (bgTime < dateUtil.now()) autosensDataTable.put(bgTime, autosensData)
- aapsLogger.debug(LTag.AUTOSENS, "Running detectSensitivity from: " + dateUtil.dateAndTimeString(oldestTimeWithData) + " to: " + dateUtil.dateAndTimeString(bgTime) + " lastDataTime:" + ads.lastDataTime(dateUtil))
+ aapsLogger.debug(
+ LTag.AUTOSENS,
+ "Running detectSensitivity from: " + dateUtil.dateAndTimeString(oldestTimeWithData) + " to: " + dateUtil.dateAndTimeString(bgTime) + " lastDataTime:" + ads.lastDataTime(
+ dateUtil
+ )
+ )
val sensitivity = activePlugin.activeSensitivity.detectSensitivity(ads, oldestTimeWithData, bgTime)
aapsLogger.debug(LTag.AUTOSENS, "Sensitivity result: $sensitivity")
autosensData.autosensResult = sensitivity
aapsLogger.debug(LTag.AUTOSENS, autosensData.toString())
}
- iobCobCalculatorPlugin.ads = ads
+ data.iobCobCalculator.ads = ads
Thread {
SystemClock.sleep(1000)
- rxBus.send(EventAutosensCalculationFinished(cause))
+ rxBus.send(EventAutosensCalculationFinished(data.cause))
}.start()
} finally {
- mWakeLock?.release()
- rxBus.send(EventIobCalculationProgress("", cause))
- aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread ended: $from")
+ rxBus.send(EventIobCalculationProgress(CalculationWorkflow.ProgressData.IOB_COB_OREF, 100, data.cause))
+ aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread ended: ${data.from}")
profiler.log(LTag.AUTOSENS, "IobCobOref1Thread", start)
}
+ return Result.success()
}
}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.kt b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOrefWorker.kt
similarity index 80%
rename from app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.kt
rename to app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOrefWorker.kt
index dd0309d03b..347d4bb573 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOrefWorker.kt
@@ -1,8 +1,10 @@
package info.nightscout.androidaps.plugins.iob.iobCobCalculator
import android.content.Context
-import android.os.PowerManager
import android.os.SystemClock
+import androidx.work.Worker
+import androidx.work.WorkerParameters
+import androidx.work.workDataOf
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R
@@ -10,9 +12,8 @@ import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.events.Event
import info.nightscout.androidaps.events.EventAutosensCalculationFinished
import info.nightscout.androidaps.interfaces.ActivePlugin
+import info.nightscout.androidaps.interfaces.IobCobCalculator
import info.nightscout.androidaps.interfaces.ProfileFunction
-import info.nightscout.shared.logging.AAPSLogger
-import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification
@@ -21,30 +22,28 @@ import info.nightscout.androidaps.plugins.iob.iobCobCalculator.data.AutosensData
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress
import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin
import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin
+import info.nightscout.androidaps.receivers.DataWorker
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.DecimalFormatter
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.Profiler
import info.nightscout.androidaps.utils.T
-import info.nightscout.androidaps.utils.buildHelper.BuildHelper
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.BuildHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
+import info.nightscout.androidaps.workflow.CalculationWorkflow
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
-import java.util.*
import javax.inject.Inject
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToLong
-class IobCobThread @Inject internal constructor(
- private val injector: HasAndroidInjector,
- private val iobCobCalculatorPlugin: IobCobCalculatorPlugin, // cannot be injected : HistoryBrowser uses different instance
- private val from: String,
- private val end: Long,
- private val bgDataReload: Boolean,
- private val limitDataToOldestAvailable: Boolean,
- private val cause: Event?
-) : Thread() {
+class IobCobOrefWorker @Inject internal constructor(
+ context: Context,
+ params: WorkerParameters
+) : Worker(context, params) {
@Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var sp: SP
@@ -60,48 +59,51 @@ class IobCobThread @Inject internal constructor(
@Inject lateinit var fabricPrivacy: FabricPrivacy
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var repository: AppRepository
-
- private var mWakeLock: PowerManager.WakeLock? = null
+ @Inject lateinit var dataWorker: DataWorker
init {
- injector.androidInjector().inject(this)
- mWakeLock = (context.applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, rh.gs(R.string.app_name) + ":iobCobThread")
+ (context.applicationContext as HasAndroidInjector).androidInjector().inject(this)
}
- override fun run() {
+ class IobCobOrefWorkerData(
+ val injector: HasAndroidInjector,
+ val iobCobCalculatorPlugin: IobCobCalculator, // cannot be injected : HistoryBrowser uses different instance
+ val from: String,
+ val end: Long,
+ val limitDataToOldestAvailable: Boolean,
+ val cause: Event?
+ )
+
+ override fun doWork(): Result {
+ val data = dataWorker.pickupObject(inputData.getLong(DataWorker.STORE_KEY, -1)) as IobCobOrefWorkerData?
+ ?: return Result.success(workDataOf("Error" to "missing input data"))
+
val start = dateUtil.now()
- mWakeLock?.acquire(T.mins(10).msecs())
try {
- aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread started: $from")
+ aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread started: ${data.from}")
if (!profileFunction.isProfileValid("IobCobThread")) {
- aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (No profile): $from")
- return // app still initializing
+ aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (No profile): ${data.from}")
+ return Result.success(workDataOf("Error" to "app still initializing"))
}
//log.debug("Locking calculateSensitivityData");
- val oldestTimeWithData = iobCobCalculatorPlugin.calculateDetectionStart(end, limitDataToOldestAvailable)
- if (bgDataReload) {
- iobCobCalculatorPlugin.ads.loadBgData(end, repository, aapsLogger, dateUtil, rxBus)
- iobCobCalculatorPlugin.clearCache()
- }
+ val oldestTimeWithData = data.iobCobCalculatorPlugin.calculateDetectionStart(data.end, data.limitDataToOldestAvailable)
// work on local copy and set back when finished
- val ads = iobCobCalculatorPlugin.ads.clone()
+ val ads = data.iobCobCalculatorPlugin.ads.clone()
val bucketedData = ads.bucketedData
val autosensDataTable = ads.autosensDataTable
if (bucketedData == null || bucketedData.size < 3) {
- aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (No bucketed data available): $from")
- return
+ aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (No bucketed data available): ${data.from}")
+ return Result.success(workDataOf("Error" to "Aborting calculation thread (No bucketed data available): ${data.from}"))
}
val prevDataTime = ads.roundUpTime(bucketedData[bucketedData.size - 3].timestamp)
aapsLogger.debug(LTag.AUTOSENS, "Prev data time: " + dateUtil.dateAndTimeString(prevDataTime))
var previous = autosensDataTable[prevDataTime]
// start from oldest to be able sub cob
for (i in bucketedData.size - 4 downTo 0) {
- val progress = i.toString() + if (buildHelper.isDev()) " ($from)" else ""
- rxBus.send(EventIobCalculationProgress(progress, cause))
- if (iobCobCalculatorPlugin.stopCalculationTrigger) {
- iobCobCalculatorPlugin.stopCalculationTrigger = false
- aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (trigger): $from")
- return
+ rxBus.send(EventIobCalculationProgress(CalculationWorkflow.ProgressData.IOB_COB_OREF, 100 - (100.0 * i / bucketedData.size).toInt(), data.cause))
+ if (isStopped) {
+ aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (trigger): ${data.from}")
+ return Result.failure(workDataOf("Error" to "Aborting calculation thread (trigger): ${data.from}"))
}
// check if data already exists
var bgTime = bucketedData[i].timestamp
@@ -114,12 +116,12 @@ class IobCobThread @Inject internal constructor(
}
val profile = profileFunction.getProfile(bgTime)
if (profile == null) {
- aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (no profile): $from")
+ aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (no profile): ${data.from}")
continue // profile not set yet
}
- aapsLogger.debug(LTag.AUTOSENS, "Processing calculation thread: " + from + " (" + i + "/" + bucketedData.size + ")")
+ aapsLogger.debug(LTag.AUTOSENS, "Processing calculation thread: ${data.from} ($i/${bucketedData.size})")
val sens = profile.getIsfMgdl(bgTime)
- val autosensData = AutosensData(injector)
+ val autosensData = AutosensData(data.injector)
autosensData.time = bgTime
if (previous != null) autosensData.activeCarbsList = previous.cloneCarbsList() else autosensData.activeCarbsList = ArrayList()
@@ -134,7 +136,7 @@ class IobCobThread @Inject internal constructor(
autosensData.bg = bg
delta = bg - bucketedData[i + 1].value
avgDelta = (bg - bucketedData[i + 3].value) / 3
- val iob = iobCobCalculatorPlugin.calculateFromTreatmentsAndTemps(bgTime, profile)
+ val iob = data.iobCobCalculatorPlugin.calculateFromTreatmentsAndTemps(bgTime, profile)
val bgi = -iob.activity * sens * 5
val deviation = delta - bgi
val avgDeviation = ((avgDelta - bgi) * 1000).roundToLong() / 1000.0
@@ -158,7 +160,7 @@ class IobCobThread @Inject internal constructor(
if (ad == null) {
aapsLogger.debug(LTag.AUTOSENS, autosensDataTable.toString())
aapsLogger.debug(LTag.AUTOSENS, bucketedData.toString())
- //aapsLogger.debug(LTag.AUTOSENS, iobCobCalculatorPlugin.getBgReadingsDataTable().toString())
+ //aapsLogger.debug(LTag.AUTOSENS, data.iobCobCalculatorPlugin.getBgReadingsDataTable().toString())
val notification = Notification(Notification.SEND_LOGFILES, rh.gs(R.string.sendlogfiles), Notification.LOW)
rxBus.send(EventNewNotification(notification))
sp.putBoolean("log_AUTOSENS", true)
@@ -181,7 +183,7 @@ class IobCobThread @Inject internal constructor(
fabricPrivacy.logException(e)
aapsLogger.debug(autosensDataTable.toString())
aapsLogger.debug(bucketedData.toString())
- //aapsLogger.debug(iobCobCalculatorPlugin.getBgReadingsDataTable().toString())
+ //aapsLogger.debug(data.iobCobCalculatorPlugin.getBgReadingsDataTable().toString())
val notification = Notification(Notification.SEND_LOGFILES, rh.gs(R.string.sendlogfiles), Notification.LOW)
rxBus.send(EventNewNotification(notification))
sp.putBoolean("log_AUTOSENS", true)
@@ -258,22 +260,27 @@ class IobCobThread @Inject internal constructor(
}
previous = autosensData
if (bgTime < dateUtil.now()) autosensDataTable.put(bgTime, autosensData)
- aapsLogger.debug(LTag.AUTOSENS, "Running detectSensitivity from: " + dateUtil.dateAndTimeString(oldestTimeWithData) + " to: " + dateUtil.dateAndTimeString(bgTime) + " lastDataTime:" + ads.lastDataTime(dateUtil))
+ aapsLogger.debug(
+ LTag.AUTOSENS,
+ "Running detectSensitivity from: " + dateUtil.dateAndTimeString(oldestTimeWithData) + " to: " + dateUtil.dateAndTimeString(bgTime) + " lastDataTime:" + ads.lastDataTime(
+ dateUtil
+ )
+ )
val sensitivity = activePlugin.activeSensitivity.detectSensitivity(ads, oldestTimeWithData, bgTime)
aapsLogger.debug(LTag.AUTOSENS, "Sensitivity result: $sensitivity")
autosensData.autosensResult = sensitivity
aapsLogger.debug(LTag.AUTOSENS, autosensData.toString())
}
- iobCobCalculatorPlugin.ads = ads
+ data.iobCobCalculatorPlugin.ads = ads
Thread {
SystemClock.sleep(1000)
- rxBus.send(EventAutosensCalculationFinished(cause))
+ rxBus.send(EventAutosensCalculationFinished(data.cause))
}.start()
} finally {
- mWakeLock?.release()
- rxBus.send(EventIobCalculationProgress("", cause))
- aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread ended: $from")
+ rxBus.send(EventIobCalculationProgress(CalculationWorkflow.ProgressData.IOB_COB_OREF, 100, data.cause))
+ aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread ended: ${data.from}")
profiler.log(LTag.AUTOSENS, "IobCobThread", start)
}
+ return Result.success()
}
}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/events/EventIobCalculationProgress.kt b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/events/EventIobCalculationProgress.kt
index a5326e4ad3..3da3b8e3bd 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/events/EventIobCalculationProgress.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/events/EventIobCalculationProgress.kt
@@ -1,5 +1,6 @@
package info.nightscout.androidaps.plugins.iob.iobCobCalculator.events
import info.nightscout.androidaps.events.Event
+import info.nightscout.androidaps.workflow.CalculationWorkflow
-class EventIobCalculationProgress(val progress: String, val cause: Event?) : Event()
\ No newline at end of file
+class EventIobCalculationProgress(val pass: CalculationWorkflow.ProgressData, val progressPct: Int, val cause: Event?) : Event()
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/profile/local/LocalProfileFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/profile/local/LocalProfileFragment.kt
index 8892c397a9..a68d6ee25a 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/profile/local/LocalProfileFragment.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/profile/local/LocalProfileFragment.kt
@@ -3,37 +3,44 @@ package info.nightscout.androidaps.plugins.profile.local
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
-import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
+import com.google.android.material.tabs.TabLayout
import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R
+import info.nightscout.androidaps.activities.SingleFragmentActivity
import info.nightscout.androidaps.data.ProfileSealed
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.databinding.LocalprofileFragmentBinding
import info.nightscout.androidaps.dialogs.ProfileSwitchDialog
+import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.GlucoseUnit
import info.nightscout.androidaps.interfaces.Profile
+import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.profile.local.events.EventLocalProfileChanged
-import info.nightscout.androidaps.utils.*
+import info.nightscout.androidaps.utils.DateUtil
+import info.nightscout.androidaps.utils.DecimalFormatter
+import info.nightscout.androidaps.utils.FabricPrivacy
+import info.nightscout.androidaps.utils.HardLimits
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.utils.protection.ProtectionCheck
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
-import info.nightscout.androidaps.utils.ui.SpinnerHelper
import info.nightscout.androidaps.utils.ui.TimeListEdit
import info.nightscout.shared.SafeParse
import info.nightscout.shared.logging.AAPSLogger
-import io.reactivex.disposables.CompositeDisposable
-import io.reactivex.rxkotlin.plusAssign
+import info.nightscout.shared.logging.LTag
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.kotlin.plusAssign
import java.math.RoundingMode
import java.text.DecimalFormat
import javax.inject.Inject
@@ -46,20 +53,22 @@ class LocalProfileFragment : DaggerFragment() {
@Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var fabricPrivacy: FabricPrivacy
@Inject lateinit var localProfilePlugin: LocalProfilePlugin
+ @Inject lateinit var profileFunction: ProfileFunction
@Inject lateinit var hardLimits: HardLimits
+ @Inject lateinit var protectionCheck: ProtectionCheck
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var aapsSchedulers: AapsSchedulers
@Inject lateinit var uel: UserEntryLogger
private var disposable: CompositeDisposable = CompositeDisposable()
-
+ private var inMenu = false
+ private var queryingProtection = false
private var basalView: TimeListEdit? = null
- private var spinner: SpinnerHelper? = null
private val save = Runnable {
doEdit()
basalView?.updateLabel(rh.gs(R.string.basal_label) + ": " + sumLabel())
- localProfilePlugin.profile?.getSpecificProfile(spinner?.selectedItem.toString())?.let {
+ localProfilePlugin.getEditedProfile()?.let {
binding.basalGraph.show(ProfileSealed.Pure(it))
binding.icGraph.show(ProfileSealed.Pure(it))
binding.isfGraph.show(ProfileSealed.Pure(it))
@@ -79,15 +88,14 @@ class LocalProfileFragment : DaggerFragment() {
}
private fun sumLabel(): String {
- val profile = localProfilePlugin.getEditProfile()
+ val profile = localProfilePlugin.getEditedProfile()
val sum = profile?.let { ProfileSealed.Pure(profile).baseBasalSum() } ?: 0.0
return " ∑" + DecimalFormatter.to2Decimal(sum) + rh.gs(R.string.insulin_unit_shortname)
}
private var _binding: LocalprofileFragmentBinding? = null
- // This property is only valid between onCreateView and
- // onDestroyView.
+ // This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
@@ -97,30 +105,25 @@ class LocalProfileFragment : DaggerFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- // activate DIA tab
- processVisibilityOnClick(binding.diaTab)
- binding.diaPlaceholder.visibility = View.VISIBLE
- // setup listeners
- binding.diaTab.setOnClickListener {
- processVisibilityOnClick(it)
- binding.diaPlaceholder.visibility = View.VISIBLE
- }
- binding.icTab.setOnClickListener {
- processVisibilityOnClick(it)
- binding.ic.visibility = View.VISIBLE
- }
- binding.isfTab.setOnClickListener {
- processVisibilityOnClick(it)
- binding.isf.visibility = View.VISIBLE
- }
- binding.basalTab.setOnClickListener {
- processVisibilityOnClick(it)
- binding.basal.visibility = View.VISIBLE
- }
- binding.targetTab.setOnClickListener {
- processVisibilityOnClick(it)
- binding.target.visibility = View.VISIBLE
- }
+ val parentClass = this.activity?.let { it::class.java }
+ inMenu = parentClass == SingleFragmentActivity::class.java
+ updateProtectedUi()
+ processVisibility(0)
+ binding.tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
+ override fun onTabSelected(tab: TabLayout.Tab) {
+ processVisibility(tab.position)
+ }
+
+ override fun onTabUnselected(tab: TabLayout.Tab) {}
+ override fun onTabReselected(tab: TabLayout.Tab) {}
+ })
+ binding.diaLabel.labelFor = binding.dia.editTextId
+ binding.unlock.setOnClickListener { queryProtection() }
+
+ val profiles = localProfilePlugin.profile?.getProfileList() ?: ArrayList()
+ val activeProfile = profileFunction.getProfileName()
+ val profileIndex = profiles.indexOf(activeProfile)
+ localProfilePlugin.currentProfileIndex = if (profileIndex >= 0) profileIndex else 0
}
fun build() {
@@ -134,56 +137,114 @@ class LocalProfileFragment : DaggerFragment() {
binding.name.addTextChangedListener(textWatch)
binding.dia.setParams(currentProfile.dia, hardLimits.minDia(), hardLimits.maxDia(), 0.1, DecimalFormat("0.0"), false, null, textWatch)
binding.dia.tag = "LP_DIA"
- TimeListEdit(context, aapsLogger, dateUtil, view, R.id.ic_holder, "IC", rh.gs(R.string.ic_long_label), currentProfile.ic, null, doubleArrayOf(hardLimits.minIC(), hardLimits.maxIC()), null, 0.1, DecimalFormat ("0.0"), save)
- basalView = TimeListEdit(context, aapsLogger, dateUtil, view, R.id.basal_holder, "BASAL", rh.gs(R.string.basal_long_label) + ": " + sumLabel(), currentProfile.basal, null, doubleArrayOf(pumpDescription.basalMinimumRate, pumpDescription.basalMaximumRate), null, 0.01, DecimalFormat("0.00"), save)
+ TimeListEdit(
+ context,
+ aapsLogger,
+ dateUtil,
+ view,
+ R.id.ic_holder,
+ "IC",
+ rh.gs(R.string.ic_long_label),
+ currentProfile.ic,
+ null,
+ doubleArrayOf(hardLimits.minIC(), hardLimits.maxIC()),
+ null,
+ 0.1,
+ DecimalFormat("0.0"),
+ save
+ )
+ basalView =
+ TimeListEdit(
+ context,
+ aapsLogger,
+ dateUtil,
+ view,
+ R.id.basal_holder,
+ "BASAL",
+ rh.gs(R.string.basal_long_label) + ": " + sumLabel(),
+ currentProfile.basal,
+ null,
+ doubleArrayOf(pumpDescription.basalMinimumRate, pumpDescription.basalMaximumRate),
+ null,
+ 0.01,
+ DecimalFormat("0.00"),
+ save
+ )
if (units == Constants.MGDL) {
val isfRange = doubleArrayOf(HardLimits.MIN_ISF, HardLimits.MAX_ISF)
- TimeListEdit(context, aapsLogger, dateUtil, view, R.id.isf_holder, "ISF", rh.gs(R.string.isf_long_label), currentProfile.isf, null, isfRange , null, 1.0, DecimalFormat("0"), save)
- TimeListEdit(context, aapsLogger, dateUtil, view, R.id.target_holder, "TARGET", rh.gs(R.string.target_long_label), currentProfile.targetLow, currentProfile.targetHigh, HardLimits.VERY_HARD_LIMIT_MIN_BG, HardLimits.VERY_HARD_LIMIT_TARGET_BG, 1.0, DecimalFormat("0"), save)
+ TimeListEdit(context, aapsLogger, dateUtil, view, R.id.isf_holder, "ISF", rh.gs(R.string.isf_long_label), currentProfile.isf, null, isfRange, null, 1.0, DecimalFormat("0"), save)
+ TimeListEdit(
+ context,
+ aapsLogger,
+ dateUtil,
+ view,
+ R.id.target_holder,
+ "TARGET",
+ rh.gs(R.string.target_long_label),
+ currentProfile.targetLow,
+ currentProfile.targetHigh,
+ HardLimits.VERY_HARD_LIMIT_MIN_BG,
+ HardLimits.VERY_HARD_LIMIT_TARGET_BG,
+ 1.0,
+ DecimalFormat("0"),
+ save
+ )
} else {
- val isfRange = doubleArrayOf(roundUp(Profile.fromMgdlToUnits(HardLimits.MIN_ISF, GlucoseUnit.MMOL)),
- roundDown(Profile.fromMgdlToUnits(HardLimits.MAX_ISF, GlucoseUnit.MMOL)))
- TimeListEdit(context, aapsLogger, dateUtil, view, R.id.isf_holder, "ISF", rh.gs(R.string.isf_long_label), currentProfile.isf, null,isfRange , null, 0.1, DecimalFormat("0.0"), save)
- val range1 = doubleArrayOf(roundUp(Profile.fromMgdlToUnits(HardLimits.VERY_HARD_LIMIT_MIN_BG[0], GlucoseUnit.MMOL)),
- roundDown(Profile.fromMgdlToUnits(HardLimits.VERY_HARD_LIMIT_MIN_BG[1], GlucoseUnit.MMOL)))
- val range2 = doubleArrayOf(roundUp(Profile.fromMgdlToUnits(HardLimits.VERY_HARD_LIMIT_MAX_BG[0], GlucoseUnit.MMOL)),
- roundDown(Profile.fromMgdlToUnits(HardLimits.VERY_HARD_LIMIT_MAX_BG[1], GlucoseUnit.MMOL)))
- Log.i("TimeListEdit", "build: range1" + range1[0] + " " + range1[1] + " range2" + range2[0] + " " + range2[1])
- TimeListEdit(context, aapsLogger, dateUtil, view, R.id.target_holder, "TARGET", rh.gs(R.string.target_long_label), currentProfile.targetLow, currentProfile.targetHigh, range1 , range2, 0.1, DecimalFormat("0.0"), save)
+ val isfRange = doubleArrayOf(
+ roundUp(Profile.fromMgdlToUnits(HardLimits.MIN_ISF, GlucoseUnit.MMOL)),
+ roundDown(Profile.fromMgdlToUnits(HardLimits.MAX_ISF, GlucoseUnit.MMOL))
+ )
+ TimeListEdit(context, aapsLogger, dateUtil, view, R.id.isf_holder, "ISF", rh.gs(R.string.isf_long_label), currentProfile.isf, null, isfRange, null, 0.1, DecimalFormat("0.0"), save)
+ val range1 = doubleArrayOf(
+ roundUp(Profile.fromMgdlToUnits(HardLimits.VERY_HARD_LIMIT_MIN_BG[0], GlucoseUnit.MMOL)),
+ roundDown(Profile.fromMgdlToUnits(HardLimits.VERY_HARD_LIMIT_MIN_BG[1], GlucoseUnit.MMOL))
+ )
+ val range2 = doubleArrayOf(
+ roundUp(Profile.fromMgdlToUnits(HardLimits.VERY_HARD_LIMIT_MAX_BG[0], GlucoseUnit.MMOL)),
+ roundDown(Profile.fromMgdlToUnits(HardLimits.VERY_HARD_LIMIT_MAX_BG[1], GlucoseUnit.MMOL))
+ )
+ aapsLogger.info(LTag.CORE, "TimeListEdit", "build: range1" + range1[0] + " " + range1[1] + " range2" + range2[0] + " " + range2[1])
+ TimeListEdit(
+ context,
+ aapsLogger,
+ dateUtil,
+ view,
+ R.id.target_holder,
+ "TARGET",
+ rh.gs(R.string.target_long_label),
+ currentProfile.targetLow,
+ currentProfile.targetHigh,
+ range1,
+ range2,
+ 0.1,
+ DecimalFormat("0.0"),
+ save
+ )
}
- // Spinner
- spinner = SpinnerHelper(binding.spinner)
context?.let { context ->
val profileList: ArrayList = localProfilePlugin.profile?.getProfileList() ?: ArrayList()
- spinner?.adapter = ArrayAdapter(context, R.layout.spinner_centered, profileList)
- val selection = localProfilePlugin.currentProfileIndex
- if (selection in 0 until profileList.size) spinner?.setSelection(selection)
+ binding.profileList.setAdapter(ArrayAdapter(context, R.layout.spinner_centered, profileList))
} ?: return
- spinner?.setOnItemSelectedListener(object : AdapterView.OnItemSelectedListener {
- override fun onNothingSelected(parent: AdapterView<*>?) {
- }
- override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
- if (localProfilePlugin.isEdited) {
- activity?.let { activity ->
- OKDialog.showConfirmation(activity, rh.gs(R.string.doyouwantswitchprofile), {
+ binding.profileList.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ ->
+ if (localProfilePlugin.isEdited) {
+ activity?.let { activity ->
+ OKDialog.showConfirmation(
+ activity, rh.gs(R.string.doyouwantswitchprofile),
+ {
localProfilePlugin.currentProfileIndex = position
localProfilePlugin.isEdited = false
build()
- }, {
- val selection = localProfilePlugin.currentProfileIndex
- if (selection in 0 until (spinner?.adapter?.count ?: -1)) spinner?.setSelection(selection)
- }
- )
- }
- } else {
- localProfilePlugin.currentProfileIndex = position
- build()
+ }, null
+ )
}
+ } else {
+ localProfilePlugin.currentProfileIndex = position
+ build()
}
- })
- localProfilePlugin.profile?.getSpecificProfile(spinner?.selectedItem.toString())?.let {
+ }
+ localProfilePlugin.getEditedProfile()?.let {
binding.basalGraph.show(ProfileSealed.Pure(it))
binding.icGraph.show(ProfileSealed.Pure(it))
binding.isfGraph.show(ProfileSealed.Pure(it))
@@ -205,8 +266,12 @@ class LocalProfileFragment : DaggerFragment() {
if (localProfilePlugin.isEdited) {
activity?.let { OKDialog.show(it, "", rh.gs(R.string.saveorresetchangesfirst)) }
} else {
- uel.log(Action.CLONE_PROFILE, Sources.LocalProfile, ValueWithUnit.SimpleString(localProfilePlugin.currentProfile()?.name
- ?: ""))
+ uel.log(
+ Action.CLONE_PROFILE, Sources.LocalProfile, ValueWithUnit.SimpleString(
+ localProfilePlugin.currentProfile()?.name
+ ?: ""
+ )
+ )
localProfilePlugin.cloneProfile()
build()
}
@@ -215,8 +280,12 @@ class LocalProfileFragment : DaggerFragment() {
binding.profileRemove.setOnClickListener {
activity?.let { activity ->
OKDialog.showConfirmation(activity, rh.gs(R.string.deletecurrentprofile), {
- uel.log(Action.PROFILE_REMOVED, Sources.LocalProfile, ValueWithUnit.SimpleString(localProfilePlugin.currentProfile()?.name
- ?: ""))
+ uel.log(
+ Action.PROFILE_REMOVED, Sources.LocalProfile, ValueWithUnit.SimpleString(
+ localProfilePlugin.currentProfile()?.name
+ ?: ""
+ )
+ )
localProfilePlugin.removeCurrentProfile()
build()
}, null)
@@ -231,7 +300,7 @@ class LocalProfileFragment : DaggerFragment() {
binding.profileswitch.setOnClickListener {
ProfileSwitchDialog()
- .also { it.arguments = Bundle().also { bundle -> bundle.putInt("profileIndex", localProfilePlugin.currentProfileIndex) } }
+ .also { it.arguments = Bundle().also { bundle -> bundle.putString("profileName", localProfilePlugin.currentProfile()?.name) } }
.show(childFragmentManager, "ProfileSwitchDialog")
}
@@ -244,8 +313,12 @@ class LocalProfileFragment : DaggerFragment() {
if (!localProfilePlugin.isValidEditState(activity)) {
return@setOnClickListener //Should not happen as saveButton should not be visible if not valid
}
- uel.log(Action.STORE_PROFILE, Sources.LocalProfile, ValueWithUnit.SimpleString(localProfilePlugin.currentProfile()?.name
- ?: ""))
+ uel.log(
+ Action.STORE_PROFILE, Sources.LocalProfile, ValueWithUnit.SimpleString(
+ localProfilePlugin.currentProfile()?.name
+ ?: ""
+ )
+ )
localProfilePlugin.storeSettings(activity)
build()
}
@@ -255,6 +328,7 @@ class LocalProfileFragment : DaggerFragment() {
@Synchronized
override fun onResume() {
super.onResume()
+ if (inMenu) queryProtection() else updateProtectedUi()
disposable += rxBus
.toObservable(EventLocalProfileChanged::class.java)
.observeOn(aapsSchedulers.main)
@@ -292,8 +366,8 @@ class LocalProfileFragment : DaggerFragment() {
val isValid = localProfilePlugin.isValidEditState(activity)
val isEdited = localProfilePlugin.isEdited
if (isValid) {
- this.view?.setBackgroundColor(rh.gc(R.color.ok_background))
- binding.spinner.isEnabled = true
+ this.view?.setBackgroundColor(rh.gac(context, R.attr.okBackgroundColor))
+ binding.profileList.isEnabled = true
if (isEdited) {
//edited profile -> save first
@@ -304,8 +378,8 @@ class LocalProfileFragment : DaggerFragment() {
binding.save.visibility = View.GONE
}
} else {
- this.view?.setBackgroundColor(rh.gc(R.color.error_background))
- binding.spinner.isEnabled = false
+ this.view?.setBackgroundColor(rh.gac(context, R.attr.errorBackgroundColor))
+ binding.profileList.isEnabled = false
binding.profileswitch.visibility = View.GONE
binding.save.visibility = View.GONE //don't save an invalid profile
}
@@ -318,17 +392,29 @@ class LocalProfileFragment : DaggerFragment() {
}
}
- private fun processVisibilityOnClick(selected: View) {
- binding.diaTab.setBackgroundColor(rh.gc(R.color.defaultbackground))
- binding.icTab.setBackgroundColor(rh.gc(R.color.defaultbackground))
- binding.isfTab.setBackgroundColor(rh.gc(R.color.defaultbackground))
- binding.basalTab.setBackgroundColor(rh.gc(R.color.defaultbackground))
- binding.targetTab.setBackgroundColor(rh.gc(R.color.defaultbackground))
- selected.setBackgroundColor(rh.gc(R.color.tabBgColorSelected))
- binding.diaPlaceholder.visibility = View.GONE
- binding.ic.visibility = View.GONE
- binding.isf.visibility = View.GONE
- binding.basal.visibility = View.GONE
- binding.target.visibility = View.GONE
+ private fun processVisibility(position: Int) {
+ binding.diaPlaceholder.visibility = (position == 0).toVisibility()
+ binding.ic.visibility = (position == 1).toVisibility()
+ binding.isf.visibility = (position == 2).toVisibility()
+ binding.basal.visibility = (position == 3).toVisibility()
+ binding.target.visibility = (position == 4).toVisibility()
+ }
+
+ private fun updateProtectedUi() {
+ _binding ?: return
+ val isLocked = protectionCheck.isLocked(ProtectionCheck.Protection.PREFERENCES)
+ binding.mainLayout.visibility = isLocked.not().toVisibility()
+ binding.unlock.visibility = isLocked.toVisibility()
+ }
+
+ private fun queryProtection() {
+ val isLocked = protectionCheck.isLocked(ProtectionCheck.Protection.PREFERENCES)
+ if (isLocked && !queryingProtection) {
+ activity?.let { activity ->
+ queryingProtection = true
+ val doUpdate = { activity.runOnUiThread { queryingProtection = false; updateProtectedUi() } }
+ protectionCheck.queryProtection(activity, ProtectionCheck.Protection.PREFERENCES, doUpdate, doUpdate, doUpdate)
+ }
+ }
}
}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/profile/local/LocalProfilePlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/profile/local/LocalProfilePlugin.kt
index cce7fc55d0..1908e1ef39 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/profile/local/LocalProfilePlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/profile/local/LocalProfilePlugin.kt
@@ -25,7 +25,7 @@ import info.nightscout.androidaps.plugins.profile.local.events.EventLocalProfile
import info.nightscout.androidaps.receivers.DataWorker
import info.nightscout.androidaps.utils.*
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONArray
import org.json.JSONException
@@ -170,7 +170,7 @@ class LocalProfilePlugin @Inject constructor(
}
@Synchronized
- fun getEditProfile(): PureProfile? {
+ fun getEditedProfile(): PureProfile? {
val profile = JSONObject()
with(profiles[currentProfileIndex]) {
profile.put("dia", dia)
@@ -461,6 +461,7 @@ class LocalProfilePlugin @Inject constructor(
@Inject lateinit var sp: SP
@Inject lateinit var config: Config
@Inject lateinit var localProfilePlugin: LocalProfilePlugin
+ @Inject lateinit var xDripBroadcast: XDripBroadcast
init {
(context.applicationContext as HasAndroidInjector).androidInjector().inject(this)
@@ -469,6 +470,7 @@ class LocalProfilePlugin @Inject constructor(
override fun doWork(): Result {
val profileJson = dataWorker.pickupJSONObject(inputData.getLong(DataWorker.STORE_KEY, -1))
?: return Result.failure(workDataOf("Error" to "missing input data"))
+ xDripBroadcast.sendProfile(profileJson)
if (sp.getBoolean(R.string.key_ns_receive_profile_store, true) || config.NSCLIENT) {
val store = ProfileStore(injector, profileJson, dateUtil)
val createdAt = store.getStartDate()
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/mdi/MDIPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/mdi/MDIPlugin.kt
deleted file mode 100644
index c7e9ee9b9b..0000000000
--- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/mdi/MDIPlugin.kt
+++ /dev/null
@@ -1,158 +0,0 @@
-package info.nightscout.androidaps.plugins.pump.mdi
-
-import dagger.android.HasAndroidInjector
-import info.nightscout.androidaps.R
-import info.nightscout.androidaps.data.DetailedBolusInfo
-import info.nightscout.androidaps.data.PumpEnactResult
-import info.nightscout.androidaps.interfaces.*
-import info.nightscout.shared.logging.AAPSLogger
-import info.nightscout.shared.logging.LTag
-import info.nightscout.androidaps.plugins.common.ManufacturerType
-import info.nightscout.androidaps.plugins.pump.common.defs.PumpType
-import info.nightscout.androidaps.utils.DateUtil
-import info.nightscout.androidaps.utils.InstanceId.instanceId
-import info.nightscout.androidaps.utils.resources.ResourceHelper
-import org.json.JSONException
-import org.json.JSONObject
-import javax.inject.Inject
-import javax.inject.Singleton
-
-@Singleton
-class MDIPlugin @Inject constructor(
- injector: HasAndroidInjector,
- aapsLogger: AAPSLogger,
- rh: ResourceHelper,
- commandQueue: CommandQueue,
- private val dateUtil: DateUtil,
- private val pumpSync: PumpSync
-) : PumpPluginBase(PluginDescription()
- .mainType(PluginType.PUMP)
- .pluginIcon(R.drawable.ic_ict)
- .pluginName(R.string.mdi)
- .description(R.string.description_pump_mdi),
- injector, aapsLogger, rh, commandQueue
-), Pump {
-
- override val pumpDescription = PumpDescription()
-
- init {
- pumpDescription.isBolusCapable = true
- pumpDescription.bolusStep = 0.5
- pumpDescription.isExtendedBolusCapable = false
- pumpDescription.isTempBasalCapable = false
- pumpDescription.isSetBasalProfileCapable = false
- pumpDescription.isRefillingCapable = false
- pumpDescription.isBatteryReplaceable = false
- }
-
- override val isFakingTempsByExtendedBoluses: Boolean = false
-
- override fun loadTDDs(): PumpEnactResult = PumpEnactResult(injector)
- override fun isInitialized(): Boolean = true
- override fun isSuspended(): Boolean = false
- override fun isBusy(): Boolean = false
- override fun isConnected(): Boolean = true
- override fun isConnecting(): Boolean = false
- override fun isHandshakeInProgress(): Boolean = false
- override fun connect(reason: String) {}
- override fun disconnect(reason: String) {}
- override fun waitForDisconnectionInSeconds(): Int = 0
- override fun stopConnecting() {}
- override fun getPumpStatus(reason: String) {}
- override fun setNewBasalProfile(profile: Profile): PumpEnactResult = PumpEnactResult(injector).success(true).enacted(true)
- override fun isThisProfileSet(profile: Profile): Boolean = false
- override fun lastDataTime(): Long = System.currentTimeMillis()
- override val baseBasalRate: Double = 0.0
- override val reservoirLevel: Double = -1.0
- override val batteryLevel: Int = -1
-
- override fun deliverTreatment(detailedBolusInfo: DetailedBolusInfo): PumpEnactResult {
- val result = PumpEnactResult(injector)
- result.success = true
- result.bolusDelivered = detailedBolusInfo.insulin
- result.carbsDelivered = detailedBolusInfo.carbs
- result.comment = rh.gs(R.string.virtualpump_resultok)
- if (detailedBolusInfo.insulin > 0)
- pumpSync.syncBolusWithPumpId(
- timestamp = detailedBolusInfo.timestamp,
- amount = detailedBolusInfo.insulin,
- type = detailedBolusInfo.bolusType,
- pumpId = dateUtil.now(),
- pumpType = PumpType.MDI,
- pumpSerial = serialNumber())
- if (detailedBolusInfo.carbs > 0)
- pumpSync.syncCarbsWithTimestamp(
- timestamp = detailedBolusInfo.carbsTimestamp ?: detailedBolusInfo.timestamp,
- amount = detailedBolusInfo.carbs,
- pumpId = null,
- pumpType = PumpType.MDI,
- pumpSerial = serialNumber())
- return result
- }
-
- override fun stopBolusDelivering() {}
- override fun setTempBasalAbsolute(absoluteRate: Double, durationInMinutes: Int, profile: Profile, enforceNew: Boolean, tbrType: PumpSync.TemporaryBasalType): PumpEnactResult {
- val result = PumpEnactResult(injector)
- result.success = false
- result.comment = rh.gs(R.string.pumperror)
- aapsLogger.debug(LTag.PUMPBTCOMM, "Setting temp basal absolute: $result")
- return result
- }
-
- override fun setTempBasalPercent(percent: Int, durationInMinutes: Int, profile: Profile, enforceNew: Boolean, tbrType: PumpSync.TemporaryBasalType): PumpEnactResult {
- val result = PumpEnactResult(injector)
- result.success = false
- result.comment = rh.gs(R.string.pumperror)
- aapsLogger.debug(LTag.PUMPBTCOMM, "Settings temp basal percent: $result")
- return result
- }
-
- override fun setExtendedBolus(insulin: Double, durationInMinutes: Int): PumpEnactResult {
- val result = PumpEnactResult(injector)
- result.success = false
- result.comment = rh.gs(R.string.pumperror)
- aapsLogger.debug(LTag.PUMPBTCOMM, "Setting extended bolus: $result")
- return result
- }
-
- override fun cancelTempBasal(enforceNew: Boolean): PumpEnactResult {
- val result = PumpEnactResult(injector)
- result.success = false
- result.comment = rh.gs(R.string.pumperror)
- aapsLogger.debug(LTag.PUMPBTCOMM, "Cancel temp basal: $result")
- return result
- }
-
- override fun cancelExtendedBolus(): PumpEnactResult {
- val result = PumpEnactResult(injector)
- result.success = false
- result.comment = rh.gs(R.string.pumperror)
- aapsLogger.debug(LTag.PUMPBTCOMM, "Canceling extended bolus: $result")
- return result
- }
-
- override fun getJSONStatus(profile: Profile, profileName: String, version: String): JSONObject {
- val now = System.currentTimeMillis()
- val pump = JSONObject()
- val status = JSONObject()
- val extended = JSONObject()
- try {
- status.put("status", "normal")
- extended.put("Version", version)
- extended.put("ActiveProfile", profileName)
- status.put("timestamp", dateUtil.toISOString(now))
- pump.put("status", status)
- pump.put("extended", extended)
- pump.put("clock", dateUtil.toISOString(now))
- } catch (e: JSONException) {
- aapsLogger.error("Exception: ", e)
- }
- return pump
- }
-
- override fun manufacturer(): ManufacturerType = ManufacturerType.AndroidAPS
- override fun model(): PumpType = PumpType.MDI
- override fun serialNumber(): String = instanceId()
- override fun shortStatus(veryShort: Boolean): String = model().model
- override fun canHandleDST(): Boolean = true
-}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpFragment.kt
index 382efc02cc..1f3d8b1f29 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpFragment.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpFragment.kt
@@ -19,10 +19,10 @@ import info.nightscout.androidaps.plugins.pump.virtual.events.EventVirtualPumpUp
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.T
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
-import io.reactivex.disposables.CompositeDisposable
-import io.reactivex.rxkotlin.plusAssign
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.kotlin.plusAssign
import javax.inject.Inject
class VirtualPumpFragment : DaggerFragment() {
@@ -47,14 +47,8 @@ class VirtualPumpFragment : DaggerFragment() {
// onDestroyView.
private val binding get() = _binding!!
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
- _binding = VirtualpumpFragmentBinding.inflate(inflater, container, false)
- return binding.root
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- }
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
+ VirtualpumpFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root
@Synchronized
override fun onResume() {
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpPlugin.kt
index d4a0ba1687..0284ce9259 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpPlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpPlugin.kt
@@ -11,8 +11,6 @@ import info.nightscout.androidaps.events.EventPreferenceChange
import info.nightscout.androidaps.extensions.convertedToAbsolute
import info.nightscout.androidaps.extensions.plannedRemainingMinutes
import info.nightscout.androidaps.interfaces.*
-import info.nightscout.shared.logging.AAPSLogger
-import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.common.ManufacturerType
import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification
@@ -22,14 +20,16 @@ import info.nightscout.androidaps.plugins.pump.common.defs.PumpType
import info.nightscout.androidaps.plugins.pump.virtual.events.EventVirtualPumpUpdateGui
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
-import info.nightscout.androidaps.utils.InstanceId.instanceId
+import info.nightscout.androidaps.utils.InstanceId
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.TimeChangeType
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
-import io.reactivex.disposables.CompositeDisposable
-import io.reactivex.rxkotlin.plusAssign
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.kotlin.plusAssign
import org.json.JSONException
import org.json.JSONObject
import javax.inject.Inject
@@ -365,7 +365,7 @@ open class VirtualPumpPlugin @Inject constructor(
override fun model(): PumpType = pumpDescription.pumpType
- override fun serialNumber(): String = instanceId()
+ override fun serialNumber(): String = InstanceId.instanceId
override fun shortStatus(veryShort: Boolean): String = "Virtual Pump"
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/AbstractSensitivityPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/AbstractSensitivityPlugin.kt
index a8132a5c2f..a942d50f94 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/AbstractSensitivityPlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/AbstractSensitivityPlugin.kt
@@ -11,7 +11,7 @@ import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensDataStore
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult
import info.nightscout.androidaps.utils.Round
import info.nightscout.shared.SafeParse
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import kotlin.math.max
import kotlin.math.min
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityAAPSPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityAAPSPlugin.kt
index 723905f31f..dee4efb932 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityAAPSPlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityAAPSPlugin.kt
@@ -19,7 +19,7 @@ import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensDataStore
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin
import info.nightscout.androidaps.utils.DateUtil
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONException
import org.json.JSONObject
@@ -137,6 +137,8 @@ class SensitivityAAPSPlugin @Inject constructor(
return output
}
+ override fun maxAbsorptionHours(): Double = sp.getDouble(R.string.key_absorption_maxtime, Constants.DEFAULT_MAX_ABSORPTION_TIME)
+
override val id: SensitivityType
get() = SensitivityType.SENSITIVITY_AAPS
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityOref1Plugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityOref1Plugin.kt
index 35230bb10b..cccd3cbeff 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityOref1Plugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityOref1Plugin.kt
@@ -20,7 +20,7 @@ import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensDataStore
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin
import info.nightscout.androidaps.utils.DateUtil
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONException
import org.json.JSONObject
@@ -53,8 +53,6 @@ class SensitivityOref1Plugin @Inject constructor(
) {
override fun detectSensitivity(ads: AutosensDataStore, fromTime: Long, toTime: Long): AutosensResult {
- // todo this method is called from the IobCobCalculatorPlugin, which leads to a circular
- // dependency, this should be avoided
val profile = profileFunction.getProfile()
if (profile == null) {
aapsLogger.error("No profile")
@@ -204,6 +202,8 @@ class SensitivityOref1Plugin @Inject constructor(
return output
}
+ override fun maxAbsorptionHours(): Double = sp.getDouble(R.string.key_absorption_cutoff, Constants.DEFAULT_MAX_ABSORPTION_TIME)
+
override fun configuration(): JSONObject {
val c = JSONObject()
try {
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityWeightedAveragePlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityWeightedAveragePlugin.kt
index 639cde7acc..aa3033733e 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityWeightedAveragePlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityWeightedAveragePlugin.kt
@@ -19,7 +19,7 @@ import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensDataStore
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult
import info.nightscout.androidaps.utils.DateUtil
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONException
import org.json.JSONObject
@@ -157,6 +157,8 @@ class SensitivityWeightedAveragePlugin @Inject constructor(
return output
}
+ override fun maxAbsorptionHours(): Double = sp.getDouble(R.string.key_absorption_maxtime, Constants.DEFAULT_MAX_ABSORPTION_TIME)
+
override val id: SensitivityType
get() = SensitivityType.SENSITIVITY_WEIGHTED
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/AidexPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/source/AidexPlugin.kt
new file mode 100644
index 0000000000..3e0a9d2d8c
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/AidexPlugin.kt
@@ -0,0 +1,115 @@
+package info.nightscout.androidaps.plugins.source
+
+import android.content.Context
+import androidx.work.Worker
+import androidx.work.WorkerParameters
+import androidx.work.workDataOf
+import dagger.android.HasAndroidInjector
+import info.nightscout.androidaps.Constants
+import info.nightscout.androidaps.R
+import info.nightscout.androidaps.database.AppRepository
+import info.nightscout.androidaps.database.entities.GlucoseValue
+import info.nightscout.androidaps.database.transactions.CgmSourceTransaction
+import info.nightscout.androidaps.interfaces.*
+import info.nightscout.androidaps.receivers.DataWorker
+import info.nightscout.androidaps.receivers.Intents
+import info.nightscout.androidaps.interfaces.BuildHelper
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.shared.logging.LTag
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class AidexPlugin @Inject constructor(
+ injector: HasAndroidInjector,
+ rh: ResourceHelper,
+ aapsLogger: AAPSLogger,
+ private val buildHelper: BuildHelper,
+ private val config: Config
+) : PluginBase(
+ PluginDescription()
+ .mainType(PluginType.BGSOURCE)
+ .fragmentClass(BGSourceFragment::class.java.name)
+ .pluginIcon((R.drawable.ic_blooddrop_48))
+ .pluginName(R.string.aidex)
+ .shortName(R.string.aidex_short)
+ .description(R.string.description_source_aidex),
+ aapsLogger, rh, injector
+), BgSource {
+
+ private var advancedFiltering = false
+
+ /**
+ * Aidex App doesn't have upload to NS
+ */
+ override fun shouldUploadToNs(glucoseValue: GlucoseValue): Boolean = true
+
+ override fun advancedFilteringSupported(): Boolean {
+ return advancedFiltering
+ }
+
+ // Allow only for pumpcontrol or dev & engineering_mode
+ override fun specialEnableCondition(): Boolean {
+ return config.APS.not() || buildHelper.isDev() && buildHelper.isEngineeringMode()
+ }
+
+ // cannot be inner class because of needed injection
+ class AidexWorker(
+ context: Context,
+ params: WorkerParameters
+ ) : Worker(context, params) {
+
+ @Inject lateinit var aapsLogger: AAPSLogger
+ @Inject lateinit var aidexPlugin: AidexPlugin
+ @Inject lateinit var repository: AppRepository
+ @Inject lateinit var dataWorker: DataWorker
+
+ init {
+ (context.applicationContext as HasAndroidInjector).androidInjector().inject(this)
+ }
+
+ override fun doWork(): Result {
+ var ret = Result.success()
+
+ if (!aidexPlugin.isEnabled()) return Result.success(workDataOf("Result" to "Plugin not enabled"))
+ val bundle = dataWorker.pickupBundle(inputData.getLong(DataWorker.STORE_KEY, -1))
+ ?: return Result.failure(workDataOf("Error" to "missing input data"))
+
+ aapsLogger.debug(LTag.BGSOURCE, "Received Aidex data: $bundle")
+
+ if (bundle.containsKey(Intents.AIDEX_TRANSMITTER_SN)) aapsLogger.debug(LTag.BGSOURCE, "transmitterSerialNumber: " + bundle.getString(Intents.AIDEX_TRANSMITTER_SN))
+ if (bundle.containsKey(Intents.AIDEX_SENSOR_ID)) aapsLogger.debug(LTag.BGSOURCE, "sensorId: " + bundle.getString(Intents.AIDEX_SENSOR_ID))
+
+ val glucoseValues = mutableListOf()
+
+ val timestamp = bundle.getLong(Intents.AIDEX_TIMESTAMP, 0)
+ val bgType = bundle.getString(Intents.AIDEX_BG_TYPE, "mg/dl")
+ val bgValue = bundle.getDouble(Intents.AIDEX_BG_VALUE, 0.0)
+
+ val bgValueTarget = if (bgType.equals("mg/dl")) bgValue else bgValue * Constants.MMOLL_TO_MGDL
+
+ aapsLogger.debug(LTag.BGSOURCE, "Received Aidex broadcast [time=$timestamp, bgType=$bgType, value=$bgValue, targetValue=$bgValueTarget")
+
+ glucoseValues += CgmSourceTransaction.TransactionGlucoseValue(
+ timestamp = timestamp,
+ value = bgValueTarget,
+ raw = null,
+ noise = null,
+ trendArrow = GlucoseValue.TrendArrow.fromString(bundle.getString(Intents.AIDEX_BG_SLOPE_NAME)),
+ sourceSensor = GlucoseValue.SourceSensor.AIDEX
+ )
+ repository.runTransactionForResult(CgmSourceTransaction(glucoseValues, emptyList(), null))
+ .doOnError {
+ aapsLogger.error(LTag.DATABASE, "Error while saving values from Aidex", it)
+ ret = Result.failure(workDataOf("Error" to it.toString()))
+ }
+ .blockingGet()
+ .also { savedValues ->
+ savedValues.all().forEach {
+ aapsLogger.debug(LTag.DATABASE, "Inserted bg $it")
+ }
+ }
+ return ret
+ }
+ }
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/BGSourceFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/source/BGSourceFragment.kt
index 901bed4b26..a3fd240570 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/source/BGSourceFragment.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/BGSourceFragment.kt
@@ -1,10 +1,9 @@
package info.nightscout.androidaps.plugins.source
-import android.graphics.Paint
import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
+import android.util.SparseArray
+import android.view.*
+import androidx.core.util.forEach
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import dagger.android.support.DaggerFragment
@@ -24,18 +23,19 @@ import info.nightscout.androidaps.extensions.valueToUnitsString
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.PluginBase
import info.nightscout.androidaps.interfaces.ProfileFunction
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBus
+import info.nightscout.androidaps.utils.ActionModeHelper
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
-import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
-import io.reactivex.disposables.CompositeDisposable
-import io.reactivex.rxkotlin.plusAssign
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.kotlin.plusAssign
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@@ -54,15 +54,20 @@ class BGSourceFragment : DaggerFragment() {
private val disposable = CompositeDisposable()
private val millsToThePast = T.hours(36).msecs()
-
+ private lateinit var actionHelper: ActionModeHelper
private var _binding: BgsourceFragmentBinding? = null
- // This property is only valid between onCreateView and
- // onDestroyView.
+ // This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
- BgsourceFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root
+ BgsourceFragmentBinding.inflate(inflater, container, false).also {
+ _binding = it
+ actionHelper = ActionModeHelper(rh, activity, this)
+ actionHelper.setUpdateListHandler { binding.recyclerview.adapter?.notifyDataSetChanged() }
+ actionHelper.setOnRemoveHandler { handler -> removeSelected(handler) }
+ setHasOptionsMenu(true)
+ }.root
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@@ -94,10 +99,21 @@ class BGSourceFragment : DaggerFragment() {
@Synchronized
override fun onPause() {
+ actionHelper.finish()
disposable.clear()
super.onPause()
}
+ override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+ super.onCreateOptionsMenu(menu, inflater)
+ actionHelper.onCreateOptionsMenu(menu, inflater)
+ }
+
+ override fun onPrepareOptionsMenu(menu: Menu) {
+ super.onPrepareOptionsMenu(menu)
+ actionHelper.onPrepareOptionsMenu(menu)
+ }
+
@Synchronized
override fun onDestroyView() {
super.onDestroyView()
@@ -105,6 +121,9 @@ class BGSourceFragment : DaggerFragment() {
_binding = null
}
+ override fun onOptionsItemSelected(item: MenuItem) =
+ actionHelper.onOptionsItemSelected(item)
+
inner class RecyclerViewAdapter internal constructor(private var glucoseValues: List) : RecyclerView.Adapter() {
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): GlucoseValuesViewHolder {
@@ -116,58 +135,84 @@ class BGSourceFragment : DaggerFragment() {
val glucoseValue = glucoseValues[position]
holder.binding.ns.visibility = (glucoseValue.interfaceIDs.nightscoutId != null).toVisibility()
holder.binding.invalid.visibility = (!glucoseValue.isValid).toVisibility()
- val sameDayPrevious = position > 0 && dateUtil.isSameDay(glucoseValue.timestamp, glucoseValues[position-1].timestamp)
- holder.binding.date.visibility = sameDayPrevious.not().toVisibility()
- holder.binding.date.text = dateUtil.dateString(glucoseValue.timestamp)
+ val newDay = position == 0 || !dateUtil.isSameDay(glucoseValue.timestamp, glucoseValues[position - 1].timestamp)
+ holder.binding.date.visibility = newDay.toVisibility()
+ holder.binding.date.text = if (newDay) dateUtil.dateStringRelative(glucoseValue.timestamp, rh) else ""
holder.binding.time.text = dateUtil.timeString(glucoseValue.timestamp)
holder.binding.value.text = glucoseValue.valueToUnitsString(profileFunction.getUnits())
holder.binding.direction.setImageResource(glucoseValue.trendArrow.directionToIcon())
- holder.binding.remove.tag = glucoseValue
if (position > 0) {
val previous = glucoseValues[position - 1]
val diff = previous.timestamp - glucoseValue.timestamp
if (diff < T.secs(20).msecs())
- holder.binding.root.setBackgroundColor(rh.gc(R.color.errorAlertBackground))
+ holder.binding.root.setBackgroundColor(rh.gac(context, R.attr.bgsourceError))
}
+
+ holder.binding.root.setOnLongClickListener {
+ if (actionHelper.startRemove()) {
+ holder.binding.cbRemove.toggle()
+ actionHelper.updateSelection(position, glucoseValue, holder.binding.cbRemove.isChecked)
+ return@setOnLongClickListener true
+ }
+ false
+ }
+ holder.binding.root.setOnClickListener {
+ if (actionHelper.isRemoving) {
+ holder.binding.cbRemove.toggle()
+ actionHelper.updateSelection(position, glucoseValue, holder.binding.cbRemove.isChecked)
+ }
+ }
+ holder.binding.cbRemove.setOnCheckedChangeListener { _, value ->
+ actionHelper.updateSelection(position, glucoseValue, value)
+ }
+ holder.binding.cbRemove.isChecked = actionHelper.isSelected(position)
+ holder.binding.cbRemove.visibility = actionHelper.isRemoving.toVisibility()
}
- override fun getItemCount(): Int = glucoseValues.size
+ override fun getItemCount() = glucoseValues.size
inner class GlucoseValuesViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val binding = BgsourceItemBinding.bind(view)
+ }
+ }
- init {
- binding.remove.paintFlags = binding.remove.paintFlags or Paint.UNDERLINE_TEXT_FLAG
- binding.remove.setOnClickListener { v: View ->
- val glucoseValue = v.tag as GlucoseValue
- activity?.let { activity ->
- val text = dateUtil.dateAndTimeString(glucoseValue.timestamp) + "\n" + glucoseValue.valueToUnitsString(profileFunction.getUnits())
- OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), text, Runnable {
- val source = when ((activePlugin.activeBgSource as PluginBase).pluginDescription.pluginName) {
- R.string.dexcom_app_patched -> Sources.Dexcom
- R.string.eversense -> Sources.Eversense
- R.string.Glimp -> Sources.Glimp
- R.string.MM640g -> Sources.MM640g
- R.string.nsclientbg -> Sources.NSClientSource
- R.string.poctech -> Sources.PocTech
- R.string.tomato -> Sources.Tomato
- R.string.glunovo -> Sources.Glunovo
- R.string.xdrip -> Sources.Xdrip
- else -> Sources.Unknown
- }
- uel.log(
- Action.BG_REMOVED, source,
- ValueWithUnit.Timestamp(glucoseValue.timestamp)
- )
- repository.runTransactionForResult(InvalidateGlucoseValueTransaction(glucoseValue.id))
- .doOnError { aapsLogger.error(LTag.DATABASE, "Error while invalidating BG value", it) }
- .blockingGet()
- .also { result -> result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated bg $it") } }
- })
+ private fun getConfirmationText(selectedItems: SparseArray): String {
+ if (selectedItems.size() == 1) {
+ val glucoseValue = selectedItems.valueAt(0)
+ return dateUtil.dateAndTimeString(glucoseValue.timestamp) + "\n" + glucoseValue.valueToUnitsString(profileFunction.getUnits())
+ }
+ return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size())
+ }
+
+ private fun removeSelected(selectedItems: SparseArray) {
+ activity?.let { activity ->
+ OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(selectedItems), Runnable {
+ selectedItems.forEach { _, glucoseValue ->
+ val source = when ((activePlugin.activeBgSource as PluginBase).pluginDescription.pluginName) {
+ R.string.dexcom_app_patched -> Sources.Dexcom
+ R.string.eversense -> Sources.Eversense
+ R.string.Glimp -> Sources.Glimp
+ R.string.MM640g -> Sources.MM640g
+ R.string.nsclientbg -> Sources.NSClientSource
+ R.string.poctech -> Sources.PocTech
+ R.string.tomato -> Sources.Tomato
+ R.string.glunovo -> Sources.Glunovo
+ R.string.xdrip -> Sources.Xdrip
+ R.string.aidex -> Sources.Aidex
+ else -> Sources.Unknown
}
+ uel.log(
+ Action.BG_REMOVED, source,
+ ValueWithUnit.Timestamp(glucoseValue.timestamp)
+ )
+ repository.runTransactionForResult(InvalidateGlucoseValueTransaction(glucoseValue.id))
+ .doOnError { aapsLogger.error(LTag.DATABASE, "Error while invalidating BG value", it) }
+ .blockingGet()
+ .also { result -> result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated bg $it") } }
}
- }
+ actionHelper.finish()
+ })
}
}
}
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/DexcomPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/source/DexcomPlugin.kt
index 5bef08a3f8..7df9fe9729 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/source/DexcomPlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/DexcomPlugin.kt
@@ -25,7 +25,7 @@ import info.nightscout.androidaps.receivers.DataWorker
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.XDripBroadcast
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
@@ -149,11 +149,15 @@ class DexcomPlugin @Inject constructor(
sourceSensor = sourceSensor
)
}
- val sensorStartTime = if (sp.getBoolean(R.string.key_dexcom_lognssensorchange, false) && bundle.containsKey("sensorInsertionTime")) {
+ var sensorStartTime = if (sp.getBoolean(R.string.key_dexcom_lognssensorchange, false) && bundle.containsKey("sensorInsertionTime")) {
bundle.getLong("sensorInsertionTime", 0) * 1000
} else {
null
}
+ // check start time validity
+ sensorStartTime?.let {
+ if (abs(it - now) > T.months(1).msecs() || it > now) sensorStartTime = null
+ }
repository.runTransactionForResult(CgmSourceTransaction(glucoseValues, calibrations, sensorStartTime))
.doOnError {
aapsLogger.error(LTag.DATABASE, "Error while saving values from Dexcom App", it)
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/EversensePlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/source/EversensePlugin.kt
index 8ba0d66e30..05715e8e12 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/source/EversensePlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/EversensePlugin.kt
@@ -20,7 +20,7 @@ import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.receivers.DataWorker
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.XDripBroadcast
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import java.util.*
import javax.inject.Inject
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/GlimpPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/source/GlimpPlugin.kt
index fb938e764a..8efc9651df 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/source/GlimpPlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/GlimpPlugin.kt
@@ -17,7 +17,7 @@ import info.nightscout.androidaps.interfaces.PluginType
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.utils.XDripBroadcast
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import javax.inject.Inject
import javax.inject.Singleton
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/GlunovoPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/source/GlunovoPlugin.kt
index c65691dfbe..239a317781 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/source/GlunovoPlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/GlunovoPlugin.kt
@@ -24,9 +24,9 @@ import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.XDripBroadcast
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
-import io.reactivex.disposables.CompositeDisposable
+import io.reactivex.rxjava3.disposables.CompositeDisposable
import javax.inject.Inject
import javax.inject.Singleton
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/MM640gPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/source/MM640gPlugin.kt
index db01f9d7c0..dd70a31e99 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/source/MM640gPlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/MM640gPlugin.kt
@@ -18,7 +18,7 @@ import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.receivers.DataWorker
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.XDripBroadcast
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONArray
import org.json.JSONException
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/NSClientSourcePlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/source/NSClientSourcePlugin.kt
index a640df98fa..6ada149c6d 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/source/NSClientSourcePlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/NSClientSourcePlugin.kt
@@ -25,7 +25,7 @@ import info.nightscout.androidaps.receivers.DataWorker
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.XDripBroadcast
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONObject
import javax.inject.Inject
@@ -115,11 +115,11 @@ class NSClientSourcePlugin @Inject constructor(
@Suppress("SpellCheckingInspection")
override fun doWork(): Result {
var ret = Result.success()
-
- if (!nsClientSourcePlugin.isEnabled() && !sp.getBoolean(R.string.key_ns_receive_cgm, false)) return Result.success(workDataOf("Result" to "Sync not enabled"))
-
val sgvs = dataWorker.pickupJSONArray(inputData.getLong(DataWorker.STORE_KEY, -1))
?: return Result.failure(workDataOf("Error" to "missing input data"))
+ xDripBroadcast.sendSgvs(sgvs)
+ if (!nsClientSourcePlugin.isEnabled() && !sp.getBoolean(R.string.key_ns_receive_cgm, false))
+ return Result.success(workDataOf("Result" to "Sync not enabled"))
try {
var latestDateInReceivedData: Long = 0
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/PoctechPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/source/PoctechPlugin.kt
index d39a1d89a7..072d1953bc 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/source/PoctechPlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/PoctechPlugin.kt
@@ -18,7 +18,7 @@ import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.utils.JsonHelper.safeGetString
import info.nightscout.androidaps.utils.XDripBroadcast
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONArray
import org.json.JSONException
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/RandomBgPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/source/RandomBgPlugin.kt
index 82d353a095..c2115d53e3 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/source/RandomBgPlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/RandomBgPlugin.kt
@@ -8,18 +8,16 @@ import info.nightscout.androidaps.R
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.entities.GlucoseValue
import info.nightscout.androidaps.database.transactions.CgmSourceTransaction
-import info.nightscout.androidaps.interfaces.BgSource
-import info.nightscout.androidaps.interfaces.PluginBase
-import info.nightscout.androidaps.interfaces.PluginDescription
-import info.nightscout.androidaps.interfaces.PluginType
+import info.nightscout.androidaps.interfaces.*
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.XDripBroadcast
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin
+import info.nightscout.androidaps.utils.extensions.isRunningTest
import info.nightscout.shared.sharedPreferences.SP
-import io.reactivex.disposables.CompositeDisposable
-import io.reactivex.rxkotlin.plusAssign
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.kotlin.plusAssign
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
@@ -33,7 +31,9 @@ class RandomBgPlugin @Inject constructor(
aapsLogger: AAPSLogger,
private val sp: SP,
private val repository: AppRepository,
- private val xDripBroadcast: XDripBroadcast
+ private val xDripBroadcast: XDripBroadcast,
+ private val virtualPumpPlugin: VirtualPumpPlugin,
+ private val buildHelper: BuildHelper
) : PluginBase(
PluginDescription()
.mainType(PluginType.BGSOURCE)
@@ -89,8 +89,8 @@ class RandomBgPlugin @Inject constructor(
}
override fun specialEnableCondition(): Boolean {
-// return isRunningTest() || virtualPumpPlugin.isEnabled() && buildHelper.isEngineeringMode()
- return true
+ return isRunningTest() || virtualPumpPlugin.isEnabled() && buildHelper.isEngineeringMode()
+// return true
}
private fun handleNewData() {
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/TomatoPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/source/TomatoPlugin.kt
index 7558878a5a..da48d8f46a 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/source/TomatoPlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/TomatoPlugin.kt
@@ -16,7 +16,7 @@ import info.nightscout.androidaps.interfaces.PluginType
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.utils.XDripBroadcast
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import javax.inject.Inject
import javax.inject.Singleton
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/XdripPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/source/XdripPlugin.kt
index c9ce4a2123..906e84cf36 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/source/XdripPlugin.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/XdripPlugin.kt
@@ -16,8 +16,8 @@ import info.nightscout.androidaps.interfaces.PluginType
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.receivers.DataWorker
-import info.nightscout.androidaps.services.Intents
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.receivers.Intents
+import info.nightscout.androidaps.interfaces.ResourceHelper
import javax.inject.Inject
import javax.inject.Singleton
diff --git a/app/src/main/java/info/nightscout/androidaps/queue/CommandQueueImplementation.kt b/app/src/main/java/info/nightscout/androidaps/queue/CommandQueueImplementation.kt
index 5315d15713..f8ba452203 100644
--- a/app/src/main/java/info/nightscout/androidaps/queue/CommandQueueImplementation.kt
+++ b/app/src/main/java/info/nightscout/androidaps/queue/CommandQueueImplementation.kt
@@ -19,12 +19,10 @@ import info.nightscout.androidaps.database.entities.EffectiveProfileSwitch
import info.nightscout.androidaps.database.entities.ProfileSwitch
import info.nightscout.androidaps.database.interfaces.end
import info.nightscout.androidaps.dialogs.BolusProgressDialog
-import info.nightscout.androidaps.events.EventBolusRequested
+import info.nightscout.androidaps.events.EventMobileToWear
import info.nightscout.androidaps.events.EventProfileSwitchChanged
import info.nightscout.androidaps.extensions.getCustomizedName
import info.nightscout.androidaps.interfaces.*
-import info.nightscout.shared.logging.AAPSLogger
-import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.androidaps.plugins.general.overview.events.EventDismissBolusProgressIfRunning
@@ -37,14 +35,18 @@ import info.nightscout.androidaps.utils.AndroidPermission
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.HtmlHelper
-import info.nightscout.androidaps.utils.buildHelper.BuildHelper
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.BuildHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
-import io.reactivex.disposables.CompositeDisposable
-import io.reactivex.rxkotlin.plusAssign
-import io.reactivex.rxkotlin.subscribeBy
+import info.nightscout.shared.weardata.EventData
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.kotlin.plusAssign
+import io.reactivex.rxjava3.kotlin.subscribeBy
import java.util.*
+import java.util.concurrent.TimeUnit
import javax.inject.Inject
import javax.inject.Singleton
@@ -80,6 +82,7 @@ class CommandQueueImplementation @Inject constructor(
disposable += rxBus
.toObservable(EventProfileSwitchChanged::class.java)
.observeOn(aapsSchedulers.io)
+ .throttleLatest(3L, TimeUnit.SECONDS)
.subscribe({
if (config.NSCLIENT) { // Effective profileswitch should be synced over NS, do not create EffectiveProfileSwitch here
return@subscribe
@@ -297,7 +300,7 @@ class CommandQueueImplementation @Inject constructor(
// not when the Bolus command is starting. The command closes the dialog upon completion).
showBolusProgressDialog(detailedBolusInfo)
// Notify Wear about upcoming bolus
- rxBus.send(EventBolusRequested(detailedBolusInfo.insulin))
+ rxBus.send(EventMobileToWear(EventData.BolusProgress(percent = 0, status = rh.gs(R.string.bolusrequested, detailedBolusInfo.insulin))))
}
}
notifyAboutNewCommand()
@@ -320,13 +323,13 @@ class CommandQueueImplementation @Inject constructor(
}
@Synchronized
- override fun cancelAllBoluses() {
+ override fun cancelAllBoluses(id: Long) {
if (!isRunning(CommandType.BOLUS)) {
- rxBus.send(EventDismissBolusProgressIfRunning(PumpEnactResult(injector).success(true).enacted(false), null))
+ rxBus.send(EventDismissBolusProgressIfRunning(PumpEnactResult(injector).success(true).enacted(false), id))
}
removeAll(CommandType.BOLUS)
removeAll(CommandType.SMB_BOLUS)
- Thread { activePlugin.activePump.stopBolusDelivering() }.run()
+ Thread { activePlugin.activePump.stopBolusDelivering() }.start()
}
// returns true if command is queued
@@ -597,12 +600,12 @@ class CommandQueueImplementation @Inject constructor(
if (detailedBolusInfo.context != null) {
val bolusProgressDialog = BolusProgressDialog()
bolusProgressDialog.setInsulin(detailedBolusInfo.insulin)
- bolusProgressDialog.setTimestamp(detailedBolusInfo.timestamp)
+ bolusProgressDialog.setId(detailedBolusInfo.id)
bolusProgressDialog.show((detailedBolusInfo.context as AppCompatActivity).supportFragmentManager, "BolusProgress")
} else {
val i = Intent()
i.putExtra("insulin", detailedBolusInfo.insulin)
- i.putExtra("timestamp", detailedBolusInfo.timestamp)
+ i.putExtra("id", detailedBolusInfo.id)
i.setClass(context, BolusProgressHelperActivity::class.java)
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(i)
diff --git a/app/src/main/java/info/nightscout/androidaps/queue/QueueThread.kt b/app/src/main/java/info/nightscout/androidaps/queue/QueueThread.kt
index 5f99519df1..ee3d8e188c 100644
--- a/app/src/main/java/info/nightscout/androidaps/queue/QueueThread.kt
+++ b/app/src/main/java/info/nightscout/androidaps/queue/QueueThread.kt
@@ -16,7 +16,7 @@ import info.nightscout.androidaps.plugins.general.overview.events.EventDismissBo
import info.nightscout.androidaps.queue.events.EventQueueChanged
import info.nightscout.androidaps.utils.AndroidPermission
import info.nightscout.androidaps.utils.T
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
@@ -52,7 +52,7 @@ class QueueThread internal constructor(
val secondsElapsed = (System.currentTimeMillis() - connectionStartTime) / 1000
val pump = activePlugin.activePump
// Manifest.permission.BLUETOOTH_CONNECT
- if (config.PUMPDRIVERS && Build.VERSION.SDK_INT >= /*Build.VERSION_CODES.S*/31)
+ if (config.PUMPDRIVERS && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
if (androidPermission.permissionNotGranted(context, "android.permission.BLUETOOTH_CONNECT")) {
aapsLogger.debug(LTag.PUMPQUEUE, "no permission")
rxBus.send(EventPumpStatusChanged(EventPumpStatusChanged.Status.CONNECTING))
diff --git a/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandBolus.kt b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandBolus.kt
index d00cfb9857..03c852754d 100644
--- a/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandBolus.kt
+++ b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandBolus.kt
@@ -26,7 +26,7 @@ class CommandBolus(
val r = activePlugin.activePump.deliverTreatment(detailedBolusInfo)
if (r.success) carbsRunnable.run()
BolusProgressDialog.bolusEnded = true
- rxBus.send(EventDismissBolusProgressIfRunning(r, detailedBolusInfo.timestamp))
+ rxBus.send(EventDismissBolusProgressIfRunning(r, detailedBolusInfo.id))
aapsLogger.debug(LTag.PUMPQUEUE, "Result success: ${r.success} enacted: ${r.enacted}")
callback?.result(r)?.run()
}
diff --git a/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandCustomCommand.kt b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandCustomCommand.kt
index 0fda49b6c8..59df7544d8 100644
--- a/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandCustomCommand.kt
+++ b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandCustomCommand.kt
@@ -15,9 +15,10 @@ class CommandCustomCommand(
@Inject lateinit var activePlugin: ActivePlugin
override fun execute() {
- val result = activePlugin.activePump.executeCustomCommand(customCommand)
- aapsLogger.debug(LTag.PUMPQUEUE, "Result success: ${result?.success} enacted: ${result?.enacted}")
- callback?.result(result)?.run()
+ activePlugin.activePump.executeCustomCommand(customCommand)?.let {
+ aapsLogger.debug(LTag.PUMPQUEUE, "Result success: ${it.success} enacted: ${it.enacted}")
+ callback?.result(it)?.run()
+ }
}
override fun status(): String = customCommand.statusDescription
diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/DataReceiver.kt b/app/src/main/java/info/nightscout/androidaps/receivers/DataReceiver.kt
index 6be6a5ad2e..446ccf39ee 100644
--- a/app/src/main/java/info/nightscout/androidaps/receivers/DataReceiver.kt
+++ b/app/src/main/java/info/nightscout/androidaps/receivers/DataReceiver.kt
@@ -6,16 +6,14 @@ import android.provider.Telephony
import androidx.work.Data
import androidx.work.OneTimeWorkRequest
import dagger.android.DaggerBroadcastReceiver
+import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin
+import info.nightscout.androidaps.plugins.source.*
+import info.nightscout.androidaps.utils.extensions.copyDouble
+import info.nightscout.androidaps.utils.extensions.copyLong
+import info.nightscout.androidaps.utils.extensions.copyString
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.BundleLogger
import info.nightscout.shared.logging.LTag
-import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin
-import info.nightscout.androidaps.plugins.source.*
-import info.nightscout.androidaps.services.Intents
-import info.nightscout.androidaps.utils.extensions.copyDouble
-import info.nightscout.androidaps.utils.extensions.copyInt
-import info.nightscout.androidaps.utils.extensions.copyLong
-import info.nightscout.androidaps.utils.extensions.copyString
import javax.inject.Inject
open class DataReceiver : DaggerBroadcastReceiver() {
@@ -28,11 +26,10 @@ open class DataReceiver : DaggerBroadcastReceiver() {
val bundle = intent.extras ?: return
aapsLogger.debug(LTag.DATABASE, "onReceive ${intent.action} ${BundleLogger.log(bundle)}")
-
when (intent.action) {
Intents.ACTION_NEW_BG_ESTIMATE ->
OneTimeWorkRequest.Builder(XdripPlugin.XdripWorker::class.java)
- .setInputData(dataWorker.storeInputData(bundle, intent)).build()
+ .setInputData(dataWorker.storeInputData(bundle, intent.action)).build()
Intents.POCTECH_BG ->
OneTimeWorkRequest.Builder(PoctechPlugin.PoctechWorker::class.java)
.setInputData(Data.Builder().also {
@@ -60,15 +57,18 @@ open class DataReceiver : DaggerBroadcastReceiver() {
}.build()).build()
Telephony.Sms.Intents.SMS_RECEIVED_ACTION ->
OneTimeWorkRequest.Builder(SmsCommunicatorPlugin.SmsCommunicatorWorker::class.java)
- .setInputData(dataWorker.storeInputData(bundle, intent)).build()
+ .setInputData(dataWorker.storeInputData(bundle, intent.action)).build()
Intents.EVERSENSE_BG ->
OneTimeWorkRequest.Builder(EversensePlugin.EversenseWorker::class.java)
- .setInputData(dataWorker.storeInputData(bundle, intent)).build()
+ .setInputData(dataWorker.storeInputData(bundle, intent.action)).build()
Intents.DEXCOM_BG ->
OneTimeWorkRequest.Builder(DexcomPlugin.DexcomWorker::class.java)
- .setInputData(dataWorker.storeInputData(bundle, intent)).build()
+ .setInputData(dataWorker.storeInputData(bundle, intent.action)).build()
+ Intents.AIDEX_NEW_BG_ESTIMATE ->
+ OneTimeWorkRequest.Builder(AidexPlugin.AidexWorker::class.java)
+ .setInputData(dataWorker.storeInputData(bundle, intent.action)).build()
else -> null
}?.let { request -> dataWorker.enqueue(request) }
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/DataWorker.kt b/app/src/main/java/info/nightscout/androidaps/receivers/DataWorker.kt
index fed3db6598..77914ddeb1 100644
--- a/app/src/main/java/info/nightscout/androidaps/receivers/DataWorker.kt
+++ b/app/src/main/java/info/nightscout/androidaps/receivers/DataWorker.kt
@@ -56,10 +56,10 @@ class DataWorker @Inject constructor(
return value as JSONObject?
}
- fun storeInputData(value: Any, intent: Intent? = null) =
+ fun storeInputData(value: Any, action: String? = null) =
Data.Builder()
.putLong(STORE_KEY, store(value))
- .putString(ACTION_KEY, intent?.action).build()
+ .putString(ACTION_KEY, action).build()
fun enqueue(request: OneTimeWorkRequest) {
WorkManager.getInstance(context)
diff --git a/app/src/main/java/info/nightscout/androidaps/services/Intents.kt b/app/src/main/java/info/nightscout/androidaps/receivers/Intents.kt
similarity index 75%
rename from app/src/main/java/info/nightscout/androidaps/services/Intents.kt
rename to app/src/main/java/info/nightscout/androidaps/receivers/Intents.kt
index bf3a8d3695..8d09939b11 100644
--- a/app/src/main/java/info/nightscout/androidaps/services/Intents.kt
+++ b/app/src/main/java/info/nightscout/androidaps/receivers/Intents.kt
@@ -1,4 +1,4 @@
-package info.nightscout.androidaps.services
+package info.nightscout.androidaps.receivers
@Suppress("unused")
interface Intents {
@@ -31,6 +31,15 @@ interface Intents {
const val POCTECH_BG = "com.china.poctech.data"
const val TOMATO_BG = "com.fanqies.tomatofn.BgEstimate"
+ // Aidex -> AAPS
+ var AIDEX_NEW_BG_ESTIMATE = "com.microtechmd.cgms.aidex.action.BgEstimate"
+ var AIDEX_BG_TYPE = "com.microtechmd.cgms.aidex.BgType"
+ var AIDEX_BG_VALUE = "com.microtechmd.cgms.aidex.BgValue"
+ var AIDEX_BG_SLOPE_NAME = "com.microtechmd.cgms.aidex.BgSlopeName"
+ var AIDEX_TIMESTAMP = "com.microtechmd.cgms.aidex.Time" // epoch in ms
+ var AIDEX_TRANSMITTER_SN = "com.microtechmd.cgms.aidex.TransmitterSerialNumber"
+ var AIDEX_SENSOR_ID = "com.microtechmd.cgms.aidex.SensorId"
+
// Broadcast status
const val AAPS_BROADCAST = "info.nightscout.androidaps.status"
}
diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveReceiver.kt b/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveReceiver.kt
deleted file mode 100644
index e20ebcb414..0000000000
--- a/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveReceiver.kt
+++ /dev/null
@@ -1,198 +0,0 @@
-package info.nightscout.androidaps.receivers
-
-import android.app.AlarmManager
-import android.app.PendingIntent
-import android.app.PendingIntent.CanceledException
-import android.app.PendingIntent.FLAG_IMMUTABLE
-import android.content.Context
-import android.content.Intent
-import android.os.SystemClock
-import androidx.work.*
-import com.google.common.util.concurrent.ListenableFuture
-import dagger.android.DaggerBroadcastReceiver
-import dagger.android.HasAndroidInjector
-import info.nightscout.androidaps.BuildConfig
-import info.nightscout.androidaps.R
-import info.nightscout.androidaps.data.ProfileSealed
-import info.nightscout.androidaps.database.AppRepository
-import info.nightscout.androidaps.events.EventProfileSwitchChanged
-import info.nightscout.androidaps.extensions.buildDeviceStatus
-import info.nightscout.androidaps.interfaces.*
-import info.nightscout.shared.logging.AAPSLogger
-import info.nightscout.shared.logging.LTag
-import info.nightscout.androidaps.plugins.bus.RxBus
-import info.nightscout.androidaps.plugins.configBuilder.RunningConfiguration
-import info.nightscout.androidaps.plugins.general.maintenance.MaintenancePlugin
-import info.nightscout.androidaps.queue.commands.Command
-import info.nightscout.androidaps.utils.DateUtil
-import info.nightscout.androidaps.utils.FabricPrivacy
-import info.nightscout.androidaps.utils.LocalAlertUtils
-import info.nightscout.androidaps.utils.T
-import info.nightscout.androidaps.utils.resources.ResourceHelper
-import javax.inject.Inject
-import kotlin.math.abs
-
-class KeepAliveReceiver : DaggerBroadcastReceiver() {
-
- @Inject lateinit var aapsLogger: AAPSLogger
-
- companion object {
-
- private val KEEP_ALIVE_MILLISECONDS = T.mins(5).msecs()
- }
-
- override fun onReceive(context: Context, intent: Intent) {
- super.onReceive(context, intent)
- aapsLogger.debug(LTag.CORE, "KeepAlive received")
-
- WorkManager.getInstance(context)
- .enqueue(OneTimeWorkRequest.Builder(KeepAliveWorker::class.java).build())
- }
-
- class KeepAliveWorker(
- private val context: Context,
- params: WorkerParameters
- ) : Worker(context, params) {
-
- @Inject lateinit var aapsLogger: AAPSLogger
- @Inject lateinit var localAlertUtils: LocalAlertUtils
- @Inject lateinit var repository: AppRepository
- @Inject lateinit var config: Config
- @Inject lateinit var iobCobCalculator: IobCobCalculator
- @Inject lateinit var loop: Loop
- @Inject lateinit var dateUtil: DateUtil
- @Inject lateinit var activePlugin: ActivePlugin
- @Inject lateinit var profileFunction: ProfileFunction
- @Inject lateinit var runningConfiguration: RunningConfiguration
- @Inject lateinit var receiverStatusStore: ReceiverStatusStore
- @Inject lateinit var rxBus: RxBus
- @Inject lateinit var commandQueue: CommandQueue
- @Inject lateinit var fabricPrivacy: FabricPrivacy
- @Inject lateinit var maintenancePlugin: MaintenancePlugin
- @Inject lateinit var rh: ResourceHelper
-
- init {
- (context.applicationContext as HasAndroidInjector).androidInjector().inject(this)
- }
-
- companion object {
-
- private val STATUS_UPDATE_FREQUENCY = T.mins(15).msecs()
- private const val IOB_UPDATE_FREQUENCY_IN_MINUTES = 5L
-
- private var lastReadStatus: Long = 0
- private var lastRun: Long = 0
- private var lastIobUpload: Long = 0
-
- }
-
- override fun doWork(): Result {
- localAlertUtils.shortenSnoozeInterval()
- localAlertUtils.checkStaleBGAlert()
- checkPump()
- checkAPS()
- maintenancePlugin.deleteLogs(30)
- workerDbStatus()
-
- return Result.success()
- }
-
- // When Worker DB grows too much, work operations become slow
- // Library is cleaning DB every 7 days which may not be sufficient for NSClient full sync
- private fun workerDbStatus() {
- val workQuery = WorkQuery.Builder
- .fromStates(listOf(WorkInfo.State.FAILED, WorkInfo.State.SUCCEEDED))
- .build()
-
- val workInfo: ListenableFuture> = WorkManager.getInstance(context).getWorkInfos(workQuery)
- aapsLogger.debug(LTag.CORE, "WorkManager size is ${workInfo.get().size}")
- if (workInfo.get().size > 1000) {
- WorkManager.getInstance(context).pruneWork()
- aapsLogger.debug(LTag.CORE, "WorkManager pruning ....")
- }
- }
-
- // Usually deviceStatus is uploaded through LoopPlugin after every loop cycle.
- // if there is no BG available, we have to upload anyway to have correct
- // IOB displayed in NS
- private fun checkAPS() {
- var shouldUploadStatus = false
- if (config.NSCLIENT) return
- if (config.PUMPCONTROL) shouldUploadStatus = true
- else if (!(loop as PluginBase).isEnabled() || iobCobCalculator.ads.actualBg() == null)
- shouldUploadStatus = true
- else if (dateUtil.isOlderThan(activePlugin.activeAPS.lastAPSRun, 5)) shouldUploadStatus = true
- if (dateUtil.isOlderThan(lastIobUpload, IOB_UPDATE_FREQUENCY_IN_MINUTES) && shouldUploadStatus) {
- lastIobUpload = dateUtil.now()
- buildDeviceStatus(dateUtil, loop, iobCobCalculator, profileFunction,
- activePlugin.activePump, receiverStatusStore, runningConfiguration,
- BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION)?.also {
- repository.insert(it)
- }
- }
- }
-
- private fun checkPump() {
- val pump = activePlugin.activePump
- val ps = profileFunction.getRequestedProfile() ?: return
- val requestedProfile = ProfileSealed.PS(ps)
- val runningProfile = profileFunction.getProfile()
- val lastConnection = pump.lastDataTime()
- val isStatusOutdated = lastConnection + STATUS_UPDATE_FREQUENCY < System.currentTimeMillis()
- val isBasalOutdated = abs(requestedProfile.getBasal() - pump.baseBasalRate) > pump.pumpDescription.basalStep
- aapsLogger.debug(LTag.CORE, "Last connection: " + dateUtil.dateAndTimeString(lastConnection))
- // sometimes keep alive broadcast stops
- // as as workaround test if readStatus was requested before an alarm is generated
- if (lastReadStatus != 0L && lastReadStatus > System.currentTimeMillis() - T.mins(5).msecs()) {
- localAlertUtils.checkPumpUnreachableAlarm(lastConnection, isStatusOutdated, loop.isDisconnected)
- }
- if (loop.isDisconnected) {
- // do nothing if pump is disconnected
- } else if (runningProfile == null || ((!pump.isThisProfileSet(requestedProfile) || !requestedProfile.isEqual(runningProfile)) && !commandQueue.isRunning(Command.CommandType.BASAL_PROFILE))) {
- rxBus.send(EventProfileSwitchChanged())
- } else if (isStatusOutdated && !pump.isBusy()) {
- lastReadStatus = System.currentTimeMillis()
- commandQueue.readStatus(rh.gs(R.string.keepalive_status_outdated), null)
- } else if (isBasalOutdated && !pump.isBusy()) {
- lastReadStatus = System.currentTimeMillis()
- commandQueue.readStatus(rh.gs(R.string.keepalive_basal_outdated), null)
- }
- if (lastRun != 0L && System.currentTimeMillis() - lastRun > T.mins(10).msecs()) {
- aapsLogger.error(LTag.CORE, "KeepAlive fail")
- fabricPrivacy.logCustom("KeepAliveFail")
- }
- lastRun = System.currentTimeMillis()
- }
- }
-
- class KeepAliveManager @Inject constructor(
- private val aapsLogger: AAPSLogger,
- private val localAlertUtils: LocalAlertUtils
- ) {
-
- //called by MainApp at first app start
- fun setAlarm(context: Context) {
- aapsLogger.debug(LTag.CORE, "KeepAlive scheduled")
- SystemClock.sleep(5000) // wait for app initialization
- localAlertUtils.shortenSnoozeInterval()
- localAlertUtils.preSnoozeAlarms()
- val am = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
- val i = Intent(context, KeepAliveReceiver::class.java)
- val pi = PendingIntent.getBroadcast(context, 0, i, FLAG_IMMUTABLE)
- try {
- pi.send()
- } catch (e: CanceledException) {
- }
- am.cancel(pi)
- am.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), KEEP_ALIVE_MILLISECONDS, pi)
- }
-
- fun cancelAlarm(context: Context) {
- aapsLogger.debug(LTag.CORE, "KeepAlive canceled")
- val intent = Intent(context, KeepAliveReceiver::class.java)
- val sender = PendingIntent.getBroadcast(context, 0, intent, FLAG_IMMUTABLE)
- val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
- alarmManager.cancel(sender)
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveWorker.kt b/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveWorker.kt
new file mode 100644
index 0000000000..ff64871b33
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveWorker.kt
@@ -0,0 +1,169 @@
+package info.nightscout.androidaps.receivers
+
+import android.content.Context
+import androidx.work.*
+import com.google.common.util.concurrent.ListenableFuture
+import dagger.android.HasAndroidInjector
+import info.nightscout.androidaps.BuildConfig
+import info.nightscout.androidaps.R
+import info.nightscout.androidaps.data.ProfileSealed
+import info.nightscout.androidaps.database.AppRepository
+import info.nightscout.androidaps.events.EventProfileSwitchChanged
+import info.nightscout.androidaps.extensions.buildDeviceStatus
+import info.nightscout.androidaps.interfaces.*
+import info.nightscout.androidaps.plugins.bus.RxBus
+import info.nightscout.androidaps.plugins.configBuilder.RunningConfiguration
+import info.nightscout.androidaps.plugins.general.maintenance.MaintenancePlugin
+import info.nightscout.androidaps.queue.commands.Command
+import info.nightscout.androidaps.utils.DateUtil
+import info.nightscout.androidaps.utils.FabricPrivacy
+import info.nightscout.androidaps.utils.LocalAlertUtils
+import info.nightscout.androidaps.utils.T
+import info.nightscout.androidaps.interfaces.ResourceHelper
+import info.nightscout.androidaps.widget.updateWidget
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.shared.logging.LTag
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+import kotlin.math.abs
+
+class KeepAliveWorker(
+ private val context: Context,
+ params: WorkerParameters
+) : Worker(context, params) {
+
+ @Inject lateinit var aapsLogger: AAPSLogger
+ @Inject lateinit var localAlertUtils: LocalAlertUtils
+ @Inject lateinit var repository: AppRepository
+ @Inject lateinit var config: Config
+ @Inject lateinit var iobCobCalculator: IobCobCalculator
+ @Inject lateinit var loop: Loop
+ @Inject lateinit var dateUtil: DateUtil
+ @Inject lateinit var activePlugin: ActivePlugin
+ @Inject lateinit var profileFunction: ProfileFunction
+ @Inject lateinit var runningConfiguration: RunningConfiguration
+ @Inject lateinit var receiverStatusStore: ReceiverStatusStore
+ @Inject lateinit var rxBus: RxBus
+ @Inject lateinit var commandQueue: CommandQueue
+ @Inject lateinit var fabricPrivacy: FabricPrivacy
+ @Inject lateinit var maintenancePlugin: MaintenancePlugin
+ @Inject lateinit var rh: ResourceHelper
+
+ init {
+ (context.applicationContext as HasAndroidInjector).androidInjector().inject(this)
+ }
+
+ companion object {
+
+ private val STATUS_UPDATE_FREQUENCY = T.mins(15).msecs()
+ private const val IOB_UPDATE_FREQUENCY_IN_MINUTES = 5L
+
+ private var lastReadStatus: Long = 0
+ private var lastRun: Long = 0
+ private var lastIobUpload: Long = 0
+
+ }
+
+ override fun doWork(): Result {
+ aapsLogger.debug(LTag.CORE, "KeepAlive received from: " + inputData.getString("schedule"))
+
+ // 15 min interval is WorkManager minimum so schedule another instances to have 5 min interval
+ if (inputData.getString("schedule") == "KeepAlive") {
+ WorkManager.getInstance(context).enqueueUniqueWork(
+ "KeepAlive_5",
+ ExistingWorkPolicy.REPLACE,
+ OneTimeWorkRequest.Builder(KeepAliveWorker::class.java)
+ .setInputData(Data.Builder().putString("schedule", "KeepAlive_5").build())
+ .setInitialDelay(5, TimeUnit.MINUTES)
+ .build()
+ )
+ WorkManager.getInstance(context).enqueueUniqueWork(
+ "KeepAlive_10",
+ ExistingWorkPolicy.REPLACE,
+ OneTimeWorkRequest.Builder(KeepAliveWorker::class.java)
+ .setInputData(Data.Builder().putString("schedule", "KeepAlive_10").build())
+ .setInitialDelay(10, TimeUnit.MINUTES)
+ .build()
+ )
+ }
+
+ updateWidget(context)
+ localAlertUtils.shortenSnoozeInterval()
+ localAlertUtils.checkStaleBGAlert()
+ checkPump()
+ checkAPS()
+ maintenancePlugin.deleteLogs(30)
+ workerDbStatus()
+
+ return Result.success()
+ }
+
+ // When Worker DB grows too much, work operations become slow
+ // Library is cleaning DB every 7 days which may not be sufficient for NSClient full sync
+ private fun workerDbStatus() {
+ val workQuery = WorkQuery.Builder
+ .fromStates(listOf(WorkInfo.State.FAILED, WorkInfo.State.SUCCEEDED))
+ .build()
+
+ val workInfo: ListenableFuture> = WorkManager.getInstance(context).getWorkInfos(workQuery)
+ aapsLogger.debug(LTag.CORE, "WorkManager size is ${workInfo.get().size}")
+ if (workInfo.get().size > 1000) {
+ WorkManager.getInstance(context).pruneWork()
+ aapsLogger.debug(LTag.CORE, "WorkManager pruning ....")
+ }
+ }
+
+ // Usually deviceStatus is uploaded through LoopPlugin after every loop cycle.
+ // if there is no BG available, we have to upload anyway to have correct
+ // IOB displayed in NS
+ private fun checkAPS() {
+ var shouldUploadStatus = false
+ if (config.NSCLIENT) return
+ if (config.PUMPCONTROL) shouldUploadStatus = true
+ else if (!(loop as PluginBase).isEnabled() || iobCobCalculator.ads.actualBg() == null)
+ shouldUploadStatus = true
+ else if (dateUtil.isOlderThan(activePlugin.activeAPS.lastAPSRun, 5)) shouldUploadStatus = true
+ if (dateUtil.isOlderThan(lastIobUpload, IOB_UPDATE_FREQUENCY_IN_MINUTES) && shouldUploadStatus) {
+ lastIobUpload = dateUtil.now()
+ buildDeviceStatus(
+ dateUtil, loop, iobCobCalculator, profileFunction,
+ activePlugin.activePump, receiverStatusStore, runningConfiguration,
+ BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION
+ )?.also {
+ repository.insert(it)
+ }
+ }
+ }
+
+ private fun checkPump() {
+ val pump = activePlugin.activePump
+ val ps = profileFunction.getRequestedProfile() ?: return
+ val requestedProfile = ProfileSealed.PS(ps)
+ val runningProfile = profileFunction.getProfile()
+ val lastConnection = pump.lastDataTime()
+ val isStatusOutdated = lastConnection + STATUS_UPDATE_FREQUENCY < System.currentTimeMillis()
+ val isBasalOutdated = abs(requestedProfile.getBasal() - pump.baseBasalRate) > pump.pumpDescription.basalStep
+ aapsLogger.debug(LTag.CORE, "Last connection: " + dateUtil.dateAndTimeString(lastConnection))
+ // sometimes keep alive broadcast stops
+ // as as workaround test if readStatus was requested before an alarm is generated
+ if (lastReadStatus != 0L && lastReadStatus > System.currentTimeMillis() - T.mins(5).msecs()) {
+ localAlertUtils.checkPumpUnreachableAlarm(lastConnection, isStatusOutdated, loop.isDisconnected)
+ }
+ if (loop.isDisconnected) {
+ // do nothing if pump is disconnected
+ } else if (runningProfile == null || ((!pump.isThisProfileSet(requestedProfile) || !requestedProfile.isEqual(runningProfile)) && !commandQueue.isRunning(Command.CommandType.BASAL_PROFILE))) {
+ rxBus.send(EventProfileSwitchChanged())
+ } else if (isStatusOutdated && !pump.isBusy()) {
+ lastReadStatus = System.currentTimeMillis()
+ commandQueue.readStatus(rh.gs(R.string.keepalive_status_outdated), null)
+ } else if (isBasalOutdated && !pump.isBusy()) {
+ lastReadStatus = System.currentTimeMillis()
+ commandQueue.readStatus(rh.gs(R.string.keepalive_basal_outdated), null)
+ }
+ if (lastRun != 0L && System.currentTimeMillis() - lastRun > T.mins(10).msecs()) {
+ aapsLogger.error(LTag.CORE, "KeepAlive fail")
+ fabricPrivacy.logCustom("KeepAliveFail")
+ }
+ lastRun = System.currentTimeMillis()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/setupwizard/SWDefinition.kt b/app/src/main/java/info/nightscout/androidaps/setupwizard/SWDefinition.kt
index aad9ac4d0b..8e26f60532 100644
--- a/app/src/main/java/info/nightscout/androidaps/setupwizard/SWDefinition.kt
+++ b/app/src/main/java/info/nightscout/androidaps/setupwizard/SWDefinition.kt
@@ -30,7 +30,7 @@ import info.nightscout.androidaps.utils.AndroidPermission
import info.nightscout.androidaps.utils.CryptoUtil
import info.nightscout.androidaps.utils.HardLimits
import info.nightscout.androidaps.utils.extensions.isRunningTest
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import java.util.*
import javax.inject.Inject
@@ -268,8 +268,8 @@ class SWDefinition @Inject constructor(
.add(SWButton(injector)
.text(R.string.doprofileswitch)
.action { ProfileSwitchDialog().show(activity.supportFragmentManager, "ProfileSwitchDialog") })
- .validator { profileFunction.getProfile() != null }
- .visibility { profileFunction.getProfile() == null }
+ .validator { profileFunction.getRequestedProfile() != null }
+ .visibility { profileFunction.getRequestedProfile() == null }
private val screenPump = SWScreen(injector, R.string.configbuilder_pump)
.skippable(false)
.add(SWPlugin(injector, this)
diff --git a/app/src/main/java/info/nightscout/androidaps/setupwizard/SWEventListener.kt b/app/src/main/java/info/nightscout/androidaps/setupwizard/SWEventListener.kt
index 2b4d667758..c1cfb270aa 100644
--- a/app/src/main/java/info/nightscout/androidaps/setupwizard/SWEventListener.kt
+++ b/app/src/main/java/info/nightscout/androidaps/setupwizard/SWEventListener.kt
@@ -8,7 +8,7 @@ import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.events.EventStatus
import info.nightscout.androidaps.setupwizard.elements.SWItem
import info.nightscout.androidaps.utils.rx.AapsSchedulers
-import io.reactivex.disposables.CompositeDisposable
+import io.reactivex.rxjava3.disposables.CompositeDisposable
import javax.inject.Inject
class SWEventListener constructor(
diff --git a/app/src/main/java/info/nightscout/androidaps/setupwizard/SWScreen.kt b/app/src/main/java/info/nightscout/androidaps/setupwizard/SWScreen.kt
index c840b0c38a..bb82acec9a 100644
--- a/app/src/main/java/info/nightscout/androidaps/setupwizard/SWScreen.kt
+++ b/app/src/main/java/info/nightscout/androidaps/setupwizard/SWScreen.kt
@@ -2,7 +2,7 @@ package info.nightscout.androidaps.setupwizard
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.setupwizard.elements.SWItem
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import java.util.*
import javax.inject.Inject
diff --git a/app/src/main/java/info/nightscout/androidaps/setupwizard/SetupWizardActivity.kt b/app/src/main/java/info/nightscout/androidaps/setupwizard/SetupWizardActivity.kt
index eef7197a7b..a5d157707f 100644
--- a/app/src/main/java/info/nightscout/androidaps/setupwizard/SetupWizardActivity.kt
+++ b/app/src/main/java/info/nightscout/androidaps/setupwizard/SetupWizardActivity.kt
@@ -24,7 +24,7 @@ import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.locale.LocaleHelper.update
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.shared.sharedPreferences.SP
-import io.reactivex.disposables.CompositeDisposable
+import io.reactivex.rxjava3.disposables.CompositeDisposable
import javax.inject.Inject
import kotlin.math.max
import kotlin.math.min
@@ -34,7 +34,6 @@ class SetupWizardActivity : NoSplashAppCompatActivity() {
@Inject lateinit var injector: HasAndroidInjector
@Inject lateinit var localProfilePlugin: LocalProfilePlugin
@Inject lateinit var swDefinition: SWDefinition
- @Inject lateinit var rxBus: RxBus
@Inject lateinit var sp: SP
@Inject lateinit var fabricPrivacy: FabricPrivacy
@Inject lateinit var aapsSchedulers: AapsSchedulers
diff --git a/app/src/main/java/info/nightscout/androidaps/setupwizard/elements/SWItem.kt b/app/src/main/java/info/nightscout/androidaps/setupwizard/elements/SWItem.kt
index dcd71f41f9..4db19af5d0 100644
--- a/app/src/main/java/info/nightscout/androidaps/setupwizard/elements/SWItem.kt
+++ b/app/src/main/java/info/nightscout/androidaps/setupwizard/elements/SWItem.kt
@@ -13,7 +13,7 @@ import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.setupwizard.events.EventSWUpdate
import info.nightscout.androidaps.utils.protection.PasswordCheck
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledFuture
diff --git a/app/src/main/java/info/nightscout/androidaps/skins/SkinButtonsOn.kt b/app/src/main/java/info/nightscout/androidaps/skins/SkinButtonsOn.kt
index 8c3595d069..aa04e0268e 100644
--- a/app/src/main/java/info/nightscout/androidaps/skins/SkinButtonsOn.kt
+++ b/app/src/main/java/info/nightscout/androidaps/skins/SkinButtonsOn.kt
@@ -11,4 +11,4 @@ class SkinButtonsOn @Inject constructor(private val config: Config) : SkinInterf
override val description: Int get() = R.string.buttonson_description
override val mainGraphHeight: Int get() = 200
override val secondaryGraphHeight: Int get() = 100
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/skins/SkinClassic.kt b/app/src/main/java/info/nightscout/androidaps/skins/SkinClassic.kt
index a6ae3b51b9..2513066d60 100644
--- a/app/src/main/java/info/nightscout/androidaps/skins/SkinClassic.kt
+++ b/app/src/main/java/info/nightscout/androidaps/skins/SkinClassic.kt
@@ -5,6 +5,7 @@ import android.view.View
import android.widget.LinearLayout
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.R
+import info.nightscout.androidaps.databinding.OverviewFragmentBinding
import javax.inject.Inject
import javax.inject.Singleton
@@ -15,8 +16,8 @@ class SkinClassic @Inject constructor(private val config: Config): SkinInterface
override val mainGraphHeight: Int get() = 200
override val secondaryGraphHeight: Int get() = 100
- override fun preProcessLandscapeOverviewLayout(dm: DisplayMetrics, view: View, isLandscape: Boolean, isTablet: Boolean, isSmallHeight: Boolean) {
- super.preProcessLandscapeOverviewLayout(dm, view, isLandscape, isTablet, isSmallHeight)
- if (!config.NSCLIENT && (isSmallHeight || isLandscape)) moveButtonsLayout(view as LinearLayout)
+ override fun preProcessLandscapeOverviewLayout(dm: DisplayMetrics, binding: OverviewFragmentBinding, isLandscape: Boolean, isTablet: Boolean, isSmallHeight: Boolean) {
+ super.preProcessLandscapeOverviewLayout(dm, binding, isLandscape, isTablet, isSmallHeight)
+ if (!config.NSCLIENT && (isSmallHeight || isLandscape)) moveButtonsLayout(binding.root)
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/skins/SkinInterface.kt b/app/src/main/java/info/nightscout/androidaps/skins/SkinInterface.kt
index 85ab6791ac..2e338955d5 100644
--- a/app/src/main/java/info/nightscout/androidaps/skins/SkinInterface.kt
+++ b/app/src/main/java/info/nightscout/androidaps/skins/SkinInterface.kt
@@ -4,11 +4,11 @@ import android.util.DisplayMetrics
import android.util.TypedValue.COMPLEX_UNIT_PX
import android.view.View
import android.widget.LinearLayout
-import android.widget.TextView
-import androidx.annotation.LayoutRes
import androidx.annotation.StringRes
import androidx.constraintlayout.widget.ConstraintLayout
import info.nightscout.androidaps.R
+import info.nightscout.androidaps.databinding.ActionsFragmentBinding
+import info.nightscout.androidaps.databinding.OverviewFragmentBinding
interface SkinInterface {
@@ -17,19 +17,20 @@ interface SkinInterface {
val mainGraphHeight: Int // in dp
val secondaryGraphHeight: Int // in dp
- @LayoutRes
- fun actionsLayout(isLandscape: Boolean, isSmallWidth: Boolean): Int = R.layout.actions_fragment
+ // no pre processing by default
+ fun preProcessLandscapeActionsLayout(dm: DisplayMetrics, binding: ActionsFragmentBinding) {
+ }
- fun preProcessLandscapeOverviewLayout(dm: DisplayMetrics, view: View, isLandscape: Boolean, isTablet: Boolean, isSmallHeight: Boolean) {
+ fun preProcessLandscapeOverviewLayout(dm: DisplayMetrics, binding: OverviewFragmentBinding, isLandscape: Boolean, isTablet: Boolean, isSmallHeight: Boolean) {
// pre-process landscape mode
val screenWidth = dm.widthPixels
val screenHeight = dm.heightPixels
val landscape = screenHeight < screenWidth
if (landscape) {
- val iobLayout = view.findViewById(R.id.iob_layout)
+ val iobLayout = binding.infoLayout.iobLayout
val iobLayoutParams = iobLayout.layoutParams as ConstraintLayout.LayoutParams
- val timeLayout = view.findViewById(R.id.time_layout)
+ val timeLayout = binding.infoLayout.timeLayout
iobLayoutParams.startToStart = ConstraintLayout.LayoutParams.UNSET
iobLayoutParams.startToEnd = timeLayout.id
iobLayoutParams.topToBottom = ConstraintLayout.LayoutParams.UNSET
@@ -37,43 +38,36 @@ interface SkinInterface {
val timeLayoutParams = timeLayout.layoutParams as ConstraintLayout.LayoutParams
timeLayoutParams.endToEnd = ConstraintLayout.LayoutParams.UNSET
timeLayoutParams.endToStart = iobLayout.id
- val cobLayoutParams = view.findViewById(R.id.cob_layout).layoutParams as ConstraintLayout.LayoutParams
+ val cobLayoutParams = binding.infoLayout.cobLayout.layoutParams as ConstraintLayout.LayoutParams
cobLayoutParams.topToTop = ConstraintLayout.LayoutParams.PARENT_ID
- val basalLayoutParams = view.findViewById(R.id.basal_layout).layoutParams as ConstraintLayout.LayoutParams
+ val basalLayoutParams = binding.infoLayout.basalLayout.layoutParams as ConstraintLayout.LayoutParams
basalLayoutParams.topToTop = ConstraintLayout.LayoutParams.PARENT_ID
- val extendedLayoutParams = view.findViewById(R.id.extended_layout).layoutParams as ConstraintLayout.LayoutParams
+ val extendedLayoutParams = binding.infoLayout.extendedLayout.layoutParams as ConstraintLayout.LayoutParams
extendedLayoutParams.topToTop = ConstraintLayout.LayoutParams.PARENT_ID
- val asLayoutParams = view.findViewById(R.id.as_layout).layoutParams as ConstraintLayout.LayoutParams
+ val asLayoutParams = binding.infoLayout.asLayout.layoutParams as ConstraintLayout.LayoutParams
asLayoutParams.topToTop = ConstraintLayout.LayoutParams.PARENT_ID
if (isTablet) {
- for (v in listOf(
- view.findViewById(R.id.bg),
- view.findViewById(R.id.time),
- view.findViewById(R.id.time_ago_short),
- view.findViewById(R.id.iob),
- view.findViewById(R.id.cob),
- view.findViewById(R.id.base_basal),
- view.findViewById(R.id.extended_bolus),
- view.findViewById(R.id.sensitivity)
- )) v?.setTextSize(COMPLEX_UNIT_PX, v.textSize * 1.5f)
- for (v in listOf(
- view.findViewById(R.id.pump),
- view.findViewById(R.id.openaps),
- view.findViewById(R.id.uploader),
- view.findViewById(R.id.cannula_age),
- view.findViewById(R.id.insulin_age),
- view.findViewById(R.id.reservoir_level),
- view.findViewById(R.id.sensor_age),
- view.findViewById(R.id.pb_age),
- view.findViewById(R.id.battery_level)
- )) v?.setTextSize(COMPLEX_UNIT_PX, v.textSize * 1.3f)
- timeLayout?.orientation = LinearLayout.HORIZONTAL
- view.findViewById(R.id.time_ago_short)?.setTextSize(COMPLEX_UNIT_PX, view.findViewById(R.id.time).textSize)
+ binding.infoLayout.apply {
+ val texts = listOf(bg, iob, cob, baseBasal, extendedBolus, sensitivity)
+ for (v in texts) v.setTextSize(COMPLEX_UNIT_PX, v.textSize * 1.5f)
+ val textsTime = listOf(time, timeAgoShort)
+ for (v in textsTime) v.setTextSize(COMPLEX_UNIT_PX, v.textSize * 2.25f)
+ }
+ binding.apply {
+ val texts = listOf(pump, openaps, uploader)
+ for (v in texts) v.setTextSize(COMPLEX_UNIT_PX, v.textSize * 1.3f)
+ }
+ binding.statusLightsLayout.apply {
+ val texts = listOf(cannulaAge, insulinAge, reservoirLevel, sensorAge, pbAge, batteryLevel)
+ for (v in texts) v.setTextSize(COMPLEX_UNIT_PX, v.textSize * 1.3f)
+ }
+ timeLayout.orientation = LinearLayout.HORIZONTAL
+ binding.infoLayout.timeAgoShort.setTextSize(COMPLEX_UNIT_PX, binding.infoLayout.time.textSize)
- view.findViewById(R.id.delta_large)?.visibility = View.VISIBLE
+ binding.infoLayout.deltaLarge.visibility = View.VISIBLE
} else {
- view.findViewById(R.id.delta_large)?.visibility = View.GONE
+ binding.infoLayout.deltaLarge.visibility = View.GONE
}
}
}
@@ -84,4 +78,5 @@ interface SkinInterface {
val innerLayout = root.findViewById(R.id.inner_layout)
innerLayout.addView(buttonsLayout)
}
-}
\ No newline at end of file
+
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/skins/SkinLargeDisplay.kt b/app/src/main/java/info/nightscout/androidaps/skins/SkinLargeDisplay.kt
index 751e0b3ad0..eb64d5514b 100644
--- a/app/src/main/java/info/nightscout/androidaps/skins/SkinLargeDisplay.kt
+++ b/app/src/main/java/info/nightscout/androidaps/skins/SkinLargeDisplay.kt
@@ -1,10 +1,9 @@
package info.nightscout.androidaps.skins
import android.util.DisplayMetrics
-import android.view.View
-import android.widget.LinearLayout
-import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.R
+import info.nightscout.androidaps.databinding.OverviewFragmentBinding
+import info.nightscout.androidaps.interfaces.Config
import javax.inject.Inject
import javax.inject.Singleton
@@ -15,8 +14,8 @@ class SkinLargeDisplay @Inject constructor(private val config: Config): SkinInte
override val mainGraphHeight: Int get() = 400
override val secondaryGraphHeight: Int get() = 150
- override fun preProcessLandscapeOverviewLayout(dm: DisplayMetrics, view: View, isLandscape: Boolean, isTablet: Boolean, isSmallHeight: Boolean) {
- super.preProcessLandscapeOverviewLayout(dm, view, isLandscape, isTablet, isSmallHeight)
- if (!config.NSCLIENT && (isSmallHeight || isLandscape)) moveButtonsLayout(view as LinearLayout)
+ override fun preProcessLandscapeOverviewLayout(dm: DisplayMetrics, binding: OverviewFragmentBinding, isLandscape: Boolean, isTablet: Boolean, isSmallHeight: Boolean) {
+ super.preProcessLandscapeOverviewLayout(dm, binding, isLandscape, isTablet, isSmallHeight)
+ if (!config.NSCLIENT && (isSmallHeight || isLandscape)) moveButtonsLayout(binding.root)
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/skins/SkinListPreference.kt b/app/src/main/java/info/nightscout/androidaps/skins/SkinListPreference.kt
index 977f19c268..51b546ce71 100644
--- a/app/src/main/java/info/nightscout/androidaps/skins/SkinListPreference.kt
+++ b/app/src/main/java/info/nightscout/androidaps/skins/SkinListPreference.kt
@@ -26,4 +26,4 @@ class SkinListPreference(context: Context, attrs: AttributeSet?)
entryValues = values.toTypedArray()
setEntries(entries.toTypedArray())
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/skins/SkinLowRes.kt b/app/src/main/java/info/nightscout/androidaps/skins/SkinLowRes.kt
index c4384db93b..440ac96733 100644
--- a/app/src/main/java/info/nightscout/androidaps/skins/SkinLowRes.kt
+++ b/app/src/main/java/info/nightscout/androidaps/skins/SkinLowRes.kt
@@ -1,10 +1,13 @@
package info.nightscout.androidaps.skins
import android.util.DisplayMetrics
-import android.view.View
-import android.widget.LinearLayout
+import android.view.View.GONE
+import android.view.ViewGroup
+import androidx.core.view.marginStart
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.R
+import info.nightscout.androidaps.databinding.ActionsFragmentBinding
+import info.nightscout.androidaps.databinding.OverviewFragmentBinding
import javax.inject.Inject
import javax.inject.Singleton
@@ -15,13 +18,63 @@ class SkinLowRes @Inject constructor(private val config: Config) : SkinInterface
override val mainGraphHeight: Int get() = 200
override val secondaryGraphHeight: Int get() = 100
- override fun actionsLayout(isLandscape: Boolean, isSmallWidth: Boolean): Int =
- when {
- isLandscape -> R.layout.actions_fragment
- else -> R.layout.actions_fragment_lowres
+ override fun preProcessLandscapeActionsLayout(dm: DisplayMetrics, binding: ActionsFragmentBinding) {
+ val screenWidth = dm.widthPixels
+ val screenHeight = dm.heightPixels
+ val isLandscape = screenHeight < screenWidth
+
+ if (!isLandscape) {
+ binding.status.apply {
+ sensorAgeLabel.visibility = GONE
+ sensorAgeLabel.visibility = GONE
+ sensorLevelLabel.visibility = GONE
+ insulinAgeLabel.visibility = GONE
+ insulinLevelLabel.visibility = GONE
+ cannulaAgeLabel.visibility = GONE
+ cannulaPlaceholder.visibility = GONE
+ pbAgeLabel.visibility = GONE
+ pbLevelLabel.visibility = GONE
+ }
+ }
+ }
+
+ override fun preProcessLandscapeOverviewLayout(dm: DisplayMetrics, binding: OverviewFragmentBinding, isLandscape: Boolean, isTablet: Boolean, isSmallHeight: Boolean) {
+ if (!config.NSCLIENT && isLandscape) moveButtonsLayout(binding.root)
+
+ binding.apply {
+ infoCard.elevation = 0F
+ infoCard.radius = 0F
+ val paramInfo = (infoCard.layoutParams as ViewGroup.MarginLayoutParams).apply {
+ setMargins(0,0,0,0)
+ }
+ infoCard.layoutParams = paramInfo
+
+ statusCard.elevation = 0F
+ statusCard.radius = 0F
+ statusCard.strokeWidth = 1
+ val paramStatus = (statusCard.layoutParams as ViewGroup.MarginLayoutParams).apply {
+ setMargins(0,0,0,0)
+ }
+ statusCard.layoutParams = paramStatus
+
+ nsclientCard.elevation = 0F
+ nsclientCard.radius = 0F
+ val paramNsClient = (nsclientCard.layoutParams as ViewGroup.MarginLayoutParams).apply {
+ setMargins(0,0,0,0)
+ }
+ nsclientCard.layoutParams = paramNsClient
+
+ graphCard.elevation = 0F
+ graphCard.radius = 0F
+ val paramGraph = (graphCard.layoutParams as ViewGroup.MarginLayoutParams).apply {
+ setMargins(0,0,0,0)
+ }
+ graphCard.layoutParams = paramGraph
+
+ activeProfile.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0)
+ tempTarget.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0)
}
- override fun preProcessLandscapeOverviewLayout(dm: DisplayMetrics, view: View, isLandscape: Boolean, isTablet: Boolean, isSmallHeight: Boolean) {
- if (!config.NSCLIENT && isLandscape) moveButtonsLayout(view as LinearLayout)
}
+
}
diff --git a/app/src/main/java/info/nightscout/androidaps/skins/SkinProvider.kt b/app/src/main/java/info/nightscout/androidaps/skins/SkinProvider.kt
index a119f102a8..b9d51adcda 100644
--- a/app/src/main/java/info/nightscout/androidaps/skins/SkinProvider.kt
+++ b/app/src/main/java/info/nightscout/androidaps/skins/SkinProvider.kt
@@ -19,4 +19,4 @@ class SkinProvider @Inject constructor(
val list: List
get() = allSkins.toImmutableMap().toList().sortedBy { it.first }.map { it.second }
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/utils/ActivityMonitor.kt b/app/src/main/java/info/nightscout/androidaps/utils/ActivityMonitor.kt
index 480bc30aa2..1b099697f5 100644
--- a/app/src/main/java/info/nightscout/androidaps/utils/ActivityMonitor.kt
+++ b/app/src/main/java/info/nightscout/androidaps/utils/ActivityMonitor.kt
@@ -2,14 +2,20 @@ package info.nightscout.androidaps.utils
import android.app.Activity
import android.app.Application
+import android.content.Context
+import android.graphics.Typeface
import android.os.Bundle
-import android.text.Spanned
+import android.view.Gravity
+import android.view.ViewGroup
+import android.widget.TableLayout
+import android.widget.TableRow
+import android.widget.TextView
import info.nightscout.androidaps.R
+import info.nightscout.androidaps.interfaces.ResourceHelper
+import info.nightscout.shared.SafeParse
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
-import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
-import info.nightscout.shared.SafeParse
import javax.inject.Inject
import javax.inject.Singleton
@@ -58,24 +64,47 @@ class ActivityMonitor @Inject constructor(
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
}
- private fun toText(): String {
- val keys: Map = sp.getAll()
- var result = ""
- for ((key, value) in keys)
- if (key.startsWith("Monitor") && key.endsWith("total")) {
- val v = if (value is Long) value else SafeParse.stringToLong(value as String)
- val activity = key.split("_")[1].replace("Activity", "")
- val duration = dateUtil.niceTimeScalar(v, rh)
- val start = sp.getLong(key.replace("total", "start"), 0)
- val days = T.msecs(dateUtil.now() - start).days()
- result += rh.gs(R.string.activitymonitorformat, activity, duration, days)
- }
- return result
- }
+ fun stats(context: Context): TableLayout =
+ TableLayout(context).also { layout ->
+ layout.layoutParams = TableLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)
+ layout.addView(
+ TextView(context).apply {
+ text = rh.gs(R.string.activitymonitor)
+ setTypeface(typeface, Typeface.BOLD)
+ gravity = Gravity.CENTER_HORIZONTAL
+ setTextAppearance(android.R.style.TextAppearance_Material_Medium)
+ })
+ layout.addView(
+ TableRow(context).also { row ->
+ val lp = TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT, TableRow.LayoutParams.WRAP_CONTENT).apply { weight = 1f }
+ row.layoutParams = TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.WRAP_CONTENT)
+ row.gravity = Gravity.CENTER_HORIZONTAL
+ row.addView(TextView(context).apply { layoutParams = lp.apply { column = 0 }; text = rh.gs(R.string.activity) })
+ row.addView(TextView(context).apply { layoutParams = lp.apply { column = 1 }; text = rh.gs(R.string.duration) })
+ row.addView(TextView(context).apply { layoutParams = lp.apply { column = 2 } })
+ }
+ )
- fun stats(): Spanned {
- return HtmlHelper.fromHtml("
" + rh.gs(R.string.activitymonitor) + ":
" + toText())
- }
+ val keys: Map = sp.getAll()
+ for ((key, value) in keys)
+ if (key.startsWith("Monitor") && key.endsWith("total")) {
+ val v = if (value is Long) value else SafeParse.stringToLong(value as String)
+ val activity = key.split("_")[1].replace("Activity", "")
+ val duration = dateUtil.niceTimeScalar(v, rh)
+ val start = sp.getLong(key.replace("total", "start"), 0)
+ val days = T.msecs(dateUtil.now() - start).days()
+ layout.addView(
+ TableRow(context).also { row ->
+ val lp = TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT, TableRow.LayoutParams.WRAP_CONTENT).apply { weight = 1f }
+ row.layoutParams = TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.WRAP_CONTENT)
+ row.gravity = Gravity.CENTER_HORIZONTAL
+ row.addView(TextView(context).apply { layoutParams = lp.apply { column = 0 }; text = activity })
+ row.addView(TextView(context).apply { layoutParams = lp.apply { column = 1 }; text = duration })
+ row.addView(TextView(context).apply { layoutParams = lp.apply { column = 2 }; text = rh.gs(R.string.in_days, days.toDouble()) })
+ }
+ )
+ }
+ }
fun reset() {
val keys: Map = sp.getAll()
diff --git a/app/src/main/java/info/nightscout/androidaps/utils/AndroidPermission.kt b/app/src/main/java/info/nightscout/androidaps/utils/AndroidPermission.kt
index e69156470b..6262e552bc 100644
--- a/app/src/main/java/info/nightscout/androidaps/utils/AndroidPermission.kt
+++ b/app/src/main/java/info/nightscout/androidaps/utils/AndroidPermission.kt
@@ -24,7 +24,7 @@ import info.nightscout.androidaps.plugins.general.overview.notifications.Notific
import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationWithAction
import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import javax.inject.Inject
import javax.inject.Singleton
@@ -97,7 +97,7 @@ class AndroidPermission @Inject constructor(
@Synchronized
fun notifyForBtConnectPermission(activity: FragmentActivity) {
- if (Build.VERSION.SDK_INT >= /*Build.VERSION_CODES.S*/31) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// Manifest.permission.BLUETOOTH_CONNECT
if (permissionNotGranted(activity, "android.permission.BLUETOOTH_CONNECT") || permissionNotGranted(activity, "android.permission.BLUETOOTH_SCAN")) {
val notification = NotificationWithAction(injector, Notification.PERMISSION_BT, rh.gs(R.string.needconnectpermission), Notification.URGENT)
@@ -113,7 +113,7 @@ class AndroidPermission @Inject constructor(
@Synchronized
fun notifyForBatteryOptimizationPermission(activity: FragmentActivity) {
if (permissionNotGranted(activity, Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)) {
- val notification = NotificationWithAction(injector, Notification.PERMISSION_BATTERY, String.format(rh.gs(R.string.needwhitelisting), rh.gs(R.string.app_name)), Notification.URGENT)
+ val notification = NotificationWithAction(injector, Notification.PERMISSION_BATTERY, rh.gs(R.string.needwhitelisting, rh.gs(R.string.app_name)), Notification.URGENT)
notification.action(R.string.request) { askForPermission(activity, arrayOf(Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)) }
rxBus.send(EventNewNotification(notification))
} else rxBus.send(EventDismissNotification(Notification.PERMISSION_BATTERY))
diff --git a/app/src/main/java/info/nightscout/androidaps/utils/BolusTimer.kt b/app/src/main/java/info/nightscout/androidaps/utils/BolusTimer.kt
index 3d8f75f2ab..a582bfa40c 100644
--- a/app/src/main/java/info/nightscout/androidaps/utils/BolusTimer.kt
+++ b/app/src/main/java/info/nightscout/androidaps/utils/BolusTimer.kt
@@ -11,7 +11,7 @@ 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.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import java.text.DecimalFormat
import javax.inject.Inject
import javax.inject.Singleton
diff --git a/app/src/main/java/info/nightscout/androidaps/utils/CarbTimer.kt b/app/src/main/java/info/nightscout/androidaps/utils/CarbTimer.kt
index 1db8839916..0652e312db 100644
--- a/app/src/main/java/info/nightscout/androidaps/utils/CarbTimer.kt
+++ b/app/src/main/java/info/nightscout/androidaps/utils/CarbTimer.kt
@@ -11,7 +11,7 @@ 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.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import java.text.DecimalFormat
import javax.inject.Inject
import javax.inject.Singleton
diff --git a/app/src/main/java/info/nightscout/androidaps/utils/LocalAlertUtils.kt b/app/src/main/java/info/nightscout/androidaps/utils/LocalAlertUtils.kt
index 2f1ea9e990..512f1b2d53 100644
--- a/app/src/main/java/info/nightscout/androidaps/utils/LocalAlertUtils.kt
+++ b/app/src/main/java/info/nightscout/androidaps/utils/LocalAlertUtils.kt
@@ -12,18 +12,18 @@ import info.nightscout.androidaps.database.transactions.InsertTherapyEventAnnoun
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.interfaces.ProfileFunction
-import info.nightscout.shared.logging.AAPSLogger
-import info.nightscout.shared.logging.LTag
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification
import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification
import info.nightscout.androidaps.plugins.general.overview.notifications.Notification
import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
-import io.reactivex.disposables.CompositeDisposable
-import io.reactivex.rxkotlin.plusAssign
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.kotlin.plusAssign
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.min
@@ -128,7 +128,7 @@ class LocalAlertUtils @Inject constructor(
rxBus.send(EventNewNotification(n))
uel.log(Action.CAREPORTAL, Sources.Aaps, rh.gs(R.string.missed_bg_readings), ValueWithUnit.TherapyEventType(TherapyEvent.Type.ANNOUNCEMENT))
if (sp.getBoolean(R.string.key_ns_create_announcements_from_errors, true)) {
- n.text?.let { disposable += repository.runTransaction(InsertTherapyEventAnnouncementTransaction(it)).subscribe() }
+ disposable += repository.runTransaction(InsertTherapyEventAnnouncementTransaction(n.text)).subscribe()
}
} else if (dateUtil.isOlderThan(bgReading.timestamp, 5).not()) {
rxBus.send(EventDismissNotification(Notification.BG_READINGS_MISSED))
diff --git a/app/src/main/java/info/nightscout/androidaps/utils/ProcessLifecycleListener.kt b/app/src/main/java/info/nightscout/androidaps/utils/ProcessLifecycleListener.kt
new file mode 100644
index 0000000000..e1bc3a719b
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/utils/ProcessLifecycleListener.kt
@@ -0,0 +1,13 @@
+package info.nightscout.androidaps.utils
+
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleOwner
+import info.nightscout.androidaps.utils.protection.ProtectionCheck
+import javax.inject.Inject
+
+class ProcessLifecycleListener @Inject constructor(private val protectionCheck: ProtectionCheck) : DefaultLifecycleObserver {
+
+ override fun onPause(owner: LifecycleOwner) {
+ protectionCheck.resetAuthorization()
+ }
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/utils/TrendCalculator.kt b/app/src/main/java/info/nightscout/androidaps/utils/TrendCalculator.kt
index a5f2dc2dcb..2d161808a6 100644
--- a/app/src/main/java/info/nightscout/androidaps/utils/TrendCalculator.kt
+++ b/app/src/main/java/info/nightscout/androidaps/utils/TrendCalculator.kt
@@ -1,20 +1,37 @@
package info.nightscout.androidaps.utils
+import info.nightscout.androidaps.R
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.entities.GlucoseValue
+import info.nightscout.androidaps.database.entities.GlucoseValue.TrendArrow.*
+import info.nightscout.androidaps.interfaces.ResourceHelper
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class TrendCalculator @Inject constructor(
- private val repository: AppRepository
+ private val repository: AppRepository,
+ private val rh: ResourceHelper
) {
fun getTrendArrow(glucoseValue: GlucoseValue?): GlucoseValue.TrendArrow =
when {
- glucoseValue?.trendArrow == null -> GlucoseValue.TrendArrow.NONE
- glucoseValue.trendArrow != GlucoseValue.TrendArrow.NONE -> glucoseValue.trendArrow
- else -> calculateDirection(glucoseValue)
+ glucoseValue?.trendArrow == null -> NONE
+ glucoseValue.trendArrow != NONE -> glucoseValue.trendArrow
+ else -> calculateDirection(glucoseValue)
+ }
+
+ fun getTrendDescription(glucoseValue: GlucoseValue?): String =
+ when (getTrendArrow(glucoseValue)) {
+ DOUBLE_DOWN -> rh.gs(R.string.a11y_arrow_double_down)
+ SINGLE_DOWN -> rh.gs(R.string.a11y_arrow_single_down)
+ FORTY_FIVE_DOWN -> rh.gs(R.string.a11y_arrow_forty_five_down)
+ FLAT -> rh.gs(R.string.a11y_arrow_flat)
+ FORTY_FIVE_UP -> rh.gs(R.string.a11y_arrow_forty_five_up)
+ SINGLE_UP -> rh.gs(R.string.a11y_arrow_single_up)
+ DOUBLE_UP -> rh.gs(R.string.a11y_arrow_double_up)
+ NONE -> rh.gs(R.string.a11y_arrow_none)
+ else -> rh.gs(R.string.a11y_arrow_unknown)
}
private fun calculateDirection(glucoseValue: GlucoseValue): GlucoseValue.TrendArrow {
@@ -23,7 +40,7 @@ class TrendCalculator @Inject constructor(
val readings = repository.compatGetBgReadingsDataFromTime(toTime - T.mins(10).msecs(), toTime, false).blockingGet()
if (readings.size < 2)
- return GlucoseValue.TrendArrow.NONE
+ return NONE
val current = readings[0]
val previous = readings[1]
@@ -35,14 +52,14 @@ class TrendCalculator @Inject constructor(
val slopeByMinute = slope * 60000
return when {
- slopeByMinute <= -3.5 -> GlucoseValue.TrendArrow.DOUBLE_DOWN
- slopeByMinute <= -2 -> GlucoseValue.TrendArrow.SINGLE_DOWN
- slopeByMinute <= -1 -> GlucoseValue.TrendArrow.FORTY_FIVE_DOWN
- slopeByMinute <= 1 -> GlucoseValue.TrendArrow.FLAT
- slopeByMinute <= 2 -> GlucoseValue.TrendArrow.FORTY_FIVE_UP
- slopeByMinute <= 3.5 -> GlucoseValue.TrendArrow.SINGLE_UP
- slopeByMinute <= 40 -> GlucoseValue.TrendArrow.DOUBLE_UP
- else -> GlucoseValue.TrendArrow.NONE
+ slopeByMinute <= -3.5 -> DOUBLE_DOWN
+ slopeByMinute <= -2 -> SINGLE_DOWN
+ slopeByMinute <= -1 -> FORTY_FIVE_DOWN
+ slopeByMinute <= 1 -> FLAT
+ slopeByMinute <= 2 -> FORTY_FIVE_UP
+ slopeByMinute <= 3.5 -> SINGLE_UP
+ slopeByMinute <= 40 -> DOUBLE_UP
+ else -> NONE
}
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/utils/XDripBroadcast.kt b/app/src/main/java/info/nightscout/androidaps/utils/XDripBroadcast.kt
index e862fdcb6b..0553ca7dde 100644
--- a/app/src/main/java/info/nightscout/androidaps/utils/XDripBroadcast.kt
+++ b/app/src/main/java/info/nightscout/androidaps/utils/XDripBroadcast.kt
@@ -10,8 +10,8 @@ import info.nightscout.androidaps.interfaces.GlucoseUnit
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
-import info.nightscout.androidaps.services.Intents
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.receivers.Intents
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONArray
import org.json.JSONException
diff --git a/app/src/main/java/info/nightscout/androidaps/utils/alertDialogs/PrefImportSummaryDialog.kt b/app/src/main/java/info/nightscout/androidaps/utils/alertDialogs/PrefImportSummaryDialog.kt
index b51e5960e6..60edb6b6ad 100644
--- a/app/src/main/java/info/nightscout/androidaps/utils/alertDialogs/PrefImportSummaryDialog.kt
+++ b/app/src/main/java/info/nightscout/androidaps/utils/alertDialogs/PrefImportSummaryDialog.kt
@@ -16,6 +16,7 @@ import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.annotation.StyleRes
import androidx.appcompat.view.ContextThemeWrapper
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
import info.nightscout.androidaps.R
import info.nightscout.androidaps.extensions.runOnUiThread
import info.nightscout.androidaps.plugins.general.maintenance.formats.Prefs
@@ -28,7 +29,7 @@ object PrefImportSummaryDialog {
@SuppressLint("InflateParams")
fun showSummary(context: Context, importOk: Boolean, importPossible: Boolean, prefs: Prefs, ok: (() -> Unit)?, cancel: (() -> Unit)? = null) {
- @StyleRes val theme: Int = if (importOk) R.style.AppTheme else {
+ @StyleRes val theme: Int = if (importOk) R.style.DialogTheme else {
if (importPossible) R.style.AppThemeWarningDialog else R.style.AppThemeErrorDialog
}
@@ -49,7 +50,6 @@ object PrefImportSummaryDialog {
var idx = 0
val details = LinkedList()
-
for ((metaKey, metaEntry) in prefs.metadata) {
val rowLayout = LayoutInflater.from(themedCtx).inflate(R.layout.import_summary_item, null)
val label = (rowLayout.findViewById(R.id.summary_text) as TextView)
@@ -92,7 +92,7 @@ object PrefImportSummaryDialog {
webView.setBackgroundColor(Color.TRANSPARENT)
webView.setLayerType(WebView.LAYER_TYPE_SOFTWARE, null)
- AlertDialogHelper.Builder(context, R.style.AppTheme)
+ MaterialAlertDialogBuilder(context, R.style.DialogTheme)
.setCustomTitle(
AlertDialogHelper.buildCustomTitle(
context,
@@ -109,11 +109,10 @@ object PrefImportSummaryDialog {
}
}
- val builder = AlertDialogHelper.Builder(context, theme)
+ val builder = MaterialAlertDialogBuilder(context, theme)
.setMessage(context.getString(messageRes))
.setCustomTitle(AlertDialogHelper.buildCustomTitle(context, context.getString(R.string.nav_import), headerIcon, theme))
.setView(innerLayout)
-
.setNegativeButton(android.R.string.cancel) { dialog: DialogInterface, _: Int ->
dialog.dismiss()
SystemClock.sleep(100)
@@ -137,4 +136,4 @@ object PrefImportSummaryDialog {
dialog.setCanceledOnTouchOutside(false)
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/utils/alertDialogs/TwoMessagesAlertDialog.kt b/app/src/main/java/info/nightscout/androidaps/utils/alertDialogs/TwoMessagesAlertDialog.kt
index a2c4e78e2f..d4f2787538 100644
--- a/app/src/main/java/info/nightscout/androidaps/utils/alertDialogs/TwoMessagesAlertDialog.kt
+++ b/app/src/main/java/info/nightscout/androidaps/utils/alertDialogs/TwoMessagesAlertDialog.kt
@@ -8,6 +8,7 @@ import android.view.LayoutInflater
import android.view.View
import android.widget.TextView
import androidx.annotation.DrawableRes
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
import info.nightscout.androidaps.R
import info.nightscout.androidaps.extensions.runOnUiThread
@@ -19,7 +20,7 @@ object TwoMessagesAlertDialog {
val secondMessageLayout = LayoutInflater.from(context).inflate(R.layout.dialog_alert_two_messages, null)
(secondMessageLayout.findViewById(R.id.password_prompt_title) as TextView).text = secondMessage
- val dialog = AlertDialogHelper.Builder(context)
+ MaterialAlertDialogBuilder(context, R.style.DialogTheme)
.setMessage(message)
.setCustomTitle(
AlertDialogHelper.buildCustomTitle(
@@ -32,7 +33,6 @@ object TwoMessagesAlertDialog {
dialog.dismiss()
SystemClock.sleep(100)
if (ok != null) runOnUiThread { ok() }
-
}
.setNegativeButton(android.R.string.cancel) { dialog: DialogInterface, _: Int ->
dialog.dismiss()
@@ -40,7 +40,7 @@ object TwoMessagesAlertDialog {
if (cancel != null) runOnUiThread { cancel() }
}
.show()
- dialog.setCanceledOnTouchOutside(false)
+ .setCanceledOnTouchOutside(false)
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/utils/androidNotification/NotificationHolderImpl.kt b/app/src/main/java/info/nightscout/androidaps/utils/androidNotification/NotificationHolderImpl.kt
index 4345775d02..9aebb55ccb 100644
--- a/app/src/main/java/info/nightscout/androidaps/utils/androidNotification/NotificationHolderImpl.kt
+++ b/app/src/main/java/info/nightscout/androidaps/utils/androidNotification/NotificationHolderImpl.kt
@@ -12,7 +12,7 @@ import info.nightscout.androidaps.MainActivity
import info.nightscout.androidaps.core.R
import info.nightscout.androidaps.interfaces.IconsProvider
import info.nightscout.androidaps.interfaces.NotificationHolder
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import javax.inject.Inject
import javax.inject.Singleton
diff --git a/app/src/main/java/info/nightscout/androidaps/utils/buildHelper/BuildHelperImpl.kt b/app/src/main/java/info/nightscout/androidaps/utils/buildHelper/BuildHelperImpl.kt
index 77e725e998..5408591634 100644
--- a/app/src/main/java/info/nightscout/androidaps/utils/buildHelper/BuildHelperImpl.kt
+++ b/app/src/main/java/info/nightscout/androidaps/utils/buildHelper/BuildHelperImpl.kt
@@ -1,6 +1,7 @@
package info.nightscout.androidaps.utils.buildHelper
import info.nightscout.androidaps.BuildConfig
+import info.nightscout.androidaps.interfaces.BuildHelper
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.plugins.general.maintenance.PrefFileListProvider
import java.io.File
diff --git a/app/src/main/java/info/nightscout/androidaps/utils/stats/DexcomTIR.kt b/app/src/main/java/info/nightscout/androidaps/utils/stats/DexcomTIR.kt
new file mode 100644
index 0000000000..c0f7acf1cf
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/utils/stats/DexcomTIR.kt
@@ -0,0 +1,156 @@
+package info.nightscout.androidaps.utils.stats
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.Typeface
+import android.view.Gravity
+import android.widget.TableRow
+import android.widget.TextView
+import info.nightscout.androidaps.Constants
+import info.nightscout.androidaps.R
+import info.nightscout.androidaps.interfaces.Profile
+import info.nightscout.androidaps.interfaces.ProfileFunction
+import info.nightscout.androidaps.interfaces.ResourceHelper
+import java.util.*
+import kotlin.math.pow
+import kotlin.math.roundToInt
+import kotlin.math.sqrt
+
+class DexcomTIR {
+
+ private var veryLow = 0
+ private var low = 0
+ private var inRange = 0
+ private var high = 0
+ private var veryHigh = 0
+ private var error = 0
+ private var count = 0
+
+ var sum = 0.0
+ val values = mutableListOf()
+
+ private val veryLowTirMgdl = Constants.STATS_RANGE_VERY_LOW_MMOL * Constants.MMOLL_TO_MGDL
+ private val lowTirMgdl = Constants.STATS_RANGE_LOW_MMOL * Constants.MMOLL_TO_MGDL
+ private val highTirMgdl = Constants.STATS_RANGE_HIGH_MMOL * Constants.MMOLL_TO_MGDL
+ private val highNightTirMgdl = Constants.STATS_RANGE_HIGH_NIGHT_MMOL * Constants.MMOLL_TO_MGDL
+ private val veryHighTirMgdl = Constants.STATS_RANGE_VERY_HIGH_MMOL * Constants.MMOLL_TO_MGDL
+
+ private fun error() = run { error++ }
+ private fun veryLow(valueMgdl: Double) = run { values.add(valueMgdl); sum += valueMgdl; veryLow++; count++ }
+ private fun low(valueMgdl: Double) = run { values.add(valueMgdl); sum += valueMgdl; low++; count++ }
+ private fun inRange(valueMgdl: Double) = run { values.add(valueMgdl); sum += valueMgdl; inRange++; count++ }
+ private fun high(valueMgdl: Double) = run { values.add(valueMgdl); sum += valueMgdl; high++; count++ }
+ private fun veryHigh(valueMgdl: Double) = run { values.add(valueMgdl); sum += valueMgdl; veryHigh++; count++ }
+
+ private fun highTirMgdl(hour: Int) = if (hour in 6..22) highTirMgdl else highNightTirMgdl
+
+ fun add(time: Long, valueMgdl: Double) {
+ val c = Calendar.getInstance()
+ c.timeInMillis = time
+ val hour = c[Calendar.HOUR_OF_DAY]
+ when {
+ valueMgdl < 39 -> error()
+ valueMgdl < veryLowTirMgdl -> veryLow(valueMgdl)
+ valueMgdl < lowTirMgdl -> low(valueMgdl)
+ valueMgdl > veryHighTirMgdl -> veryHigh(valueMgdl)
+ valueMgdl > highTirMgdl(hour) -> high(valueMgdl)
+ else -> inRange(valueMgdl)
+ }
+ }
+
+ private fun veryLowPct() = if (count > 0) veryLow.toDouble() / count * 100.0 else 0.0
+ private fun lowPct() = if (count > 0) low.toDouble() / count * 100.0 else 0.0
+ private fun inRangePct() = if (count > 0) 100 - veryLowPct() - lowPct() - highPct() - veryHighPct() else 0.0
+ private fun highPct() = if (count > 0) high.toDouble() / count * 100.0 else 0.0
+ private fun veryHighPct() = if (count > 0) veryHigh.toDouble() / count * 100.0 else 0.0
+ private fun mean() = sum / count
+
+ fun calculateSD(): Double {
+ if (count == 0) return 0.0
+ var standardDeviation = 0.0
+ for (num in values) standardDeviation += (num - mean()).pow(2.0)
+ return sqrt(standardDeviation / count)
+ }
+
+ fun toHbA1cView(context: Context, rh: ResourceHelper): TextView =
+ TextView(context).apply {
+ text =
+ if (count == 0) ""
+ else rh.gs(R.string.hba1c) +
+ (10 * (mean() + 46.7) / 28.7).roundToInt() / 10.0 + "%" +
+ " (" +
+ (((mean() + 46.7) / 28.7 - 2.15) * 10.929).roundToInt() +
+ " mmol/L)"
+ setTypeface(typeface, Typeface.NORMAL)
+ gravity = Gravity.CENTER_HORIZONTAL
+ }
+
+ @SuppressLint("SetTextI18n")
+ fun toSDView(context: Context, rh: ResourceHelper, profileFunction: ProfileFunction): TextView =
+ TextView(context).apply {
+ val sd = calculateSD()
+ text = "\n" + rh.gs(R.string.std_deviation, Profile.toUnitsString(sd, sd * Constants.MGDL_TO_MMOLL, profileFunction.getUnits()))
+ setTypeface(typeface, Typeface.NORMAL)
+ gravity = Gravity.CENTER_HORIZONTAL
+ }
+
+ fun toRangeHeaderView(context: Context, rh: ResourceHelper, profileFunction: ProfileFunction): TextView =
+ TextView(context).apply {
+ text = StringBuilder()
+ .append(rh.gs(R.string.detailed_14_days))
+ .append("\n")
+ .append(rh.gs(R.string.day_tir))
+ .append(" (")
+ .append(Profile.toUnitsString(0.0, 0.0, profileFunction.getUnits()))
+ .append("-")
+ .append(Profile.toCurrentUnitsString(profileFunction, veryLowTirMgdl))
+ .append("-")
+ .append(Profile.toCurrentUnitsString(profileFunction, lowTirMgdl))
+ .append("-")
+ .append(Profile.toCurrentUnitsString(profileFunction, highTirMgdl))
+ .append("-")
+ .append(Profile.toCurrentUnitsString(profileFunction, veryHighTirMgdl))
+ .append("-∞)\n")
+ .append(rh.gs(R.string.night_tir))
+ .append(" (")
+ .append(Profile.toUnitsString(0.0, 0.0, profileFunction.getUnits()))
+ .append("-")
+ .append(Profile.toCurrentUnitsString(profileFunction, veryLowTirMgdl))
+ .append("-")
+ .append(Profile.toCurrentUnitsString(profileFunction, lowTirMgdl))
+ .append("-")
+ .append(Profile.toCurrentUnitsString(profileFunction, highNightTirMgdl))
+ .append("-")
+ .append(Profile.toCurrentUnitsString(profileFunction, veryHighTirMgdl))
+ .append("-∞)\n")
+ .toString()
+ setTypeface(typeface, Typeface.BOLD)
+ gravity = Gravity.CENTER_HORIZONTAL
+ setTextAppearance(android.R.style.TextAppearance_Material_Medium)
+ }
+
+ fun toTableRowHeader(context: Context, rh: ResourceHelper): TableRow =
+ TableRow(context).also { header ->
+ val lp = TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT, TableRow.LayoutParams.WRAP_CONTENT)
+ header.layoutParams = TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.WRAP_CONTENT)
+ header.gravity = Gravity.CENTER_HORIZONTAL
+ header.addView(TextView(context).apply { gravity = Gravity.CENTER_HORIZONTAL; layoutParams = lp.apply { column = 0; weight = 1f }; text = rh.gs(R.string.veryLow) })
+ header.addView(TextView(context).apply { gravity = Gravity.CENTER_HORIZONTAL; layoutParams = lp.apply { column = 1; weight = 1f }; text = rh.gs(R.string.low) })
+ header.addView(TextView(context).apply { gravity = Gravity.CENTER_HORIZONTAL; layoutParams = lp.apply { column = 2; weight = 1f }; text = rh.gs(R.string.in_range) })
+ header.addView(TextView(context).apply { gravity = Gravity.CENTER_HORIZONTAL; layoutParams = lp.apply { column = 3; weight = 1f }; text = rh.gs(R.string.high) })
+ header.addView(TextView(context).apply { gravity = Gravity.CENTER_HORIZONTAL; layoutParams = lp.apply { column = 4; weight = 1f }; text = rh.gs(R.string.veryHigh) })
+ }
+
+ @SuppressLint("SetTextI18n")
+ fun toTableRow(context: Context, rh: ResourceHelper): TableRow =
+ TableRow(context).also { row ->
+ val lp = TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT, TableRow.LayoutParams.WRAP_CONTENT, 1f)
+ row.layoutParams = TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.WRAP_CONTENT)
+ row.gravity = Gravity.CENTER_HORIZONTAL
+ row.addView(TextView(context).apply { gravity = Gravity.CENTER_HORIZONTAL; layoutParams = lp.apply { column = 0 }; text = rh.gs(R.string.formatPercent, veryLowPct()) })
+ row.addView(TextView(context).apply { gravity = Gravity.CENTER_HORIZONTAL; layoutParams = lp.apply { column = 1 }; text = rh.gs(R.string.formatPercent, lowPct()) })
+ row.addView(TextView(context).apply { gravity = Gravity.CENTER_HORIZONTAL; layoutParams = lp.apply { column = 2 }; text = rh.gs(R.string.formatPercent, inRangePct()) })
+ row.addView(TextView(context).apply { gravity = Gravity.CENTER_HORIZONTAL; layoutParams = lp.apply { column = 3 }; text = rh.gs(R.string.formatPercent, highPct()) })
+ row.addView(TextView(context).apply { gravity = Gravity.CENTER_HORIZONTAL; layoutParams = lp.apply { column = 4 }; text = rh.gs(R.string.formatPercent, veryHighPct()) })
+ }
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/utils/stats/DexcomTirCalculator.kt b/app/src/main/java/info/nightscout/androidaps/utils/stats/DexcomTirCalculator.kt
new file mode 100644
index 0000000000..64508f1762
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/utils/stats/DexcomTirCalculator.kt
@@ -0,0 +1,46 @@
+package info.nightscout.androidaps.utils.stats
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.view.ViewGroup
+import android.widget.TableLayout
+import info.nightscout.androidaps.database.AppRepository
+import info.nightscout.androidaps.interfaces.ProfileFunction
+import info.nightscout.androidaps.interfaces.ResourceHelper
+import info.nightscout.androidaps.utils.DateUtil
+import info.nightscout.androidaps.utils.MidnightTime
+import info.nightscout.androidaps.utils.T
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class DexcomTirCalculator @Inject constructor(
+ private val rh: ResourceHelper,
+ private val profileFunction: ProfileFunction,
+ private val dateUtil: DateUtil,
+ private val repository: AppRepository
+) {
+ val days = 14L
+
+ fun calculate(): DexcomTIR {
+ val startTime = MidnightTime.calc(dateUtil.now() - T.days(days).msecs())
+ val endTime = MidnightTime.calc(dateUtil.now())
+
+ val bgReadings = repository.compatGetBgReadingsDataFromTime(startTime, endTime, true).blockingGet()
+ val result = DexcomTIR()
+ for (bg in bgReadings) result.add(bg.timestamp, bg.value)
+ return result
+ }
+
+ @SuppressLint("SetTextI18n")
+ fun stats(context: Context): TableLayout =
+ TableLayout(context).also { layout ->
+ val tir = calculate()
+ layout.layoutParams = TableLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)
+ layout.addView(tir.toRangeHeaderView(context, rh, profileFunction))
+ layout.addView(tir.toTableRowHeader(context, rh))
+ layout.addView(tir.toTableRow(context, rh))
+ layout.addView(tir.toSDView(context, rh, profileFunction))
+ layout.addView(tir.toHbA1cView(context, rh))
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/utils/stats/TIR.kt b/app/src/main/java/info/nightscout/androidaps/utils/stats/TIR.kt
index 6b688c15cb..90ebef0c0e 100644
--- a/app/src/main/java/info/nightscout/androidaps/utils/stats/TIR.kt
+++ b/app/src/main/java/info/nightscout/androidaps/utils/stats/TIR.kt
@@ -1,11 +1,16 @@
package info.nightscout.androidaps.utils.stats
+import android.annotation.SuppressLint
+import android.content.Context
+import android.view.Gravity
+import android.widget.TableRow
+import android.widget.TextView
import info.nightscout.androidaps.R
import info.nightscout.androidaps.utils.DateUtil
-import info.nightscout.androidaps.utils.resources.ResourceHelper
-import kotlin.math.roundToInt
+import info.nightscout.androidaps.interfaces.ResourceHelper
class TIR(val date: Long, val lowThreshold: Double, val highThreshold: Double) {
+
internal var below = 0
internal var inRange = 0
internal var above = 0
@@ -17,11 +22,44 @@ class TIR(val date: Long, val lowThreshold: Double, val highThreshold: Double) {
fun inRange() = run { inRange++; count++ }
fun above() = run { above++; count++ }
- private fun belowPct() = if (count > 0) (below.toDouble() / count * 100.0).roundToInt() else 0
- private fun inRangePct() = if (count > 0) 100 - belowPct() - abovePct() else 0
- private fun abovePct() = if (count > 0) (above.toDouble() / count * 100.0).roundToInt() else 0
+ private fun belowPct() = if (count > 0) below.toDouble() / count * 100.0 else 0.0
+ private fun inRangePct() = if (count > 0) 100 - belowPct() - abovePct() else 0.0
+ private fun abovePct() = if (count > 0) above.toDouble() / count * 100.0 else 0.0
- fun toText(rh: ResourceHelper, dateUtil: DateUtil): String = rh.gs(R.string.tirformat, dateUtil.dateStringShort(date), belowPct(), inRangePct(), abovePct())
+ companion object {
- fun toText(rh: ResourceHelper, days: Int): String = rh.gs(R.string.tirformat, "%02d".format(days) + " " + rh.gs(R.string.days), belowPct(), inRangePct(), abovePct())
+ fun toTableRowHeader(context: Context, rh: ResourceHelper): TableRow =
+ TableRow(context).also { header ->
+ val lp = TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT, TableRow.LayoutParams.WRAP_CONTENT)
+ header.layoutParams = TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.WRAP_CONTENT)
+ header.gravity = Gravity.CENTER_HORIZONTAL
+ header.addView(TextView(context).apply { gravity = Gravity.CENTER_HORIZONTAL; layoutParams = lp.apply { column = 0; weight = 1f }; text = rh.gs(R.string.date) })
+ header.addView(TextView(context).apply { gravity = Gravity.CENTER_HORIZONTAL; layoutParams = lp.apply { column = 1; weight = 1f }; text = rh.gs(R.string.below) })
+ header.addView(TextView(context).apply { gravity = Gravity.CENTER_HORIZONTAL; layoutParams = lp.apply { column = 2; weight = 1f }; text = rh.gs(R.string.in_range) })
+ header.addView(TextView(context).apply { gravity = Gravity.CENTER_HORIZONTAL; layoutParams = lp.apply { column = 3; weight = 1f }; text = rh.gs(R.string.above) })
+ }
+ }
+
+ fun toTableRow(context: Context, rh: ResourceHelper, dateUtil: DateUtil): TableRow =
+ TableRow(context).also { row ->
+ val lp = TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT, TableRow.LayoutParams.WRAP_CONTENT, 1f)
+ row.layoutParams = TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.WRAP_CONTENT)
+ row.gravity = Gravity.CENTER_HORIZONTAL
+ row.addView(TextView(context).apply { gravity = Gravity.CENTER_HORIZONTAL; layoutParams = lp.apply { column = 0 }; text = dateUtil.dateStringShort(date) })
+ row.addView(TextView(context).apply { gravity = Gravity.CENTER_HORIZONTAL; layoutParams = lp.apply { column = 1 }; text = rh.gs(R.string.formatPercent, belowPct()) })
+ row.addView(TextView(context).apply { gravity = Gravity.CENTER_HORIZONTAL; layoutParams = lp.apply { column = 2 }; text = rh.gs(R.string.formatPercent, inRangePct()) })
+ row.addView(TextView(context).apply { gravity = Gravity.CENTER_HORIZONTAL; layoutParams = lp.apply { column = 3 }; text = rh.gs(R.string.formatPercent, abovePct()) })
+ }
+
+ @SuppressLint("SetTextI18n")
+ fun toTableRow(context: Context, rh: ResourceHelper, days: Int): TableRow =
+ TableRow(context).also { row ->
+ val lp = TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT, TableRow.LayoutParams.WRAP_CONTENT, 1f)
+ row.layoutParams = TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.WRAP_CONTENT)
+ row.gravity = Gravity.CENTER_HORIZONTAL
+ row.addView(TextView(context).apply { gravity = Gravity.CENTER_HORIZONTAL; layoutParams = lp.apply { column = 0 }; text = "%02d".format(days) + " " + rh.gs(R.string.days) })
+ row.addView(TextView(context).apply { gravity = Gravity.CENTER_HORIZONTAL; layoutParams = lp.apply { column = 1 }; text = rh.gs(R.string.formatPercent, belowPct()) })
+ row.addView(TextView(context).apply { gravity = Gravity.CENTER_HORIZONTAL; layoutParams = lp.apply { column = 2 }; text = rh.gs(R.string.formatPercent, inRangePct()) })
+ row.addView(TextView(context).apply { gravity = Gravity.CENTER_HORIZONTAL; layoutParams = lp.apply { column = 3 }; text = rh.gs(R.string.formatPercent, abovePct()) })
+ }
}
diff --git a/app/src/main/java/info/nightscout/androidaps/utils/stats/TddCalculator.kt b/app/src/main/java/info/nightscout/androidaps/utils/stats/TddCalculator.kt
index 88d281a348..f9b7ba509a 100644
--- a/app/src/main/java/info/nightscout/androidaps/utils/stats/TddCalculator.kt
+++ b/app/src/main/java/info/nightscout/androidaps/utils/stats/TddCalculator.kt
@@ -1,23 +1,31 @@
package info.nightscout.androidaps.utils.stats
-import android.text.Spanned
+import android.content.Context
+import android.graphics.Typeface
import android.util.LongSparseArray
+import android.view.Gravity
+import android.view.ViewGroup
+import android.widget.TableLayout
+import android.widget.TableRow
+import android.widget.TextView
import info.nightscout.androidaps.R
import info.nightscout.androidaps.database.AppRepository
+import info.nightscout.androidaps.database.ValueWrapper
+import info.nightscout.androidaps.database.embedments.InterfaceIDs
import info.nightscout.androidaps.database.entities.Bolus
import info.nightscout.androidaps.database.entities.TotalDailyDose
+import info.nightscout.androidaps.extensions.convertedToAbsolute
+import info.nightscout.androidaps.extensions.toTableRow
+import info.nightscout.androidaps.extensions.toTableRowHeader
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.IobCobCalculator
import info.nightscout.androidaps.interfaces.ProfileFunction
-import info.nightscout.shared.logging.AAPSLogger
-import info.nightscout.shared.logging.LTag
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.DateUtil
-import info.nightscout.androidaps.utils.HtmlHelper
import info.nightscout.androidaps.utils.MidnightTime
import info.nightscout.androidaps.utils.T
-import info.nightscout.androidaps.extensions.convertedToAbsolute
-import info.nightscout.androidaps.extensions.toText
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.shared.logging.LTag
import javax.inject.Inject
class TddCalculator @Inject constructor(
@@ -31,31 +39,90 @@ class TddCalculator @Inject constructor(
) {
fun calculate(days: Long): LongSparseArray {
- val startTime = MidnightTime.calc(dateUtil.now() - T.days(days).msecs())
+ var startTime = MidnightTime.calc(dateUtil.now() - T.days(days).msecs())
val endTime = MidnightTime.calc(dateUtil.now())
val result = LongSparseArray()
+ // Try to load cached values
+ while (startTime < endTime) {
+ val tdd = repository.getCalculatedTotalDailyDose(startTime).blockingGet()
+ if (tdd is ValueWrapper.Existing) result.put(startTime, tdd.value)
+ else break
+ startTime += T.hours(24).msecs()
+ }
+
+ if (endTime > startTime) {
+ repository.getBolusesDataFromTimeToTime(startTime, endTime, true).blockingGet()
+ .filter { it.type != Bolus.Type.PRIMING }
+ .forEach { t ->
+ val midnight = MidnightTime.calc(t.timestamp)
+ val tdd = result[midnight] ?: TotalDailyDose(timestamp = midnight)
+ tdd.bolusAmount += t.amount
+ result.put(midnight, tdd)
+ }
+ repository.getCarbsDataFromTimeToTimeExpanded(startTime, endTime, true).blockingGet().forEach { t ->
+ val midnight = MidnightTime.calc(t.timestamp)
+ val tdd = result[midnight] ?: TotalDailyDose(timestamp = midnight)
+ tdd.carbs += t.amount
+ result.put(midnight, tdd)
+ }
+
+ val calculationStep = T.mins(5).msecs()
+ val tempBasals = iobCobCalculator.getTempBasalIncludingConvertedExtendedForRange(startTime, endTime, calculationStep)
+ for (t in startTime until endTime step calculationStep) {
+ val midnight = MidnightTime.calc(t)
+ val tdd = result[midnight] ?: TotalDailyDose(timestamp = midnight)
+ val tbr = tempBasals[t]
+ val profile = profileFunction.getProfile(t) ?: continue
+ val absoluteRate = tbr?.convertedToAbsolute(t, profile) ?: profile.getBasal(t)
+ tdd.basalAmount += absoluteRate / T.mins(60).msecs().toDouble() * calculationStep.toDouble()
+
+ if (!activePlugin.activePump.isFakingTempsByExtendedBoluses) {
+ // they are not included in TBRs
+ val eb = iobCobCalculator.getExtendedBolus(t)
+ val absoluteEbRate = eb?.rate ?: 0.0
+ tdd.bolusAmount += absoluteEbRate / T.mins(60).msecs().toDouble() * calculationStep.toDouble()
+ }
+ result.put(midnight, tdd)
+ }
+ }
+ for (i in 0 until result.size()) {
+ val tdd = result.valueAt(i)
+ tdd.totalAmount = tdd.bolusAmount + tdd.basalAmount
+ if (tdd.interfaceIDs.pumpType != InterfaceIDs.PumpType.CACHE) {
+ tdd.interfaceIDs.pumpType = InterfaceIDs.PumpType.CACHE
+ aapsLogger.debug(LTag.CORE, "Storing TDD $tdd")
+ repository.createTotalDailyDose(tdd)
+ }
+ }
+ return result
+ }
+
+ fun calculateToday(): TotalDailyDose {
+ var startTime = MidnightTime.calc(dateUtil.now())
+ val endTime = dateUtil.now()
+ return calculate(startTime, endTime)
+ }
+
+ fun calculateDaily(startHours: Long, endHours: Long): TotalDailyDose {
+ val startTime = dateUtil.now() + T.hours(hour = startHours).msecs()
+ val endTime = dateUtil.now() + T.hours(hour = endHours).msecs()
+ return calculate(startTime, endTime)
+ }
+
+ fun calculate(startTime: Long, endTime: Long): TotalDailyDose {
+ val tdd = TotalDailyDose(timestamp = startTime)
repository.getBolusesDataFromTimeToTime(startTime, endTime, true).blockingGet()
.filter { it.type != Bolus.Type.PRIMING }
.forEach { t ->
- val midnight = MidnightTime.calc(t.timestamp)
- val tdd = result[midnight] ?: TotalDailyDose(timestamp = midnight)
tdd.bolusAmount += t.amount
- result.put(midnight, tdd)
}
repository.getCarbsDataFromTimeToTimeExpanded(startTime, endTime, true).blockingGet().forEach { t ->
- val midnight = MidnightTime.calc(t.timestamp)
- val tdd = result[midnight] ?: TotalDailyDose(timestamp = midnight)
tdd.carbs += t.amount
- result.put(midnight, tdd)
}
-
val calculationStep = T.mins(5).msecs()
- val tempBasals = iobCobCalculator.getTempBasalIncludingConvertedExtendedForRange(startTime, endTime, calculationStep)
for (t in startTime until endTime step calculationStep) {
- val midnight = MidnightTime.calc(t)
- val tdd = result[midnight] ?: TotalDailyDose(timestamp = midnight)
- val tbr = tempBasals[t]
+ val tbr = iobCobCalculator.getTempBasalIncludingConvertedExtended(t)
val profile = profileFunction.getProfile(t) ?: continue
val absoluteRate = tbr?.convertedToAbsolute(t, profile) ?: profile.getBasal(t)
tdd.basalAmount += absoluteRate / 60.0 * 5.0
@@ -66,18 +133,15 @@ class TddCalculator @Inject constructor(
val absoluteEbRate = eb?.rate ?: 0.0
tdd.bolusAmount += absoluteEbRate / 60.0 * 5.0
}
- result.put(midnight, tdd)
}
- for (i in 0 until result.size()) {
- val tdd = result.valueAt(i)
- tdd.totalAmount = tdd.bolusAmount + tdd.basalAmount
- }
- aapsLogger.debug(LTag.CORE, result.toString())
- return result
+ tdd.totalAmount = tdd.bolusAmount + tdd.basalAmount
+ aapsLogger.debug(LTag.CORE, tdd.toString())
+ return tdd
}
- private fun averageTDD(tdds: LongSparseArray): TotalDailyDose {
+ fun averageTDD(tdds: LongSparseArray): TotalDailyDose? {
val totalTdd = TotalDailyDose(timestamp = dateUtil.now())
+ if (tdds.size() == 0) return null
for (i in 0 until tdds.size()) {
val tdd = tdds.valueAt(i)
totalTdd.basalAmount += tdd.basalAmount
@@ -92,23 +156,38 @@ class TddCalculator @Inject constructor(
return totalTdd
}
- fun stats(): Spanned {
+ fun stats(context: Context): TableLayout {
val tdds = calculate(7)
val averageTdd = averageTDD(tdds)
- return HtmlHelper.fromHtml(
- "" + rh.gs(R.string.tdd) + ":
" +
- toText(tdds, true) +
- "" + rh.gs(R.string.average) + ":
" +
- averageTdd.toText(rh, tdds.size(), true)
- )
- }
-
- @Suppress("SameParameterValue")
- private fun toText(tdds: LongSparseArray, includeCarbs: Boolean): String {
- var t = ""
- for (i in 0 until tdds.size()) {
- t += "${tdds.valueAt(i).toText(rh, dateUtil, includeCarbs)}
"
+ val todayTdd = calculateToday()
+ val lp = TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT, TableRow.LayoutParams.WRAP_CONTENT)
+ return TableLayout(context).also { layout ->
+ layout.layoutParams = TableLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)
+ layout.addView(TextView(context).apply {
+ text = rh.gs(R.string.tdd)
+ setTypeface(typeface, Typeface.BOLD)
+ gravity = Gravity.CENTER_HORIZONTAL
+ setTextAppearance(android.R.style.TextAppearance_Material_Medium)
+ })
+ layout.addView(TotalDailyDose.toTableRowHeader(context, rh, includeCarbs = true))
+ for (i in 0 until tdds.size()) layout.addView(tdds.valueAt(i).toTableRow(context, rh, dateUtil, includeCarbs = true))
+ averageTdd?.let { averageTdd ->
+ layout.addView(TextView(context).apply {
+ layoutParams = lp
+ text = rh.gs(R.string.average)
+ setTypeface(typeface, Typeface.BOLD)
+ gravity = Gravity.CENTER_HORIZONTAL
+ setTextAppearance(android.R.style.TextAppearance_Material_Medium)
+ })
+ layout.addView(averageTdd.toTableRow(context, rh, tdds.size(), includeCarbs = true))
+ }
+ layout.addView(TextView(context).apply {
+ text = rh.gs(R.string.today)
+ setTypeface(typeface, Typeface.BOLD)
+ gravity = Gravity.CENTER_HORIZONTAL
+ setTextAppearance(android.R.style.TextAppearance_Material_Medium)
+ })
+ layout.addView(todayTdd.toTableRow(context, rh, dateUtil, includeCarbs = true))
}
- return t
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/utils/stats/TirCalculator.kt b/app/src/main/java/info/nightscout/androidaps/utils/stats/TirCalculator.kt
index 58ec13ab54..628c131788 100644
--- a/app/src/main/java/info/nightscout/androidaps/utils/stats/TirCalculator.kt
+++ b/app/src/main/java/info/nightscout/androidaps/utils/stats/TirCalculator.kt
@@ -1,17 +1,22 @@
package info.nightscout.androidaps.utils.stats
-import android.text.Spanned
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.Typeface
import android.util.LongSparseArray
+import android.view.Gravity
+import android.view.ViewGroup
+import android.widget.TableLayout
+import android.widget.TextView
import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R
-import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.database.AppRepository
+import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.utils.DateUtil
-import info.nightscout.androidaps.utils.HtmlHelper
import info.nightscout.androidaps.utils.MidnightTime
import info.nightscout.androidaps.utils.T
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import javax.inject.Inject
import javax.inject.Singleton
@@ -63,38 +68,49 @@ class TirCalculator @Inject constructor(
return totalTir
}
- fun stats(): Spanned {
- val lowTirMgdl = Constants.STATS_RANGE_LOW_MMOL * Constants.MMOLL_TO_MGDL
- val highTirMgdl = Constants.STATS_RANGE_HIGH_MMOL * Constants.MMOLL_TO_MGDL
- val lowTitMgdl = Constants.STATS_TARGET_LOW_MMOL * Constants.MMOLL_TO_MGDL
- val highTitMgdl = Constants.STATS_TARGET_HIGH_MMOL * Constants.MMOLL_TO_MGDL
+ @SuppressLint("SetTextI18n")
+ fun stats(context: Context): TableLayout =
+ TableLayout(context).also { layout ->
+ val lowTirMgdl = Constants.STATS_RANGE_LOW_MMOL * Constants.MMOLL_TO_MGDL
+ val highTirMgdl = Constants.STATS_RANGE_HIGH_MMOL * Constants.MMOLL_TO_MGDL
+ val lowTitMgdl = Constants.STATS_TARGET_LOW_MMOL * Constants.MMOLL_TO_MGDL
+ val highTitMgdl = Constants.STATS_TARGET_HIGH_MMOL * Constants.MMOLL_TO_MGDL
- val tir7 = calculate(7, lowTirMgdl, highTirMgdl)
- val averageTir7 = averageTIR(tir7)
- val tir30 = calculate(30, lowTirMgdl, highTirMgdl)
- val averageTir30 = averageTIR(tir30)
- val tit7 = calculate(7, lowTitMgdl, highTitMgdl)
- val averageTit7 = averageTIR(tit7)
- val tit30 = calculate(30, lowTitMgdl, highTitMgdl)
- val averageTit30 = averageTIR(tit30)
- return HtmlHelper.fromHtml(
- "
" + rh.gs(R.string.tir) + " (" + Profile.toCurrentUnitsString(profileFunction, lowTirMgdl) + "-" + Profile.toCurrentUnitsString(profileFunction, highTirMgdl) + "):
" +
- toText(rh, tir7) +
- "
" + rh.gs(R.string.average) + " (" + Profile.toCurrentUnitsString(profileFunction, lowTirMgdl) + "-" + Profile.toCurrentUnitsString(profileFunction, highTirMgdl) + "):
" +
- averageTir7.toText(rh, tir7.size()) + "
" +
- averageTir30.toText(rh, tir30.size()) +
- "
" + rh.gs(R.string.average) + " (" + Profile.toCurrentUnitsString(profileFunction, lowTitMgdl) + "-" + Profile.toCurrentUnitsString(profileFunction, highTitMgdl) + "):
" +
- averageTit7.toText(rh, tit7.size()) + "
" +
- averageTit30.toText(rh, tit30.size())
- )
- }
-
- fun toText(rh: ResourceHelper, tirs: LongSparseArray): String {
- var t = ""
- for (i in 0 until tirs.size()) {
- t += "${tirs.valueAt(i).toText(rh, dateUtil)}
"
+ val tir7 = calculate(7, lowTirMgdl, highTirMgdl)
+ val averageTir7 = averageTIR(tir7)
+ val tir30 = calculate(30, lowTirMgdl, highTirMgdl)
+ val averageTir30 = averageTIR(tir30)
+ val tit7 = calculate(7, lowTitMgdl, highTitMgdl)
+ val averageTit7 = averageTIR(tit7)
+ val tit30 = calculate(30, lowTitMgdl, highTitMgdl)
+ val averageTit30 = averageTIR(tit30)
+ layout.layoutParams = TableLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)
+ layout.addView(
+ TextView(context).apply {
+ text = rh.gs(R.string.tir) + " (" + Profile.toCurrentUnitsString(profileFunction, lowTirMgdl) + "-" + Profile.toCurrentUnitsString(profileFunction, highTirMgdl) + ")"
+ setTypeface(typeface, Typeface.BOLD)
+ gravity = Gravity.CENTER_HORIZONTAL
+ setTextAppearance(android.R.style.TextAppearance_Material_Medium)
+ })
+ layout.addView(TIR.toTableRowHeader(context, rh))
+ for (i in 0 until tir7.size()) layout.addView(tir7.valueAt(i).toTableRow(context, rh, dateUtil))
+ layout.addView(
+ TextView(context).apply {
+ text = rh.gs(R.string.average) + " (" + Profile.toCurrentUnitsString(profileFunction, lowTirMgdl) + "-" + Profile.toCurrentUnitsString(profileFunction, highTirMgdl) + ")"
+ setTypeface(typeface, Typeface.BOLD)
+ gravity = Gravity.CENTER_HORIZONTAL
+ setTextAppearance(android.R.style.TextAppearance_Material_Medium)
+ })
+ layout.addView(averageTir7.toTableRow(context, rh, tir7.size()))
+ layout.addView(averageTir30.toTableRow(context, rh, tir30.size()))
+ layout.addView(
+ TextView(context).apply {
+ text = rh.gs(R.string.average) + " (" + Profile.toCurrentUnitsString(profileFunction, lowTitMgdl) + "-" + Profile.toCurrentUnitsString(profileFunction, highTitMgdl) + ")"
+ setTypeface(typeface, Typeface.BOLD)
+ gravity = Gravity.CENTER_HORIZONTAL
+ setTextAppearance(android.R.style.TextAppearance_Material_Medium)
+ })
+ layout.addView(averageTit7.toTableRow(context, rh, tit7.size()))
+ layout.addView(averageTit30.toTableRow(context, rh, tit30.size()))
}
- return t
- }
-
}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/utils/ui/TimeListEdit.java b/app/src/main/java/info/nightscout/androidaps/utils/ui/TimeListEdit.java
index 246792e17b..e1645c907f 100644
--- a/app/src/main/java/info/nightscout/androidaps/utils/ui/TimeListEdit.java
+++ b/app/src/main/java/info/nightscout/androidaps/utils/ui/TimeListEdit.java
@@ -112,6 +112,7 @@ public class TimeListEdit {
float factor = layout.getContext().getResources().getDisplayMetrics().density;
finalAdd = new ImageView(context);
finalAdd.setImageResource(R.drawable.ic_add);
+ finalAdd.setContentDescription(layout.getContext().getResources().getString(R.string.a11y_add_new_to_list));
LinearLayout.LayoutParams illp = new LinearLayout.LayoutParams((int) (35d * factor), (int) (35 * factor));
illp.setMargins(0, 25, 0, 25); // llp.setMargins(left, top, right, bottom);
illp.gravity = Gravity.CENTER;
@@ -181,7 +182,7 @@ public class TimeListEdit {
numberPickers1[position].setTextWatcher(new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
- Double value1 = SafeParse.stringToDouble(numberPickers1[position].getText());
+ Double value1 = SafeParse.INSTANCE.stringToDouble(numberPickers1[position].getText(), 0.0);
Double value2 = value2(position);
if (data2 != null && value1 > value2) {
value2 = value1;
@@ -208,7 +209,7 @@ public class TimeListEdit {
@Override
public void afterTextChanged(Editable s) {
Double value1 = value1(position);
- Double value2 = SafeParse.stringToDouble(numberPickers2[position].getText());
+ Double value2 = SafeParse.INSTANCE.stringToDouble(numberPickers2[position].getText(), 0.0);
if (data2 != null && value2 < value1) {
value1 = value2;
numberPickers1[position].setValue(value1);
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 c9a361dca5..449af8dd64 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
@@ -18,6 +18,8 @@ import info.nightscout.androidaps.database.entities.ValueWithUnit
import info.nightscout.androidaps.database.transactions.InsertOrUpdateBolusCalculatorResultTransaction
import info.nightscout.androidaps.events.EventRefreshOverview
import info.nightscout.androidaps.extensions.formatColor
+import info.nightscout.androidaps.extensions.highValueToUnitsToString
+import info.nightscout.androidaps.extensions.lowValueToUnitsToString
import info.nightscout.androidaps.interfaces.*
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
@@ -29,10 +31,10 @@ import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProv
import info.nightscout.androidaps.queue.Callback
import info.nightscout.androidaps.utils.*
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
-import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
-import io.reactivex.disposables.CompositeDisposable
-import io.reactivex.rxkotlin.plusAssign
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.kotlin.plusAssign
import java.util.*
import javax.inject.Inject
import kotlin.math.abs
@@ -63,10 +65,14 @@ class BolusWizard @Inject constructor(
private val disposable = CompositeDisposable()
+ var timeStamp : Long
+
init {
injector.androidInjector().inject(this)
+ timeStamp = dateUtil.now()
}
+
// Intermediate
var sens = 0.0
private set
@@ -135,27 +141,28 @@ class BolusWizard @Inject constructor(
private var quickWizard: Boolean = true
var usePercentage: Boolean = false
- fun doCalc(profile: Profile,
- profileName: String,
- tempTarget: TemporaryTarget?,
- carbs: Int,
- cob: Double,
- bg: Double,
- correction: Double,
- percentageCorrection: Int = 100,
- useBg: Boolean,
- useCob: Boolean,
- includeBolusIOB: Boolean,
- includeBasalIOB: Boolean,
- useSuperBolus: Boolean,
- useTT: Boolean,
- useTrend: Boolean,
- useAlarm: Boolean,
- notes: String = "",
- carbTime: Int = 0,
- usePercentage: Boolean = false,
- totalPercentage: Double = 100.0,
- quickWizard: Boolean = false
+ fun doCalc(
+ profile: Profile,
+ profileName: String,
+ tempTarget: TemporaryTarget?,
+ carbs: Int,
+ cob: Double,
+ bg: Double,
+ correction: Double,
+ percentageCorrection: Int = 100,
+ useBg: Boolean,
+ useCob: Boolean,
+ includeBolusIOB: Boolean,
+ includeBasalIOB: Boolean,
+ useSuperBolus: Boolean,
+ useTT: Boolean,
+ useTrend: Boolean,
+ useAlarm: Boolean,
+ notes: String = "",
+ carbTime: Int = 0,
+ usePercentage: Boolean = false,
+ totalPercentage: Double = 100.0,
+ quickWizard: Boolean = false
): BolusWizard {
this.profile = profile
@@ -233,7 +240,7 @@ class BolusWizard @Inject constructor(
// Total
calculatedTotalInsulin = insulinFromBG + insulinFromTrend + insulinFromCarbs + insulinFromBolusIOB + insulinFromBasalIOB + insulinFromCorrection + insulinFromSuperBolus + insulinFromCOB
- var percentage = if (usePercentage) totalPercentage else percentageCorrection.toDouble()
+ val percentage = if (usePercentage) totalPercentage else percentageCorrection.toDouble()
// Percentage adjustment
totalBeforePercentageAdjustment = calculatedTotalInsulin
@@ -261,22 +268,23 @@ class BolusWizard @Inject constructor(
return this
}
- private fun createBolusCalculatorResult(): BolusCalculatorResult =
- BolusCalculatorResult(
+ private fun createBolusCalculatorResult(): BolusCalculatorResult {
+ val unit = profileFunction.getUnits()
+ return BolusCalculatorResult(
timestamp = dateUtil.now(),
- targetBGLow = targetBGLow,
- targetBGHigh = targetBGHigh,
- isf = sens,
+ targetBGLow = Profile.toMgdl(targetBGLow, unit),
+ targetBGHigh = Profile.toMgdl(targetBGHigh, unit),
+ isf = Profile.toMgdl(sens, unit),
ic = ic,
bolusIOB = insulinFromBolusIOB,
wasBolusIOBUsed = includeBolusIOB,
basalIOB = insulinFromBasalIOB,
wasBasalIOBUsed = includeBasalIOB,
- glucoseValue = bg,
+ glucoseValue = Profile.toMgdl(bg, unit),
wasGlucoseUsed = useBg && bg > 0,
glucoseDifference = bgDiff,
glucoseInsulin = insulinFromBG,
- glucoseTrend = trend,
+ glucoseTrend = Profile.fromMgdlToUnits(trend, unit),
wasTrendUsed = useTrend,
trendInsulin = insulinFromTrend,
cob = cob,
@@ -294,13 +302,14 @@ class BolusWizard @Inject constructor(
profileName = profileName,
note = notes
)
+ }
- private fun confirmMessageAfterConstraints(advisor: Boolean): Spanned {
+ private fun confirmMessageAfterConstraints(context: Context, advisor: Boolean): Spanned {
val actions: LinkedList = LinkedList()
if (insulinAfterConstraints > 0) {
val pct = if (percentageCorrection != 100) " ($percentageCorrection%)" else ""
- actions.add(rh.gs(R.string.bolus) + ": " + rh.gs(R.string.formatinsulinunits, insulinAfterConstraints).formatColor(rh, R.color.bolus) + pct)
+ actions.add(rh.gs(R.string.bolus) + ": " + rh.gs(R.string.formatinsulinunits, insulinAfterConstraints).formatColor(context, rh, R.attr.bolusColor) + pct)
}
if (carbs > 0 && !advisor) {
var timeShift = ""
@@ -309,22 +318,25 @@ class BolusWizard @Inject constructor(
} else if (carbTime < 0) {
timeShift += " (" + rh.gs(R.string.mins, carbTime) + ")"
}
- actions.add(rh.gs(R.string.carbs) + ": " + rh.gs(R.string.format_carbs, carbs).formatColor(rh, R.color.carbs) + timeShift)
+ actions.add(rh.gs(R.string.carbs) + ": " + rh.gs(R.string.format_carbs, carbs).formatColor(context, rh, R.attr.carbsColor) + timeShift)
}
if (insulinFromCOB > 0) {
- actions.add(rh.gs(R.string.cobvsiob) + ": " + rh.gs(R.string.formatsignedinsulinunits, insulinFromBolusIOB + insulinFromBasalIOB + insulinFromCOB + insulinFromBG).formatColor(rh, R.color.cobAlert))
+ actions.add(
+ rh.gs(R.string.cobvsiob) + ": " + rh.gs(R.string.formatsignedinsulinunits, insulinFromBolusIOB + insulinFromBasalIOB + insulinFromCOB + insulinFromBG).formatColor(context, rh, R.attr
+ .cobAlertColor)
+ )
val absorptionRate = iobCobCalculator.ads.slowAbsorptionPercentage(60)
if (absorptionRate > .25)
- actions.add(rh.gs(R.string.slowabsorptiondetected, rh.gc(R.color.cobAlert), (absorptionRate * 100).toInt()))
+ actions.add(rh.gs(R.string.slowabsorptiondetected, rh.gac(context, R.attr.cobAlertColor), (absorptionRate * 100).toInt()))
}
if (abs(insulinAfterConstraints - calculatedTotalInsulin) > activePlugin.activePump.pumpDescription.pumpType.determineCorrectBolusStepSize(insulinAfterConstraints))
- actions.add(rh.gs(R.string.bolusconstraintappliedwarn, calculatedTotalInsulin, insulinAfterConstraints).formatColor(rh, R.color.warning))
+ actions.add(rh.gs(R.string.bolusconstraintappliedwarn, calculatedTotalInsulin, insulinAfterConstraints).formatColor(context, rh, R.attr.warningColor))
if (config.NSCLIENT && insulinAfterConstraints > 0)
- actions.add(rh.gs(R.string.bolusrecordedonly).formatColor(rh, R.color.warning))
+ actions.add(rh.gs(R.string.bolusrecordedonly).formatColor(context, rh, R.attr.warningColor))
if (useAlarm && !advisor && carbs > 0 && carbTime > 0)
- actions.add(rh.gs(R.string.alarminxmin, carbTime).formatColor(rh, R.color.info))
+ actions.add(rh.gs(R.string.alarminxmin, carbTime).formatColor(context, rh, R.attr.infoColor))
if (advisor)
- actions.add(rh.gs(R.string.advisoralarm).formatColor(rh, R.color.info))
+ actions.add(rh.gs(R.string.advisoralarm).formatColor(context, rh, R.attr.infoColor))
return HtmlHelper.fromHtml(Joiner.on("
").join(actions))
}
@@ -342,8 +354,8 @@ class BolusWizard @Inject constructor(
carbTimer.removeEatReminder()
if (sp.getBoolean(R.string.key_usebolusadvisor, false) && Profile.toMgdl(bg, profile.units) > 180 && carbs > 0 && carbTime >= 0)
OKDialog.showYesNoCancel(ctx, rh.gs(R.string.bolusadvisor), rh.gs(R.string.bolusadvisormessage),
- { bolusAdvisorProcessing(ctx) },
- { commonProcessing(ctx) }
+ { bolusAdvisorProcessing(ctx) },
+ { commonProcessing(ctx) }
)
else
commonProcessing(ctx)
@@ -353,7 +365,7 @@ class BolusWizard @Inject constructor(
}
private fun bolusAdvisorProcessing(ctx: Context) {
- val confirmMessage = confirmMessageAfterConstraints(advisor = true)
+ val confirmMessage = confirmMessageAfterConstraints(ctx, advisor = true)
OKDialog.showConfirmation(ctx, rh.gs(R.string.boluswizard), confirmMessage, {
DetailedBolusInfo().apply {
eventType = DetailedBolusInfo.EventType.CORRECTION_BOLUS
@@ -365,10 +377,13 @@ class BolusWizard @Inject constructor(
carbTime = 0
bolusCalculatorResult = createBolusCalculatorResult()
notes = this@BolusWizard.notes
- uel.log(Action.BOLUS_ADVISOR, if (quickWizard) Sources.QuickWizard else Sources.WizardDialog,
+ uel.log(
+ Action.BOLUS_ADVISOR,
+ if (quickWizard) Sources.QuickWizard else Sources.WizardDialog,
notes,
ValueWithUnit.TherapyEventType(eventType.toDBbEventType()),
- ValueWithUnit.Insulin(insulinAfterConstraints))
+ ValueWithUnit.Insulin(insulinAfterConstraints)
+ )
if (insulin > 0) {
commandQueue.bolus(this, object : Callback() {
override fun run() {
@@ -383,11 +398,30 @@ class BolusWizard @Inject constructor(
})
}
+ fun explainShort(): String {
+ var message = rh.gs(R.string.wizard_explain_calc, ic, sens)
+ message += "\n" + rh.gs(R.string.wizard_explain_carbs, insulinFromCarbs)
+ if (useTT && tempTarget != null) {
+ val tt = if (tempTarget?.lowTarget == tempTarget?.highTarget) tempTarget?.lowValueToUnitsToString(profile.units)
+ else rh.gs(R.string.wizard_explain_tt_to, tempTarget?.lowValueToUnitsToString(profile.units), tempTarget?.highValueToUnitsToString(profile.units))
+ message += "\n" + rh.gs(R.string.wizard_explain_tt, tt)
+ }
+ if (useCob) message += "\n" + rh.gs(R.string.wizard_explain_cob, cob, insulinFromCOB)
+ if (useBg) message += "\n" + rh.gs(R.string.wizard_explain_bg, insulinFromBG)
+ if (includeBolusIOB) message += "\n" + rh.gs(R.string.wizard_explain_iob, insulinFromBolusIOB + insulinFromBasalIOB)
+ if (useTrend) message += "\n" + rh.gs(R.string.wizard_explain_trend, insulinFromTrend)
+ if (useSuperBolus) message += "\n" + rh.gs(R.string.wizard_explain_superbolus, insulinFromSuperBolus)
+ if (percentageCorrection != 100) {
+ message += "\n" + rh.gs(R.string.wizard_explain_percent, totalBeforePercentageAdjustment, percentageCorrection, calculatedTotalInsulin)
+ }
+ return message
+ }
+
private fun commonProcessing(ctx: Context) {
val profile = profileFunction.getProfile() ?: return
val pump = activePlugin.activePump
- val confirmMessage = confirmMessageAfterConstraints(advisor = false)
+ val confirmMessage = confirmMessageAfterConstraints(ctx, advisor = false)
OKDialog.showConfirmation(ctx, rh.gs(R.string.boluswizard), confirmMessage, {
if (insulinAfterConstraints > 0 || carbs > 0) {
if (useSuperBolus) {
@@ -409,12 +443,7 @@ class BolusWizard @Inject constructor(
commandQueue.tempBasalPercent(0, 120, true, profile, PumpSync.TemporaryBasalType.NORMAL, object : Callback() {
override fun run() {
if (!result.success) {
- val i = Intent(ctx, ErrorHelperActivity::class.java)
- i.putExtra(ErrorHelperActivity.SOUND_ID, R.raw.boluserror)
- i.putExtra(ErrorHelperActivity.STATUS, result.comment)
- i.putExtra(ErrorHelperActivity.TITLE, rh.gs(R.string.tempbasaldeliveryerror))
- i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- ctx.startActivity(i)
+ ErrorHelperActivity.runAlarm(ctx, result.comment, rh.gs(R.string.tempbasaldeliveryerror), R.raw.boluserror)
}
}
})
@@ -431,17 +460,17 @@ class BolusWizard @Inject constructor(
bolusCalculatorResult = createBolusCalculatorResult()
notes = this@BolusWizard.notes
if (insulin > 0 || carbs > 0) {
- val action = when {
+ val action = when {
insulinAfterConstraints.equals(0.0) -> Action.CARBS
carbs.equals(0.0) -> Action.BOLUS
else -> Action.TREATMENT
}
uel.log(action, if (quickWizard) Sources.QuickWizard else Sources.WizardDialog,
- notes,
- ValueWithUnit.TherapyEventType(eventType.toDBbEventType()),
- ValueWithUnit.Insulin(insulinAfterConstraints).takeIf { insulinAfterConstraints != 0.0 },
- ValueWithUnit.Gram(this@BolusWizard.carbs).takeIf { this@BolusWizard.carbs != 0 },
- ValueWithUnit.Minute(carbTime).takeIf { carbTime != 0 })
+ notes,
+ ValueWithUnit.TherapyEventType(eventType.toDBbEventType()),
+ ValueWithUnit.Insulin(insulinAfterConstraints).takeIf { insulinAfterConstraints != 0.0 },
+ ValueWithUnit.Gram(this@BolusWizard.carbs).takeIf { this@BolusWizard.carbs != 0 },
+ ValueWithUnit.Minute(carbTime).takeIf { carbTime != 0 })
commandQueue.bolus(this, object : Callback() {
override fun run() {
if (!result.success) {
@@ -467,9 +496,9 @@ class BolusWizard @Inject constructor(
private fun calcPercentageWithConstraints() {
calculatedPercentage = 100.0
if (totalBeforePercentageAdjustment != insulinFromCorrection)
- calculatedPercentage = calculatedTotalInsulin/(totalBeforePercentageAdjustment-insulinFromCorrection) * 100
+ calculatedPercentage = calculatedTotalInsulin / (totalBeforePercentageAdjustment - insulinFromCorrection) * 100
calculatedPercentage = max(calculatedPercentage, 10.0)
- calculatedPercentage = min(calculatedPercentage,250.0)
+ calculatedPercentage = min(calculatedPercentage, 250.0)
}
private fun calcCorrectionWithConstraints() {
@@ -479,4 +508,4 @@ class BolusWizard @Inject constructor(
calculatedCorrection = max(-constraintChecker.getMaxBolusAllowed().value(), calculatedCorrection)
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/utils/wizard/QuickWizard.kt b/app/src/main/java/info/nightscout/androidaps/utils/wizard/QuickWizard.kt
index a254831690..88a4d4edc5 100644
--- a/app/src/main/java/info/nightscout/androidaps/utils/wizard/QuickWizard.kt
+++ b/app/src/main/java/info/nightscout/androidaps/utils/wizard/QuickWizard.kt
@@ -5,8 +5,10 @@ import info.nightscout.androidaps.R
import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONArray
import org.json.JSONObject
+import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
+import kotlin.collections.ArrayList
@Singleton
class QuickWizard @Inject constructor(
@@ -18,6 +20,18 @@ class QuickWizard @Inject constructor(
init {
setData(JSONArray(sp.getString(R.string.key_quickwizard, "[]")))
+ setGuidsForOldEntries()
+ }
+
+ private fun setGuidsForOldEntries() {
+ // for migration purposes; guid is a new required property
+ for (i in 0 until storage.length()) {
+ val entry = QuickWizardEntry(injector).from(storage.get(i) as JSONObject, i)
+ if (entry.guid() == "") {
+ val guid = UUID.randomUUID().toString()
+ entry.storage.put("guid", guid)
+ }
+ }
}
fun getActive(): QuickWizardEntry? {
@@ -41,6 +55,43 @@ class QuickWizard @Inject constructor(
operator fun get(position: Int): QuickWizardEntry =
QuickWizardEntry(injector).from(storage.get(position) as JSONObject, position)
+ fun list(): ArrayList =
+ ArrayList().also {
+ for (i in 0 until size()) it.add(get(i))
+ }
+
+ fun get(guid: String): QuickWizardEntry? {
+ for (i in 0 until storage.length()) {
+ val entry = QuickWizardEntry(injector).from(storage.get(i) as JSONObject, i)
+ if (entry.guid() == guid) {
+ return entry
+ }
+ }
+ return null
+ }
+
+ fun move(from: Int, to: Int) {
+ //Log.i("QuickWizard", "moveItem: $from $to")
+ val fromEntry = storage[from] as JSONObject
+ storage.remove(from)
+ addToPos(to, fromEntry, storage)
+ save()
+ }
+
+ fun removePos(pos: Int, jsonObj: JSONObject?, jsonArr: JSONArray) {
+ for (i in jsonArr.length() downTo pos + 1) {
+ jsonArr.put(i, jsonArr[i - 1])
+ }
+ jsonArr.put(pos, jsonObj)
+ }
+
+ private fun addToPos(pos: Int, jsonObj: JSONObject?, jsonArr: JSONArray) {
+ for (i in jsonArr.length() downTo pos + 1) {
+ jsonArr.put(i, jsonArr[i - 1])
+ }
+ jsonArr.put(pos, jsonObj)
+ }
+
fun newEmptyItem(): QuickWizardEntry {
return QuickWizardEntry(injector)
}
@@ -57,4 +108,5 @@ class QuickWizard @Inject constructor(
storage.remove(position)
save()
}
+
}
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 79ff43bf66..c6dee8a660 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
@@ -19,6 +19,7 @@ import info.nightscout.androidaps.utils.JsonHelper.safeGetString
import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONException
import org.json.JSONObject
+import java.util.*
import javax.inject.Inject
class QuickWizardEntry @Inject constructor(private val injector: HasAndroidInjector) {
@@ -41,11 +42,26 @@ class QuickWizardEntry @Inject constructor(private val injector: HasAndroidInjec
const val NO = 1
private const val POSITIVE_ONLY = 2
private const val NEGATIVE_ONLY = 3
+ const val DEVICE_ALL = 0
+ const val DEVICE_PHONE = 1
+ const val DEVICE_WATCH = 2
+ const val DEFAULT = 0
+ const val CUSTOM = 1
}
init {
injector.androidInjector().inject(this)
- val emptyData = "{\"buttonText\":\"\",\"carbs\":0,\"validFrom\":0,\"validTo\":86340}"
+ val guid = UUID.randomUUID().toString()
+ val emptyData = """{
+ "guid": "$guid",
+ "buttonText": "",
+ "carbs": 0,
+ "validFrom": 0,
+ "validTo": 86340,
+ "device": "all",
+ "usePercentage": "default",
+ "percentage": 100
+ }""".trimMargin()
try {
storage = JSONObject(emptyData)
} catch (e: JSONException) {
@@ -55,6 +71,8 @@ class QuickWizardEntry @Inject constructor(private val injector: HasAndroidInjec
/*
{
+ guid: string,
+ device: string, // (phone, watch, all)
buttonText: "Meal",
carbs: 36,
validFrom: 8 * 60 * 60, // seconds from midnight
@@ -66,15 +84,18 @@ class QuickWizardEntry @Inject constructor(private val injector: HasAndroidInjec
useTrend: 0,
useSuperBolus: 0,
useTemptarget: 0
+ usePercentage: string, // default, custom
+ percentage: int,
}
*/
fun from(entry: JSONObject, position: Int): QuickWizardEntry {
+ // TODO set guid if missing for migration
storage = entry
this.position = position
return this
}
- fun isActive(): Boolean = profileFunction.secondsFromMidnight() >= validFrom() && profileFunction.secondsFromMidnight() <= validTo()
+ fun isActive(): Boolean = profileFunction.secondsFromMidnight() >= validFrom() && profileFunction.secondsFromMidnight() <= validTo() && forDevice(DEVICE_PHONE)
fun doCalc(profile: Profile, profileName: String, lastBG: GlucoseValue, _synchronized: Boolean): BolusWizard {
val dbRecord = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet()
@@ -119,10 +140,16 @@ class QuickWizardEntry @Inject constructor(private val injector: HasAndroidInjec
} else if (useTrend() == NEGATIVE_ONLY && glucoseStatus != null && glucoseStatus.shortAvgDelta < 0) {
trend = true
}
- val percentage = sp.getInt(R.string.key_boluswizard_percentage, 100)
+ val percentage = if (usePercentage() == DEFAULT) sp.getInt(R.string.key_boluswizard_percentage, 100) else percentage()
return BolusWizard(injector).doCalc(profile, profileName, tempTarget, carbs(), cob, bg, 0.0, percentage, true, useCOB() == YES, bolusIOB, basalIOB, superBolus, useTempTarget() == YES, trend, false, buttonText(), quickWizard = true) //tbc, ok if only quickwizard, but if other sources elsewhere use Sources.QuickWizard
}
+ fun guid(): String = safeGetString(storage, "guid", "")
+
+ fun device(): Int = safeGetInt(storage, "device", DEVICE_ALL)
+
+ fun forDevice(device: Int) = device() == device || device() == DEVICE_ALL
+
fun buttonText(): String = safeGetString(storage, "buttonText", "")
fun carbs(): Int = safeGetInt(storage, "carbs")
@@ -148,4 +175,8 @@ class QuickWizardEntry @Inject constructor(private val injector: HasAndroidInjec
fun useSuperBolus(): Int = safeGetInt(storage, "useSuperBolus", NO)
fun useTempTarget(): Int = safeGetInt(storage, "useTempTarget", NO)
-}
\ No newline at end of file
+
+ fun usePercentage(): Int = safeGetInt(storage, "usePercentage", DEFAULT)
+
+ fun percentage(): Int = safeGetInt(storage, "percentage", 100)
+}
diff --git a/app/src/main/java/info/nightscout/androidaps/widget/Widget.kt b/app/src/main/java/info/nightscout/androidaps/widget/Widget.kt
new file mode 100644
index 0000000000..b4a2cbcd29
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/widget/Widget.kt
@@ -0,0 +1,262 @@
+package info.nightscout.androidaps.widget
+
+import android.app.PendingIntent
+import android.appwidget.AppWidgetManager
+import android.appwidget.AppWidgetProvider
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.graphics.Color
+import android.graphics.Paint
+import android.os.Handler
+import android.os.HandlerThread
+import android.view.View
+import android.widget.RemoteViews
+import dagger.android.HasAndroidInjector
+import info.nightscout.androidaps.Constants
+import info.nightscout.androidaps.MainActivity
+import info.nightscout.androidaps.R
+import info.nightscout.androidaps.data.ProfileSealed
+import info.nightscout.androidaps.database.interfaces.end
+import info.nightscout.androidaps.extensions.directionToIcon
+import info.nightscout.androidaps.extensions.toVisibility
+import info.nightscout.androidaps.extensions.valueToUnitsString
+import info.nightscout.androidaps.interfaces.*
+import info.nightscout.androidaps.plugins.aps.openAPSSMB.DetermineBasalResultSMB
+import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
+import info.nightscout.androidaps.plugins.general.overview.OverviewData
+import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProvider
+import info.nightscout.androidaps.utils.DateUtil
+import info.nightscout.androidaps.utils.TrendCalculator
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.shared.logging.LTag
+import info.nightscout.shared.sharedPreferences.SP
+import java.util.*
+import javax.inject.Inject
+import kotlin.math.abs
+
+/**
+ * Implementation of App Widget functionality.
+ */
+class Widget : AppWidgetProvider() {
+
+ @Inject lateinit var profileFunction: ProfileFunction
+ @Inject lateinit var overviewData: OverviewData
+ @Inject lateinit var trendCalculator: TrendCalculator
+ @Inject lateinit var rh: ResourceHelper
+ @Inject lateinit var glucoseStatusProvider: GlucoseStatusProvider
+ @Inject lateinit var dateUtil: DateUtil
+ @Inject lateinit var aapsLogger: AAPSLogger
+ @Inject lateinit var activePlugin: ActivePlugin
+ @Inject lateinit var iobCobCalculator: IobCobCalculator
+ @Inject lateinit var loop: Loop
+ @Inject lateinit var config: Config
+ @Inject lateinit var sp: SP
+ @Inject lateinit var constraintChecker: ConstraintChecker
+
+ private var handler = Handler(HandlerThread(this::class.simpleName + "Handler").also { it.start() }.looper)
+ private val intentAction = "OpenApp"
+
+ override fun onReceive(context: Context, intent: Intent?) {
+ (context.applicationContext as HasAndroidInjector).androidInjector().inject(this)
+ super.onReceive(context, intent)
+ }
+
+ override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
+ // There may be multiple widgets active, so update all of them
+ for (appWidgetId in appWidgetIds) {
+ updateAppWidget(context, appWidgetManager, appWidgetId)
+ }
+ }
+
+ override fun onEnabled(context: Context) {
+ // Enter relevant functionality for when the first widget is created
+ }
+
+ override fun onDisabled(context: Context) {
+ // Enter relevant functionality for when the last widget is disabled
+ }
+
+ private fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int) {
+ aapsLogger.debug(LTag.WIDGET, "updateAppWidget called")
+
+ val views = RemoteViews(context.packageName, R.layout.widget_layout)
+ val alpha = sp.getInt(WidgetConfigureActivity.PREF_PREFIX_KEY + appWidgetId, WidgetConfigureActivity.DEFAULT_OPACITY)
+
+ // Create an Intent to launch MainActivity when clicked
+ val intent = Intent(context, MainActivity::class.java).also { it.action = intentAction }
+ val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
+ // Widgets allow click handlers to only launch pending intents
+ views.setOnClickPendingIntent(R.id.widget_layout, pendingIntent)
+ views.setInt(R.id.widget_layout, "setBackgroundColor", Color.argb(alpha, 0, 0, 0))
+
+ handler.post {
+ updateBg(views)
+ updateTemporaryBasal(views)
+ updateExtendedBolus(views)
+ updateIobCob(views)
+ updateTemporaryTarget(views)
+ updateProfile(views)
+ updateSensitivity(views)
+ // Instruct the widget manager to update the widget
+ appWidgetManager.updateAppWidget(appWidgetId, views)
+ }
+ }
+
+ private fun updateBg(views: RemoteViews) {
+ val units = profileFunction.getUnits()
+ views.setTextViewText(R.id.bg, overviewData.lastBg?.valueToUnitsString(units) ?: rh.gs(R.string.notavailable))
+ views.setTextColor(
+ R.id.bg, when {
+ overviewData.isLow -> rh.gc(R.color.widget_low)
+ overviewData.isHigh -> rh.gc(R.color.widget_high)
+ else -> rh.gc(R.color.widget_inrange)
+ }
+ )
+ views.setImageViewResource(R.id.arrow, trendCalculator.getTrendArrow(overviewData.lastBg).directionToIcon())
+ views.setInt(
+ R.id.arrow, "setColorFilter", when {
+ overviewData.isLow -> rh.gc(R.color.widget_low)
+ overviewData.isHigh -> rh.gc(R.color.widget_high)
+ else -> rh.gc(R.color.widget_inrange)
+ }
+ )
+
+ val glucoseStatus = glucoseStatusProvider.glucoseStatusData
+ if (glucoseStatus != null) {
+ views.setTextViewText(R.id.delta, Profile.toSignedUnitsString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units))
+ views.setTextViewText(R.id.avg_delta, Profile.toSignedUnitsString(glucoseStatus.shortAvgDelta, glucoseStatus.shortAvgDelta * Constants.MGDL_TO_MMOLL, units))
+ views.setTextViewText(R.id.long_avg_delta, Profile.toSignedUnitsString(glucoseStatus.longAvgDelta, glucoseStatus.longAvgDelta * Constants.MGDL_TO_MMOLL, units))
+ } else {
+ views.setTextViewText(R.id.delta, rh.gs(R.string.notavailable))
+ views.setTextViewText(R.id.avg_delta, rh.gs(R.string.notavailable))
+ views.setTextViewText(R.id.long_avg_delta, rh.gs(R.string.notavailable))
+ }
+
+ // strike through if BG is old
+ if (!overviewData.isActualBg) views.setInt(R.id.bg, "setPaintFlags", Paint.STRIKE_THRU_TEXT_FLAG or Paint.ANTI_ALIAS_FLAG)
+ else views.setInt(R.id.bg, "setPaintFlags", Paint.ANTI_ALIAS_FLAG)
+
+ views.setTextViewText(R.id.time_ago, dateUtil.minAgo(rh, overviewData.lastBg?.timestamp))
+ views.setTextViewText(R.id.time_ago_short, "(" + dateUtil.minAgoShort(overviewData.lastBg?.timestamp) + ")")
+ }
+
+ private fun updateTemporaryBasal(views: RemoteViews) {
+ views.setTextViewText(R.id.base_basal, overviewData.temporaryBasalText(iobCobCalculator))
+ views.setTextColor(R.id.base_basal, iobCobCalculator.getTempBasalIncludingConvertedExtended(dateUtil.now())?.let { rh.gc(R.color.widget_basal) }
+ ?: rh.gc(R.color.white))
+ views.setImageViewResource(R.id.base_basal_icon, overviewData.temporaryBasalIcon(iobCobCalculator))
+ }
+
+ private fun updateExtendedBolus(views: RemoteViews) {
+ val pump = activePlugin.activePump
+ views.setTextViewText(R.id.extended_bolus, overviewData.extendedBolusText(iobCobCalculator))
+ views.setViewVisibility(R.id.extended_layout, (iobCobCalculator.getExtendedBolus(dateUtil.now()) != null && !pump.isFakingTempsByExtendedBoluses).toVisibility())
+ }
+
+ private fun updateIobCob(views: RemoteViews) {
+ views.setTextViewText(R.id.iob, overviewData.iobText(iobCobCalculator))
+ // cob
+ var cobText = overviewData.cobInfo(iobCobCalculator).displayText(rh, dateUtil, isDev = false) ?: rh.gs(R.string.value_unavailable_short)
+
+ val constraintsProcessed = loop.lastRun?.constraintsProcessed
+ val lastRun = loop.lastRun
+ if (config.APS && constraintsProcessed != null && lastRun != null) {
+ if (constraintsProcessed.carbsReq > 0) {
+ //only display carbsreq when carbs have not been entered recently
+ if (overviewData.lastCarbsTime < lastRun.lastAPSRun) {
+ cobText += " | " + constraintsProcessed.carbsReq + " " + rh.gs(R.string.required)
+ }
+ }
+ }
+ views.setTextViewText(R.id.cob, cobText)
+ }
+
+ private fun updateTemporaryTarget(views: RemoteViews) {
+ val units = profileFunction.getUnits()
+ val tempTarget = overviewData.temporaryTarget
+ if (tempTarget != null) {
+ // this is crashing, use background as text for now
+ //views.setTextColor(R.id.temp_target, rh.gc(R.color.ribbonTextWarning))
+ //views.setInt(R.id.temp_target, "setBackgroundColor", rh.gc(R.color.ribbonWarning))
+ views.setTextColor(R.id.temp_target, rh.gc(R.color.widget_ribbonWarning))
+ views.setTextViewText(R.id.temp_target, Profile.toTargetRangeString(tempTarget.lowTarget, tempTarget.highTarget, GlucoseUnit.MGDL, units) + " " + dateUtil.untilString(tempTarget.end, rh))
+ } else {
+ // If the target is not the same as set in the profile then oref has overridden it
+ profileFunction.getProfile()?.let { profile ->
+ val targetUsed = loop.lastRun?.constraintsProcessed?.targetBG ?: 0.0
+
+ if (targetUsed != 0.0 && abs(profile.getTargetMgdl() - targetUsed) > 0.01) {
+ aapsLogger.debug("Adjusted target. Profile: ${profile.getTargetMgdl()} APS: $targetUsed")
+ views.setTextViewText(R.id.temp_target, Profile.toTargetRangeString(targetUsed, targetUsed, GlucoseUnit.MGDL, units))
+ // this is crashing, use background as text for now
+ //views.setTextColor(R.id.temp_target, rh.gc(R.color.ribbonTextWarning))
+ //views.setInt(R.id.temp_target, "setBackgroundResource", rh.gc(R.color.tempTargetBackground))
+ views.setTextColor(R.id.temp_target, rh.gc(R.color.widget_ribbonWarning))
+ } else {
+ // this is crashing, use background as text for now
+ //views.setTextColor(R.id.temp_target, rh.gc(R.color.ribbonTextDefault))
+ //views.setInt(R.id.temp_target, "setBackgroundColor", rh.gc(R.color.ribbonDefault))
+ views.setTextColor(R.id.temp_target, rh.gc(R.color.widget_ribbonTextDefault))
+ views.setTextViewText(R.id.temp_target, Profile.toTargetRangeString(profile.getTargetLowMgdl(), profile.getTargetHighMgdl(), GlucoseUnit.MGDL, units))
+ }
+ }
+ }
+ }
+
+ fun updateProfile(views: RemoteViews) {
+ val profileTextColor =
+ profileFunction.getProfile()?.let {
+ if (it is ProfileSealed.EPS) {
+ if (it.value.originalPercentage != 100 || it.value.originalTimeshift != 0L || it.value.originalDuration != 0L)
+ rh.gc(R.color.widget_ribbonWarning)
+ else rh.gc(R.color.widget_ribbonTextDefault)
+ } else if (it is ProfileSealed.PS) {
+ rh.gc(R.color.widget_ribbonTextDefault)
+ } else {
+ rh.gc(R.color.widget_ribbonTextDefault)
+ }
+ } ?: rh.gc(R.color.widget_ribbonCritical)
+
+ views.setTextViewText(R.id.active_profile, profileFunction.getProfileNameWithRemainingTime())
+ // this is crashing, use background as text for now
+ //views.setInt(R.id.active_profile, "setBackgroundColor", profileBackgroundColor)
+ //views.setTextColor(R.id.active_profile, profileTextColor)
+ views.setTextColor(R.id.active_profile, profileTextColor)
+ }
+
+ private fun updateSensitivity(views: RemoteViews) {
+ if (sp.getBoolean(R.string.key_openapsama_useautosens, false) && constraintChecker.isAutosensModeEnabled().value())
+ views.setImageViewResource(R.id.sensitivity_icon, R.drawable.ic_swap_vert_black_48dp_green)
+ else
+ views.setImageViewResource(R.id.sensitivity_icon, R.drawable.ic_x_swap_vert)
+ views.setTextViewText(R.id.sensitivity, overviewData.lastAutosensData(iobCobCalculator)?.let { autosensData ->
+ String.format(Locale.ENGLISH, "%.0f%%", autosensData.autosensResult.ratio * 100)
+ } ?: "")
+
+ // Show variable sensitivity
+ val request = loop.lastRun?.request
+ if (request is DetermineBasalResultSMB) {
+ val isfMgdl = profileFunction.getProfile()?.getIsfMgdl()
+ val variableSens = request.variableSens
+ if (variableSens != isfMgdl && variableSens != null && isfMgdl != null) {
+ views.setTextViewText(
+ R.id.variable_sensitivity,
+ String.format(
+ Locale.getDefault(), "%1$.1f→%2$.1f",
+ Profile.toUnits(isfMgdl, isfMgdl * Constants.MGDL_TO_MMOLL, profileFunction.getUnits()),
+ Profile.toUnits(variableSens, variableSens * Constants.MGDL_TO_MMOLL, profileFunction.getUnits())
+ )
+ )
+ views.setViewVisibility(R.id.variable_sensitivity, View.VISIBLE)
+ } else views.setViewVisibility(R.id.variable_sensitivity, View.GONE)
+ } else views.setViewVisibility(R.id.variable_sensitivity, View.GONE)
+ }
+}
+
+internal fun updateWidget(context: Context) {
+ context.sendBroadcast(Intent().also {
+ it.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, AppWidgetManager.getInstance(context).getAppWidgetIds(ComponentName(context, Widget::class.java)))
+ it.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
+ })
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/widget/WidgetConfigureActivity.kt b/app/src/main/java/info/nightscout/androidaps/widget/WidgetConfigureActivity.kt
new file mode 100644
index 0000000000..4647b86454
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/widget/WidgetConfigureActivity.kt
@@ -0,0 +1,78 @@
+package info.nightscout.androidaps.widget
+
+import android.appwidget.AppWidgetManager
+import android.content.Intent
+import android.os.Bundle
+import android.widget.SeekBar
+import dagger.android.DaggerActivity
+import info.nightscout.androidaps.databinding.WidgetConfigureBinding
+import info.nightscout.shared.sharedPreferences.SP
+import javax.inject.Inject
+
+/**
+ * The configuration screen for the [Widget] AppWidget.
+ */
+class WidgetConfigureActivity : DaggerActivity() {
+
+ @Inject lateinit var sp: SP
+
+ companion object {
+
+ @Suppress("PrivatePropertyName")
+ const val PREF_PREFIX_KEY = "appwidget_"
+ const val DEFAULT_OPACITY = 25
+ }
+
+ private var appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID
+ private var value = 0
+
+ private lateinit var binding: WidgetConfigureBinding
+
+ public override fun onCreate(icicle: Bundle?) {
+ super.onCreate(icicle)
+
+ // Set the result to CANCELED. This will cause the widget host to cancel
+ // out of the widget placement if the user presses the back button.
+ setResult(RESULT_CANCELED)
+
+ binding = WidgetConfigureBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ binding.seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
+ override fun onStopTrackingTouch(seekBar: SeekBar) {
+ // Make sure we pass back the original appWidgetId
+ val resultValue = Intent()
+ resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
+ setResult(RESULT_OK, resultValue)
+ finish()
+ }
+
+ override fun onStartTrackingTouch(seekBar: SeekBar) {}
+ override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
+ value = progress
+ saveTitlePref(appWidgetId, value)
+ updateWidget(this@WidgetConfigureActivity)
+ }
+ })
+
+ // Find the widget id from the intent.
+ appWidgetId = intent.extras?.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID) ?: AppWidgetManager.INVALID_APPWIDGET_ID
+
+ // If this activity was started with an intent without an app widget ID, finish with an error.
+ if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
+ finish()
+ return
+ }
+
+ binding.seekBar.progress = loadTitlePref(appWidgetId)
+ }
+
+ // Write the prefix to the SharedPreferences object for this widget
+ fun saveTitlePref(appWidgetId: Int, value: Int) {
+ sp.putInt(PREF_PREFIX_KEY + appWidgetId, value)
+ }
+
+ // Read the prefix from the SharedPreferences object for this widget.
+ // If there is no preference saved, get the default from a resource
+ private fun loadTitlePref(appWidgetId: Int): Int = sp.getInt(PREF_PREFIX_KEY + appWidgetId, 25)
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/workflow/CalculationWorkflow.kt b/app/src/main/java/info/nightscout/androidaps/workflow/CalculationWorkflow.kt
new file mode 100644
index 0000000000..ddae280577
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/workflow/CalculationWorkflow.kt
@@ -0,0 +1,270 @@
+package info.nightscout.androidaps.workflow
+
+import android.content.Context
+import android.os.SystemClock
+import androidx.work.*
+import dagger.android.HasAndroidInjector
+import info.nightscout.androidaps.R
+import info.nightscout.androidaps.events.Event
+import info.nightscout.androidaps.events.EventAppInitialized
+import info.nightscout.androidaps.events.EventOfflineChange
+import info.nightscout.androidaps.events.EventPreferenceChange
+import info.nightscout.androidaps.events.EventTherapyEventChange
+import info.nightscout.androidaps.interfaces.ActivePlugin
+import info.nightscout.androidaps.interfaces.IobCobCalculator
+import info.nightscout.androidaps.plugins.bus.RxBus
+import info.nightscout.androidaps.plugins.general.overview.OverviewData
+import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin
+import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobOref1Worker
+import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobOrefWorker
+import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventNewHistoryData
+import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref1Plugin
+import info.nightscout.androidaps.receivers.DataWorker
+import info.nightscout.androidaps.utils.DateUtil
+import info.nightscout.androidaps.utils.FabricPrivacy
+import info.nightscout.androidaps.interfaces.ResourceHelper
+import info.nightscout.androidaps.utils.rx.AapsSchedulers
+import info.nightscout.shared.logging.AAPSLogger
+import info.nightscout.shared.logging.LTag
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.kotlin.plusAssign
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class CalculationWorkflow @Inject constructor(
+ aapsSchedulers: AapsSchedulers,
+ rh: ResourceHelper,
+ rxBus: RxBus,
+ private val context: Context,
+ private val injector: HasAndroidInjector,
+ private val aapsLogger: AAPSLogger,
+ private val fabricPrivacy: FabricPrivacy,
+ private val dateUtil: DateUtil,
+ private val sensitivityOref1Plugin: SensitivityOref1Plugin,
+ private val dataWorker: DataWorker,
+ private val activePlugin: ActivePlugin
+) {
+
+ companion object {
+
+ const val MAIN_CALCULATION = "calculation"
+ const val HISTORY_CALCULATION = "history_calculation"
+ const val JOB = "job"
+ }
+
+ private var disposable: CompositeDisposable = CompositeDisposable()
+
+ private val iobCobCalculator: IobCobCalculator
+ get() = activePlugin.activeIobCobCalculator // cross-dependency CalculationWorkflow x IobCobCalculator
+ private val overviewData: OverviewData
+ get() = (iobCobCalculator as IobCobCalculatorPlugin).overviewData
+
+ enum class ProgressData(val pass: Int, val percentOfTotal: Int) {
+ PREPARE_BASAL_DATA(0, 5),
+ PREPARE_TEMPORARY_TARGET_DATA(1, 5),
+ PREPARE_TREATMENTS_DATA(2, 5),
+ IOB_COB_OREF(3, 75),
+ PREPARE_IOB_AUTOSENS_DATA(4, 10);
+
+ fun finalPercent(progress: Int): Int {
+ var total = 0
+ for (i in values()) if (i.pass < pass) total += i.percentOfTotal
+ total += (percentOfTotal.toDouble() * progress / 100.0).toInt()
+ return total
+ }
+ }
+
+ init {
+ // Verify definition
+ var sumPercent = 0
+ for (pass in ProgressData.values()) sumPercent += pass.percentOfTotal
+ require(sumPercent == 100)
+
+ disposable += rxBus
+ .toObservable(EventTherapyEventChange::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({ runOnEventTherapyEventChange() }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventOfflineChange::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({ runOnEventTherapyEventChange() }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventPreferenceChange::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe({ event ->
+ if (event.isChanged(rh, R.string.key_units)) {
+ overviewData.reset()
+ rxBus.send(EventNewHistoryData(0, false))
+ }
+ if (event.isChanged(rh, R.string.key_rangetodisplay)) {
+ overviewData.initRange()
+ runOnScaleChanged()
+ rxBus.send(EventNewHistoryData(0, false))
+ }
+ }, fabricPrivacy::logException)
+ disposable += rxBus
+ .toObservable(EventAppInitialized::class.java)
+ .observeOn(aapsSchedulers.io)
+ .subscribe(
+ {
+ runCalculation(
+ MAIN_CALCULATION,
+ iobCobCalculator,
+ overviewData,
+ "onEventAppInitialized",
+ System.currentTimeMillis(),
+ bgDataReload = true,
+ limitDataToOldestAvailable = true,
+ cause = it,
+ runLoop = true
+ )
+ },
+ fabricPrivacy::logException
+ )
+
+ }
+
+ fun stopCalculation(job: String, from: String) {
+ aapsLogger.debug(LTag.AUTOSENS, "Stopping calculation thread: $from")
+ WorkManager.getInstance(context).cancelUniqueWork(job)
+ val workStatus = WorkManager.getInstance(context).getWorkInfosForUniqueWork(job).get()
+ while (workStatus.size >= 1 && workStatus[0].state == WorkInfo.State.RUNNING)
+ SystemClock.sleep(100)
+ aapsLogger.debug(LTag.AUTOSENS, "Calculation thread stopped: $from")
+ }
+
+ fun runCalculation(
+ job: String,
+ iobCobCalculator: IobCobCalculator,
+ overviewData: OverviewData,
+ from: String,
+ end: Long,
+ bgDataReload: Boolean,
+ limitDataToOldestAvailable: Boolean,
+ cause: Event?,
+ runLoop: Boolean
+ ) {
+ aapsLogger.debug(LTag.AUTOSENS, "Starting calculation worker: $from to ${dateUtil.dateAndTimeAndSecondsString(end)}")
+
+ WorkManager.getInstance(context)
+ .beginUniqueWork(
+ job, ExistingWorkPolicy.REPLACE,
+ if (bgDataReload) OneTimeWorkRequest.Builder(LoadBgDataWorker::class.java).setInputData(dataWorker.storeInputData(LoadBgDataWorker.LoadBgData(iobCobCalculator, end))).build()
+ else OneTimeWorkRequest.Builder(DummyWorker::class.java).build()
+ )
+ .then(
+ OneTimeWorkRequest.Builder(PrepareBucketedDataWorker::class.java)
+ .setInputData(dataWorker.storeInputData(PrepareBucketedDataWorker.PrepareBucketedData(iobCobCalculator, overviewData)))
+ .build()
+ )
+ .then(
+ OneTimeWorkRequest.Builder(PrepareBgDataWorker::class.java)
+ .setInputData(dataWorker.storeInputData(PrepareBgDataWorker.PrepareBgData(iobCobCalculator, overviewData)))
+ .build()
+ )
+ .then(
+ OneTimeWorkRequest.Builder(UpdateGraphWorker::class.java)
+ .setInputData(Data.Builder().putString(JOB, job).build())
+ .build()
+ )
+ .then(
+ OneTimeWorkRequest.Builder(PrepareTreatmentsDataWorker::class.java)
+ .setInputData(dataWorker.storeInputData(PrepareTreatmentsDataWorker.PrepareTreatmentsData(overviewData)))
+ .build()
+ )
+ .then(
+ OneTimeWorkRequest.Builder(PrepareBasalDataWorker::class.java)
+ .setInputData(dataWorker.storeInputData(PrepareBasalDataWorker.PrepareBasalData(iobCobCalculator, overviewData)))
+ .build()
+ )
+ .then(
+ OneTimeWorkRequest.Builder(PrepareTemporaryTargetDataWorker::class.java)
+ .setInputData(dataWorker.storeInputData(PrepareTemporaryTargetDataWorker.PrepareTemporaryTargetData(overviewData)))
+ .build()
+ )
+ .then(
+ OneTimeWorkRequest.Builder(UpdateGraphWorker::class.java)
+ .setInputData(Data.Builder().putString(JOB, job).build())
+ .build()
+ )
+ .then(
+ if (sensitivityOref1Plugin.isEnabled())
+ OneTimeWorkRequest.Builder(IobCobOref1Worker::class.java)
+ .setInputData(dataWorker.storeInputData(IobCobOref1Worker.IobCobOref1WorkerData(injector, iobCobCalculator, from, end, limitDataToOldestAvailable, cause)))
+ .build()
+ else
+ OneTimeWorkRequest.Builder(IobCobOrefWorker::class.java)
+ .setInputData(dataWorker.storeInputData(IobCobOrefWorker.IobCobOrefWorkerData(injector, iobCobCalculator, from, end, limitDataToOldestAvailable, cause)))
+ .build()
+ )
+ .then(OneTimeWorkRequest.Builder(UpdateIobCobSensWorker::class.java).build())
+ .then(
+ OneTimeWorkRequest.Builder(PrepareIobAutosensGraphDataWorker::class.java)
+ .setInputData(dataWorker.storeInputData(PrepareIobAutosensGraphDataWorker.PrepareIobAutosensData(iobCobCalculator, overviewData)))
+ .build()
+ )
+ .then(
+ OneTimeWorkRequest.Builder(UpdateGraphWorker::class.java)
+ .setInputData(Data.Builder().putString(JOB, job).build())
+ .build()
+ )
+ .then(
+ runLoop,
+ OneTimeWorkRequest.Builder(InvokeLoopWorker::class.java)
+ .setInputData(dataWorker.storeInputData(InvokeLoopWorker.InvokeLoopData(cause)))
+ .build()
+ )
+ .then(
+ runLoop,
+ OneTimeWorkRequest.Builder(PreparePredictionsWorker::class.java)
+ .setInputData(dataWorker.storeInputData(PreparePredictionsWorker.PreparePredictionsData(overviewData)))
+ .build()
+ )
+ .then(
+ runLoop, OneTimeWorkRequest.Builder(UpdateGraphWorker::class.java)
+ .setInputData(Data.Builder().putString(JOB, job).build())
+ .build()
+ )
+ .enqueue()
+ }
+
+ fun WorkContinuation.then(shouldAdd: Boolean, work: OneTimeWorkRequest): WorkContinuation =
+ if (shouldAdd) then(work) else this
+
+ private fun runOnEventTherapyEventChange() {
+ WorkManager.getInstance(context)
+ .beginUniqueWork(
+ MAIN_CALCULATION, ExistingWorkPolicy.APPEND,
+ OneTimeWorkRequest.Builder(PrepareTreatmentsDataWorker::class.java)
+ .setInputData(dataWorker.storeInputData(PrepareTreatmentsDataWorker.PrepareTreatmentsData(overviewData)))
+ .build()
+ )
+ .then(
+ OneTimeWorkRequest.Builder(UpdateGraphWorker::class.java)
+ .build()
+ )
+ .enqueue()
+
+ }
+
+ private fun runOnScaleChanged() {
+ WorkManager.getInstance(context)
+ .beginUniqueWork(
+ MAIN_CALCULATION, ExistingWorkPolicy.APPEND,
+ OneTimeWorkRequest.Builder(PrepareBucketedDataWorker::class.java)
+ .setInputData(dataWorker.storeInputData(PrepareBucketedDataWorker.PrepareBucketedData(iobCobCalculator, overviewData)))
+ .build()
+ )
+ .then(
+ OneTimeWorkRequest.Builder(PrepareBgDataWorker::class.java)
+ .setInputData(dataWorker.storeInputData(PrepareBgDataWorker.PrepareBgData(iobCobCalculator, overviewData)))
+ .build()
+ )
+ .then(
+ OneTimeWorkRequest.Builder(UpdateGraphWorker::class.java)
+ .build()
+ )
+ .enqueue()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/workflow/DummyWorker.kt b/app/src/main/java/info/nightscout/androidaps/workflow/DummyWorker.kt
new file mode 100644
index 0000000000..fd30c54dc6
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/workflow/DummyWorker.kt
@@ -0,0 +1,13 @@
+package info.nightscout.androidaps.workflow
+
+import android.content.Context
+import androidx.work.Worker
+import androidx.work.WorkerParameters
+
+class DummyWorker(
+ context: Context,
+ params: WorkerParameters
+) : Worker(context, params) {
+
+ override fun doWork(): Result = Result.success()
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/workflow/InvokeLoopWorker.kt b/app/src/main/java/info/nightscout/androidaps/workflow/InvokeLoopWorker.kt
new file mode 100644
index 0000000000..b6e5fb910d
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/workflow/InvokeLoopWorker.kt
@@ -0,0 +1,51 @@
+package info.nightscout.androidaps.workflow
+
+import android.content.Context
+import androidx.work.Worker
+import androidx.work.WorkerParameters
+import androidx.work.workDataOf
+import dagger.android.HasAndroidInjector
+import info.nightscout.androidaps.events.Event
+import info.nightscout.androidaps.events.EventNewBG
+import info.nightscout.androidaps.interfaces.IobCobCalculator
+import info.nightscout.androidaps.interfaces.Loop
+import info.nightscout.androidaps.receivers.DataWorker
+import javax.inject.Inject
+
+class InvokeLoopWorker(
+ context: Context,
+ params: WorkerParameters
+) : Worker(context, params) {
+
+ @Inject lateinit var dataWorker: DataWorker
+ @Inject lateinit var iobCobCalculator: IobCobCalculator
+ @Inject lateinit var loop: Loop
+
+ init {
+ (context.applicationContext as HasAndroidInjector).androidInjector().inject(this)
+ }
+
+ class InvokeLoopData(
+ val cause: Event?
+ )
+
+ /*
+ This method is triggered once autosens calculation has completed, so the LoopPlugin
+ has current data to work with. However, autosens calculation can be triggered by multiple
+ sources and currently only a new BG should trigger a loop run. Hence we return early if
+ the event causing the calculation is not EventNewBg.
+
+ */
+ override fun doWork(): Result {
+
+ val data = dataWorker.pickupObject(inputData.getLong(DataWorker.STORE_KEY, -1)) as InvokeLoopData?
+ ?: return Result.failure(workDataOf("Error" to "missing input data"))
+
+ if (data.cause !is EventNewBG) return Result.success(workDataOf("Result" to "no calculation needed"))
+ val glucoseValue = iobCobCalculator.ads.actualBg() ?: return Result.success(workDataOf("Result" to "bg outdated"))
+ if (glucoseValue.timestamp <= loop.lastBgTriggeredRun) return Result.success(workDataOf("Result" to "already looped with that value"))
+ loop.lastBgTriggeredRun = glucoseValue.timestamp
+ loop.invoke("Calculation for $glucoseValue", true)
+ return Result.success()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/workflow/LoadBgDataWorker.kt b/app/src/main/java/info/nightscout/androidaps/workflow/LoadBgDataWorker.kt
new file mode 100644
index 0000000000..67e8290822
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/workflow/LoadBgDataWorker.kt
@@ -0,0 +1,45 @@
+package info.nightscout.androidaps.workflow
+
+import android.content.Context
+import androidx.work.Worker
+import androidx.work.WorkerParameters
+import androidx.work.workDataOf
+import dagger.android.HasAndroidInjector
+import info.nightscout.androidaps.database.AppRepository
+import info.nightscout.androidaps.interfaces.IobCobCalculator
+import info.nightscout.androidaps.plugins.bus.RxBus
+import info.nightscout.androidaps.receivers.DataWorker
+import info.nightscout.androidaps.utils.DateUtil
+import info.nightscout.shared.logging.AAPSLogger
+import javax.inject.Inject
+
+class LoadBgDataWorker(
+ context: Context,
+ params: WorkerParameters
+) : Worker(context, params) {
+
+ @Inject lateinit var dataWorker: DataWorker
+ @Inject lateinit var aapsLogger: AAPSLogger
+ @Inject lateinit var dateUtil: DateUtil
+ @Inject lateinit var rxBus: RxBus
+ @Inject lateinit var repository: AppRepository
+
+ init {
+ (context.applicationContext as HasAndroidInjector).androidInjector().inject(this)
+ }
+
+ class LoadBgData(
+ val iobCobCalculator: IobCobCalculator,
+ val end: Long
+ )
+
+ override fun doWork(): Result {
+
+ val data = dataWorker.pickupObject(inputData.getLong(DataWorker.STORE_KEY, -1)) as LoadBgData?
+ ?: return Result.failure(workDataOf("Error" to "missing input data"))
+
+ data.iobCobCalculator.ads.loadBgData(data.end, repository, aapsLogger, dateUtil, rxBus)
+ data.iobCobCalculator.clearCache()
+ return Result.success()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/workflow/PrepareBasalDataWorker.kt b/app/src/main/java/info/nightscout/androidaps/workflow/PrepareBasalDataWorker.kt
new file mode 100644
index 0000000000..45f8bd4e0c
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/workflow/PrepareBasalDataWorker.kt
@@ -0,0 +1,144 @@
+package info.nightscout.androidaps.workflow
+
+import android.content.Context
+import android.graphics.DashPathEffect
+import android.graphics.Paint
+import androidx.work.Worker
+import androidx.work.WorkerParameters
+import androidx.work.workDataOf
+import com.jjoe64.graphview.series.LineGraphSeries
+import dagger.android.HasAndroidInjector
+import info.nightscout.androidaps.R
+import info.nightscout.androidaps.interfaces.IobCobCalculator
+import info.nightscout.androidaps.interfaces.ProfileFunction
+import info.nightscout.androidaps.plugins.bus.RxBus
+import info.nightscout.androidaps.plugins.general.overview.OverviewData
+import info.nightscout.androidaps.plugins.general.overview.graphExtensions.ScaledDataPoint
+import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress
+import info.nightscout.androidaps.receivers.DataWorker
+import info.nightscout.androidaps.interfaces.ResourceHelper
+import java.util.ArrayList
+import javax.inject.Inject
+import kotlin.math.max
+
+class PrepareBasalDataWorker(
+ context: Context,
+ params: WorkerParameters
+) : Worker(context, params) {
+
+ @Inject lateinit var dataWorker: DataWorker
+ @Inject lateinit var profileFunction: ProfileFunction
+ @Inject lateinit var rh: ResourceHelper
+ @Inject lateinit var rxBus: RxBus
+ var ctx: Context
+ init {
+ (context.applicationContext as HasAndroidInjector).androidInjector().inject(this)
+ ctx = rh.getThemedCtx(context)
+ }
+
+ class PrepareBasalData(
+ val iobCobCalculator: IobCobCalculator, // cannot be injected : HistoryBrowser uses different instance
+ val overviewData: OverviewData
+ )
+
+ override fun doWork(): Result {
+
+ val data = dataWorker.pickupObject(inputData.getLong(DataWorker.STORE_KEY, -1)) as PrepareBasalData?
+ ?: return Result.failure(workDataOf("Error" to "missing input data"))
+
+ rxBus.send(EventIobCalculationProgress(CalculationWorkflow.ProgressData.PREPARE_BASAL_DATA, 0, null))
+ val baseBasalArray: MutableList = ArrayList()
+ val tempBasalArray: MutableList = ArrayList()
+ val basalLineArray: MutableList = ArrayList()
+ val absoluteBasalLineArray: MutableList = ArrayList()
+ var lastLineBasal = 0.0
+ var lastAbsoluteLineBasal = -1.0
+ var lastBaseBasal = 0.0
+ var lastTempBasal = 0.0
+ var time = data.overviewData.fromTime
+ while (time < data.overviewData.toTime) {
+ val progress = (time - data.overviewData.fromTime).toDouble() / (data.overviewData.toTime - data.overviewData.fromTime) * 100.0
+ rxBus.send(EventIobCalculationProgress(CalculationWorkflow.ProgressData.PREPARE_BASAL_DATA, progress.toInt(), null))
+ val profile = profileFunction.getProfile(time)
+ if (profile == null) {
+ time += 60 * 1000L
+ continue
+ }
+ val basalData = data.iobCobCalculator.getBasalData(profile, time)
+ val baseBasalValue = basalData.basal
+ var absoluteLineValue = baseBasalValue
+ var tempBasalValue = 0.0
+ var basal = 0.0
+ if (basalData.isTempBasalRunning) {
+ tempBasalValue = basalData.tempBasalAbsolute
+ absoluteLineValue = tempBasalValue
+ if (tempBasalValue != lastTempBasal) {
+ tempBasalArray.add(ScaledDataPoint(time, lastTempBasal, data.overviewData.basalScale))
+ tempBasalArray.add(ScaledDataPoint(time, tempBasalValue.also { basal = it }, data.overviewData.basalScale))
+ }
+ if (lastBaseBasal != 0.0) {
+ baseBasalArray.add(ScaledDataPoint(time, lastBaseBasal, data.overviewData.basalScale))
+ baseBasalArray.add(ScaledDataPoint(time, 0.0, data.overviewData.basalScale))
+ lastBaseBasal = 0.0
+ }
+ } else {
+ if (baseBasalValue != lastBaseBasal) {
+ baseBasalArray.add(ScaledDataPoint(time, lastBaseBasal, data.overviewData.basalScale))
+ baseBasalArray.add(ScaledDataPoint(time, baseBasalValue.also { basal = it }, data.overviewData.basalScale))
+ lastBaseBasal = baseBasalValue
+ }
+ if (lastTempBasal != 0.0) {
+ tempBasalArray.add(ScaledDataPoint(time, lastTempBasal, data.overviewData.basalScale))
+ tempBasalArray.add(ScaledDataPoint(time, 0.0, data.overviewData.basalScale))
+ }
+ }
+ if (baseBasalValue != lastLineBasal) {
+ basalLineArray.add(ScaledDataPoint(time, lastLineBasal, data.overviewData.basalScale))
+ basalLineArray.add(ScaledDataPoint(time, baseBasalValue, data.overviewData.basalScale))
+ }
+ if (absoluteLineValue != lastAbsoluteLineBasal) {
+ absoluteBasalLineArray.add(ScaledDataPoint(time, lastAbsoluteLineBasal, data.overviewData.basalScale))
+ absoluteBasalLineArray.add(ScaledDataPoint(time, basal, data.overviewData.basalScale))
+ }
+ lastAbsoluteLineBasal = absoluteLineValue
+ lastLineBasal = baseBasalValue
+ lastTempBasal = tempBasalValue
+ time += 60 * 1000L
+ }
+
+ // final points
+ basalLineArray.add(ScaledDataPoint(data.overviewData.toTime, lastLineBasal, data.overviewData.basalScale))
+ baseBasalArray.add(ScaledDataPoint(data.overviewData.toTime, lastBaseBasal, data.overviewData.basalScale))
+ tempBasalArray.add(ScaledDataPoint(data.overviewData.toTime, lastTempBasal, data.overviewData.basalScale))
+ absoluteBasalLineArray.add(ScaledDataPoint(data.overviewData.toTime, lastAbsoluteLineBasal, data.overviewData.basalScale))
+
+ // create series
+ data.overviewData.baseBasalGraphSeries = LineGraphSeries(Array(baseBasalArray.size) { i -> baseBasalArray[i] }).also {
+ it.isDrawBackground = true
+ it.backgroundColor = rh.gac(ctx, R.attr.baseBasalColor )
+ it.thickness = 0
+ }
+ data.overviewData.tempBasalGraphSeries = LineGraphSeries(Array(tempBasalArray.size) { i -> tempBasalArray[i] }).also {
+ it.isDrawBackground = true
+ it.backgroundColor = rh.gac(ctx, R.attr.tempBasalColor )
+ it.thickness = 0
+ }
+ data.overviewData.basalLineGraphSeries = LineGraphSeries(Array(basalLineArray.size) { i -> basalLineArray[i] }).also {
+ it.setCustomPaint(Paint().also { paint ->
+ paint.style = Paint.Style.STROKE
+ paint.strokeWidth = rh.getDisplayMetrics().scaledDensity * 2
+ paint.pathEffect = DashPathEffect(floatArrayOf(2f, 4f), 0f)
+ paint.color = rh.gac(ctx, R.attr.basal )
+ })
+ }
+ data.overviewData.absoluteBasalGraphSeries = LineGraphSeries(Array(absoluteBasalLineArray.size) { i -> absoluteBasalLineArray[i] }).also {
+ it.setCustomPaint(Paint().also { absolutePaint ->
+ absolutePaint.style = Paint.Style.STROKE
+ absolutePaint.strokeWidth = rh.getDisplayMetrics().scaledDensity * 2
+ absolutePaint.color =rh.gac(ctx, R.attr.basal )
+ })
+ }
+ rxBus.send(EventIobCalculationProgress(CalculationWorkflow.ProgressData.PREPARE_BASAL_DATA, 100, null))
+ return Result.success()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/workflow/PrepareBgDataWorker.kt b/app/src/main/java/info/nightscout/androidaps/workflow/PrepareBgDataWorker.kt
new file mode 100644
index 0000000000..16ad858693
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/workflow/PrepareBgDataWorker.kt
@@ -0,0 +1,67 @@
+package info.nightscout.androidaps.workflow
+
+import android.content.Context
+import androidx.work.Worker
+import androidx.work.WorkerParameters
+import androidx.work.workDataOf
+import dagger.android.HasAndroidInjector
+import info.nightscout.androidaps.database.AppRepository
+import info.nightscout.androidaps.interfaces.GlucoseUnit
+import info.nightscout.androidaps.interfaces.IobCobCalculator
+import info.nightscout.androidaps.interfaces.Profile
+import info.nightscout.androidaps.interfaces.ProfileFunction
+import info.nightscout.androidaps.plugins.general.overview.OverviewData
+import info.nightscout.androidaps.plugins.general.overview.graphExtensions.DataPointWithLabelInterface
+import info.nightscout.androidaps.plugins.general.overview.graphExtensions.GlucoseValueDataPoint
+import info.nightscout.androidaps.plugins.general.overview.graphExtensions.PointsWithLabelGraphSeries
+import info.nightscout.androidaps.receivers.DataWorker
+import info.nightscout.androidaps.utils.DefaultValueHelper
+import info.nightscout.androidaps.utils.Round
+import info.nightscout.androidaps.interfaces.ResourceHelper
+import java.util.ArrayList
+import javax.inject.Inject
+
+class PrepareBgDataWorker(
+ context: Context,
+ params: WorkerParameters
+) : Worker(context, params) {
+
+ @Inject lateinit var dataWorker: DataWorker
+ @Inject lateinit var profileFunction: ProfileFunction
+ @Inject lateinit var rh: ResourceHelper
+ @Inject lateinit var defaultValueHelper: DefaultValueHelper
+ @Inject lateinit var repository: AppRepository
+
+ init {
+ (context.applicationContext as HasAndroidInjector).androidInjector().inject(this)
+ }
+
+ class PrepareBgData(
+ val iobCobCalculator: IobCobCalculator, // cannot be injected : HistoryBrowser uses different instance
+ val overviewData: OverviewData
+ )
+
+ override fun doWork(): Result {
+
+ val data = dataWorker.pickupObject(inputData.getLong(DataWorker.STORE_KEY, -1)) as PrepareBgData?
+ ?: return Result.failure(workDataOf("Error" to "missing input data"))
+
+ data.overviewData.maxBgValue = Double.MIN_VALUE
+ data.overviewData.bgReadingsArray = repository.compatGetBgReadingsDataFromTime(data.overviewData.fromTime, data.overviewData.toTime, false).blockingGet()
+ val bgListArray: MutableList = ArrayList()
+ for (bg in data.overviewData.bgReadingsArray) {
+ if (bg.timestamp < data.overviewData.fromTime || bg.timestamp > data.overviewData.toTime) continue
+ if (bg.value > data.overviewData.maxBgValue) data.overviewData.maxBgValue = bg.value
+ bgListArray.add(GlucoseValueDataPoint(bg, defaultValueHelper, profileFunction, rh))
+ }
+ bgListArray.sortWith { o1: DataPointWithLabelInterface, o2: DataPointWithLabelInterface -> o1.x.compareTo(o2.x) }
+ data.overviewData.bgReadingGraphSeries = PointsWithLabelGraphSeries(Array(bgListArray.size) { i -> bgListArray[i] })
+ data.overviewData.maxBgValue = Profile.fromMgdlToUnits(data.overviewData.maxBgValue, profileFunction.getUnits())
+ if (defaultValueHelper.determineHighLine() > data.overviewData.maxBgValue) data.overviewData.maxBgValue = defaultValueHelper.determineHighLine()
+ data.overviewData.maxBgValue = addUpperChartMargin(data.overviewData.maxBgValue)
+ return Result.success()
+ }
+
+ private fun addUpperChartMargin(maxBgValue: Double) =
+ if (profileFunction.getUnits() == GlucoseUnit.MGDL) Round.roundTo(maxBgValue, 40.0) + 80 else Round.roundTo(maxBgValue, 2.0) + 4
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/workflow/PrepareBucketedDataWorker.kt b/app/src/main/java/info/nightscout/androidaps/workflow/PrepareBucketedDataWorker.kt
new file mode 100644
index 0000000000..db2d6af09d
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/workflow/PrepareBucketedDataWorker.kt
@@ -0,0 +1,57 @@
+package info.nightscout.androidaps.workflow
+
+import android.content.Context
+import androidx.work.Worker
+import androidx.work.WorkerParameters
+import androidx.work.workDataOf
+import dagger.android.HasAndroidInjector
+import info.nightscout.androidaps.interfaces.IobCobCalculator
+import info.nightscout.androidaps.interfaces.ProfileFunction
+import info.nightscout.androidaps.plugins.general.overview.OverviewData
+import info.nightscout.androidaps.plugins.general.overview.graphExtensions.DataPointWithLabelInterface
+import info.nightscout.androidaps.plugins.general.overview.graphExtensions.InMemoryGlucoseValueDataPoint
+import info.nightscout.androidaps.plugins.general.overview.graphExtensions.PointsWithLabelGraphSeries
+import info.nightscout.androidaps.receivers.DataWorker
+import info.nightscout.androidaps.interfaces.ResourceHelper
+import info.nightscout.shared.logging.AAPSLogger
+import javax.inject.Inject
+
+class PrepareBucketedDataWorker(
+ context: Context,
+ params: WorkerParameters
+) : Worker(context, params) {
+
+ @Inject lateinit var dataWorker: DataWorker
+ @Inject lateinit var profileFunction: ProfileFunction
+ @Inject lateinit var rh: ResourceHelper
+ @Inject lateinit var aapsLogger: AAPSLogger
+
+ init {
+ (context.applicationContext as HasAndroidInjector).androidInjector().inject(this)
+ }
+
+ class PrepareBucketedData(
+ val iobCobCalculator: IobCobCalculator, // cannot be injected : HistoryBrowser uses different instance
+ val overviewData: OverviewData
+ )
+
+ override fun doWork(): Result {
+
+ val data = dataWorker.pickupObject(inputData.getLong(DataWorker.STORE_KEY, -1)) as PrepareBucketedData?
+ ?: return Result.failure(workDataOf("Error" to "missing input data"))
+
+ val bucketedData = data.iobCobCalculator.ads.getBucketedDataTableCopy() ?: return Result.success()
+ if (bucketedData.isEmpty()) {
+ aapsLogger.debug("No bucketed data.")
+ return Result.success()
+ }
+ val bucketedListArray: MutableList