diff --git a/app/src/main/java/info/nightscout/androidaps/db/CompatDBHelper.kt b/app/src/main/java/info/nightscout/androidaps/db/CompatDBHelper.kt index 3aa64ddee0..2bb83ef910 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/CompatDBHelper.kt +++ b/app/src/main/java/info/nightscout/androidaps/db/CompatDBHelper.kt @@ -75,11 +75,11 @@ class CompatDBHelper @Inject constructor( rxBus.send(EventFoodDatabaseChanged()) } it.filterIsInstance().firstOrNull()?.let { - aapsLogger.debug(LTag.DATABASE, "Firing EventProfileNeedsUpdate") + aapsLogger.debug(LTag.DATABASE, "Firing EventProfileSwitchChanged") rxBus.send(EventProfileSwitchChanged()) } it.filterIsInstance().firstOrNull()?.let { - aapsLogger.debug(LTag.DATABASE, "Firing EventProfileNeedsUpdate") + aapsLogger.debug(LTag.DATABASE, "Firing EventProfileSwitchChanged") rxBus.send(EventProfileSwitchChanged()) } } diff --git a/app/src/main/java/info/nightscout/androidaps/historyBrowser/HistoryBrowseActivity.kt b/app/src/main/java/info/nightscout/androidaps/historyBrowser/HistoryBrowseActivity.kt index a32bf3089d..1abda3295f 100644 --- a/app/src/main/java/info/nightscout/androidaps/historyBrowser/HistoryBrowseActivity.kt +++ b/app/src/main/java/info/nightscout/androidaps/historyBrowser/HistoryBrowseActivity.kt @@ -279,7 +279,7 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() { if (destroyed) return@launch binding.date.text = dateUtil.dateAndTimeString(start) binding.zoom.text = rangeToDisplay.toString() - val graphData = GraphData(injector, binding.bggraph, iobCobCalculatorPluginHistory) + val graphData = GraphData(injector, binding.bggraph) val secondaryGraphsData: ArrayList = ArrayList() // do preparation in different thread @@ -293,29 +293,29 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() { graphData.addInRangeArea(fromTime, toTime, lowLine, highLine) // **** BG **** - graphData.addBgReadings(fromTime, toTime, highLine, null) - if (buildHelper.isDev()) graphData.addBucketedData(fromTime, toTime) +// graphData.addBgReadings(fromTime, toTime, highLine, null) +// if (buildHelper.isDev()) graphData.addBucketedData(fromTime, toTime) // add target line - graphData.addTargetLine(fromTime, toTime, profile, null) +// graphData.addTargetLine(fromTime, toTime, profile, null) // **** NOW line **** graphData.addNowLine(pointer) if (!bgOnly) { // Treatments - graphData.addTreatments(fromTime, toTime) +// graphData.addTreatments(fromTime, toTime) if (menuChartSettings[0][OverviewMenus.CharType.ACT.ordinal]) - graphData.addActivity(fromTime, toTime, false, 0.8) +// graphData.addActivity(fromTime, toTime, false, 0.8) // add basal data if (pump.pumpDescription.isTempBasalCapable && menuChartSettings[0][OverviewMenus.CharType.BAS.ordinal]) { - graphData.addBasals(fromTime, toTime, lowLine / graphData.maxY / 1.2) +// graphData.addBasals(fromTime, toTime, lowLine / graphData.maxY / 1.2) } // ------------------ 2nd graph synchronized(graphLock) { for (g in 0 until secondaryGraphs.size) { - val secondGraphData = GraphData(injector, secondaryGraphs[g], iobCobCalculatorPluginHistory) + val secondGraphData = GraphData(injector, secondaryGraphs[g]) var useIobForScale = false var useCobForScale = false var useDevForScale = false @@ -336,13 +336,13 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() { val alignIobScale = menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal] val alignDevBgiScale = menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] - if (menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal]) secondGraphData.addAbsIob(fromTime, toTime, useABSForScale, 1.0) - if (menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal]) secondGraphData.addIob(fromTime, toTime, useIobForScale, 1.0, menuChartSettings[g + 1][OverviewMenus.CharType.PRE.ordinal], alignIobScale) - if (menuChartSettings[g + 1][OverviewMenus.CharType.COB.ordinal]) secondGraphData.addCob(fromTime, toTime, useCobForScale, if (useCobForScale) 1.0 else 0.5) - if (menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal]) secondGraphData.addDeviations(fromTime, toTime, useDevForScale, 1.0, alignDevBgiScale) - if (menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal]) secondGraphData.addRatio(fromTime, toTime, useRatioForScale, 1.0) - if (menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal]) secondGraphData.addMinusBGI(fromTime, toTime, useBGIForScale, if (alignDevBgiScale) 1.0 else 0.8, alignDevBgiScale) - if (menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] && buildHelper.isDev()) secondGraphData.addDeviationSlope(fromTime, toTime, useDSForScale, 1.0) +// if (menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal]) secondGraphData.addAbsIob(fromTime, toTime, useABSForScale, 1.0) +// if (menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal]) secondGraphData.addIob(fromTime, toTime, useIobForScale, 1.0, menuChartSettings[g + 1][OverviewMenus.CharType.PRE.ordinal], alignIobScale) +// if (menuChartSettings[g + 1][OverviewMenus.CharType.COB.ordinal]) secondGraphData.addCob(fromTime, toTime, useCobForScale, if (useCobForScale) 1.0 else 0.5) +// if (menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal]) secondGraphData.addDeviations(fromTime, toTime, useDevForScale, 1.0, alignDevBgiScale) +// if (menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal]) secondGraphData.addRatio(fromTime, toTime, useRatioForScale, 1.0) +// if (menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal]) secondGraphData.addMinusBGI(fromTime, toTime, useBGIForScale, if (alignDevBgiScale) 1.0 else 0.8, alignDevBgiScale) +// if (menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] && buildHelper.isDev()) secondGraphData.addDeviationSlope(fromTime, toTime, useDSForScale, 1.0) // set manual x bounds to have nice steps secondGraphData.formatAxis(fromTime, toTime) 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 new file mode 100644 index 0000000000..e90e8cfb88 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/events/EventLoopInvoked.kt @@ -0,0 +1,5 @@ +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/LoopPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopPlugin.kt index eab790dc48..072a9fb1d5 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 @@ -54,6 +54,7 @@ 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.utils.rx.AapsSchedulers import info.nightscout.androidaps.utils.sharedPreferences.SP @@ -286,7 +287,7 @@ open class LoopPlugin @Inject constructor( if (apsResult == null) { rxBus.send(EventLoopSetLastRunGui(resourceHelper.gs(R.string.noapsselected))) return - } + } else rxBus.send(EventLoopInvoked()) // Prepare for pumps using % basals if (pump.pumpDescription.tempBasalStyle == PumpDescription.PERCENT && allowPercentage()) { 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 b945124a5d..c5049d171d 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 @@ -1,5 +1,6 @@ package info.nightscout.androidaps.plugins.configBuilder +import androidx.collection.LongSparseArray import info.nightscout.androidaps.Constants import info.nightscout.androidaps.core.R import info.nightscout.androidaps.data.ProfileSealed @@ -35,6 +36,8 @@ class ProfileFunctionImplementation @Inject constructor( private val dateUtil: DateUtil ) : ProfileFunction { + val cache = LongSparseArray() + private val disposable = CompositeDisposable() override fun getProfileName(): String = @@ -65,10 +68,20 @@ class ProfileFunctionImplementation @Inject constructor( getProfile(dateUtil.now()) override fun getProfile(time: Long): Profile? { - // aapsLogger.debug("XXXXXXXXXXXXXXX getProfile called for $time") + val rounded = time - time % 1000 + val cached = cache[rounded] + if (cached != null) { +// aapsLogger.debug("XXXXXXXXXXXXXXX HIT getProfile for $time $rounded") + return cached + } +// aapsLogger.debug("XXXXXXXXXXXXXXX getProfile called for $time") val ps = repository.getEffectiveProfileSwitchActiveAt(time).blockingGet() - return if (ps is ValueWrapper.Existing) ProfileSealed.EPS(ps.value) - else null + if (ps is ValueWrapper.Existing) { + val sealed = ProfileSealed.EPS(ps.value) + cache.put(rounded, sealed) + return sealed + } + return null } override fun getRequestedProfile(): ProfileSwitch? = repository.getActiveProfileSwitch(dateUtil.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 3d0eb423cb..1e18a5f54f 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 @@ -11,7 +11,6 @@ import android.widget.LinearLayout import android.widget.TextView import androidx.core.content.ContextCompat import dagger.android.support.DaggerFragment -import info.nightscout.androidaps.interfaces.Config import info.nightscout.androidaps.Constants import info.nightscout.androidaps.R import info.nightscout.androidaps.activities.ErrorHelperActivity @@ -21,13 +20,18 @@ 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.dialogs.* -import info.nightscout.androidaps.events.* +import info.nightscout.androidaps.events.EventCustomActionsChanged +import info.nightscout.androidaps.events.EventExtendedBolusChange +import info.nightscout.androidaps.events.EventInitializationChanged +import info.nightscout.androidaps.events.EventTempBasalChange +import info.nightscout.androidaps.events.EventTherapyEventChange import info.nightscout.androidaps.extensions.toStringMedium import info.nightscout.androidaps.extensions.toStringShort import info.nightscout.androidaps.extensions.toVisibility import info.nightscout.androidaps.historyBrowser.HistoryBrowseActivity import info.nightscout.androidaps.interfaces.ActivePlugin import info.nightscout.androidaps.interfaces.CommandQueueProvider +import info.nightscout.androidaps.interfaces.Config import info.nightscout.androidaps.interfaces.IobCobCalculator import info.nightscout.androidaps.interfaces.ProfileFunction import info.nightscout.androidaps.logging.AAPSLogger @@ -229,10 +233,6 @@ class ActionsFragment : DaggerFragment() { .toObservable(EventInitializationChanged::class.java) .observeOn(aapsSchedulers.main) .subscribe({ updateGui() }, fabricPrivacy::logException) - disposable += rxBus - .toObservable(EventRefreshOverview::class.java) - .observeOn(aapsSchedulers.main) - .subscribe({ updateGui() }, fabricPrivacy::logException) disposable += rxBus .toObservable(EventExtendedBolusChange::class.java) .observeOn(aapsSchedulers.main) 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 3ba7f417d4..d5f64cc96e 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,32 +1,133 @@ package info.nightscout.androidaps.plugins.general.overview +import com.jjoe64.graphview.series.BarGraphSeries +import com.jjoe64.graphview.series.DataPoint +import com.jjoe64.graphview.series.LineGraphSeries import info.nightscout.androidaps.R +import info.nightscout.androidaps.data.IobTotal +import info.nightscout.androidaps.database.entities.ExtendedBolus +import info.nightscout.androidaps.database.entities.GlucoseValue import info.nightscout.androidaps.database.entities.TemporaryBasal +import info.nightscout.androidaps.database.entities.TemporaryTarget import info.nightscout.androidaps.extensions.convertedToPercent 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.Profile +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.DateUtil +import info.nightscout.androidaps.utils.DefaultValueHelper +import info.nightscout.androidaps.utils.T import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.androidaps.utils.sharedPreferences.SP +import java.util.* import javax.inject.Inject import javax.inject.Singleton +import kotlin.collections.ArrayList @Singleton class OverviewData @Inject constructor( private val resourceHelper: ResourceHelper, - private val dateUtil: DateUtil + private val dateUtil: DateUtil, + private val sp: SP, + private val activePlugin: ActivePlugin, + private val defaultValueHelper: DefaultValueHelper, + private val profileFunction: ProfileFunction ) { enum class Property { + TIME, + CALC_PROGRESS, PROFILE, - TEMPORARY_BASAL + TEMPORARY_BASAL, + EXTENDED_BOLUS, + TEMPORARY_TARGET, + BG, + IOB_COB, + SENSITIVITY, + GRAPH } - @get:Synchronized @set:Synchronized - var profile: Profile? = null + var rangeToDisplay = 6 // for graph + var toTime: Long = 0 + var fromTime: Long = 0 + var endTime: Long = 0 - @get:Synchronized @set:Synchronized + fun initRange() { + rangeToDisplay = sp.getInt(R.string.key_rangetodisplay, 6) + + 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) + } + + toTime = calendar.timeInMillis + 100000 // little bit more to avoid wrong rounding - GraphView specific + fromTime = toTime - T.hours(rangeToDisplay.toLong()).msecs() + endTime = toTime + } + + /* + * PROFILE + */ + var profile: Profile? = null var profileName: String? = null + var profileNameWithRemainingTime: String? = null + + val profileBackgroudColor: Int + get() = + profile?.let { profile -> + if (profile.percentage != 100 || profile.timeshift != 0) resourceHelper.gc(R.color.ribbonWarning) + else resourceHelper.gc(R.color.ribbonDefault) + } ?: resourceHelper.gc(R.color.ribbonTextDefault) + + val profileTextColor: Int + get() = + profile?.let { profile -> + if (profile.percentage != 100 || profile.timeshift != 0) resourceHelper.gc(R.color.ribbonTextWarning) + else resourceHelper.gc(R.color.ribbonTextDefault) + } ?: resourceHelper.gc(R.color.ribbonTextDefault) + + /* + * CALC PROGRESS + */ + + var calcProgress: String = "" + + /* + * BG + */ + + var lastBg: GlucoseValue? = null + + val lastBgColor: Int + get() = lastBg?.let { lastBg -> + when { + lastBg.valueToUnits(profileFunction.getUnits()) < defaultValueHelper.determineLowLine() -> resourceHelper.gc(R.color.low) + lastBg.valueToUnits(profileFunction.getUnits()) > defaultValueHelper.determineHighLine() -> resourceHelper.gc(R.color.high) + else -> resourceHelper.gc(R.color.inrange) + } + } ?: resourceHelper.gc(R.color.inrange) + + val isActualBg: Boolean + get() = + lastBg?.let { lastBg -> + lastBg.timestamp > dateUtil.now() - T.mins(9).msecs() + } ?: false + + /* + * TEMPORARY BASAL + */ var temporaryBasal: TemporaryBasal? = null @@ -62,4 +163,119 @@ class OverviewData @Inject constructor( val temporaryBasalColor: Int get() = temporaryBasal?.let { resourceHelper.gc(R.color.basal) } ?: resourceHelper.gc(R.color.defaulttextcolor) -} \ No newline at end of file + + /* + * EXTENDED BOLUS + */ + + var extendedBolus: ExtendedBolus? = null + + val extendedBolusText: String + get() = + extendedBolus?.let { extendedBolus -> + if (activePlugin.activePump.isFakingTempsByExtendedBoluses) resourceHelper.gs(R.string.pump_basebasalrate, extendedBolus.rate) + else "" + } ?: "" + + val extendedBolusDialogText: String + get() = extendedBolus?.toStringFull(dateUtil) ?: "" + + /* + * IOB, COB + */ + + var bolusIob: IobTotal? = null + var basalIob: IobTotal? = null + var cobInfo: CobInfo? = null + var lastCarbsTime: Long = 0L + + val iobText: String + get() = + bolusIob?.let { bolusIob -> + basalIob?.let { basalIob -> + resourceHelper.gs(R.string.formatinsulinunits, bolusIob.iob + basalIob.basaliob) + } ?: resourceHelper.gs(R.string.value_unavailable_short) + } ?: resourceHelper.gs(R.string.value_unavailable_short) + + val iobDialogText: String + get() = + bolusIob?.let { bolusIob -> + basalIob?.let { basalIob -> + resourceHelper.gs(R.string.formatinsulinunits, bolusIob.iob + basalIob.basaliob) + "\n" + + resourceHelper.gs(R.string.bolus) + ": " + resourceHelper.gs(R.string.formatinsulinunits, bolusIob.iob) + "\n" + + resourceHelper.gs(R.string.basal) + ": " + resourceHelper.gs(R.string.formatinsulinunits, basalIob.basaliob) + } ?: resourceHelper.gs(R.string.value_unavailable_short) + } ?: resourceHelper.gs(R.string.value_unavailable_short) + + /* + * TEMP TARGET + */ + + var temporarytarget: TemporaryTarget? = null + + /* + * SENSITIVITY + */ + + var lastAutosensData: AutosensData? = null + /* + * Graphs + */ + + var bgReadingsArray: List = ArrayList() + var maxBgValue = Double.MIN_VALUE + var bucketedGraphSeries: PointsWithLabelGraphSeries = PointsWithLabelGraphSeries() + var bgReadingGraphSeries: PointsWithLabelGraphSeries = PointsWithLabelGraphSeries() + var predictionsGraphSeries: PointsWithLabelGraphSeries = PointsWithLabelGraphSeries() + + var maxBasalValueFound = 0.0 + val basalScale = Scale() + var baseBasalGraphSeries: LineGraphSeries = LineGraphSeries() + var tempBasalGraphSeries: LineGraphSeries = LineGraphSeries() + var basalLineGraphSeries: LineGraphSeries = LineGraphSeries() + var absoluteBasalGraphSeries: LineGraphSeries = LineGraphSeries() + + var temporaryTargetSeries: LineGraphSeries = LineGraphSeries() + + var maxIAValue = 0.0 + val actScale = Scale() + var activitySeries: FixedLineGraphSeries = FixedLineGraphSeries() + var activityPredictionSeries: FixedLineGraphSeries = FixedLineGraphSeries() + + var maxTreatmentsValue = 0.0 + var treatmentsSeries: PointsWithLabelGraphSeries = PointsWithLabelGraphSeries() + + var maxIobValueFound = Double.MIN_VALUE + val iobScale = Scale() + var iobSeries: FixedLineGraphSeries = FixedLineGraphSeries() + var absIobSeries: FixedLineGraphSeries = FixedLineGraphSeries() + var iobPredictions1Series: PointsWithLabelGraphSeries = PointsWithLabelGraphSeries() + var iobPredictions2Series: PointsWithLabelGraphSeries = PointsWithLabelGraphSeries() + + var maxBGIValue = Double.MIN_VALUE + val bgiScale = Scale() + var minusBgiSeries: FixedLineGraphSeries = FixedLineGraphSeries() + var minusBgiHistSeries: FixedLineGraphSeries = FixedLineGraphSeries() + + var maxCobValueFound = Double.MIN_VALUE + val cobScale = Scale() + var cobSeries: FixedLineGraphSeries = FixedLineGraphSeries() + var cobMinFailOverSeries: PointsWithLabelGraphSeries = PointsWithLabelGraphSeries() + + var maxDevValueFound = Double.MIN_VALUE + val devScale = Scale() + var deviationsSeries: BarGraphSeries = BarGraphSeries() + + var maxRatioValueFound = 5.0 //even if sens data equals 0 for all the period, minimum scale is between 95% and 105% + var minRatioValueFound = -maxRatioValueFound + val ratioScale = Scale() + var ratioSeries: LineGraphSeries = LineGraphSeries() + + var maxFromMaxValueFound = Double.MIN_VALUE + var maxFromMinValueFound = Double.MIN_VALUE + val dsMaxScale = Scale() + val dsMinScale = Scale() + var dsMaxSeries: LineGraphSeries = LineGraphSeries() + var dsMinSeries: LineGraphSeries = LineGraphSeries() + +} 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 ea423a352f..d4b9d90774 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 @@ -20,7 +20,6 @@ import android.widget.LinearLayout import android.widget.RelativeLayout import android.widget.TextView import androidx.core.text.toSpanned -import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import com.jjoe64.graphview.GraphView import dagger.android.HasAndroidInjector @@ -28,15 +27,19 @@ import dagger.android.support.DaggerFragment import info.nightscout.androidaps.Constants import info.nightscout.androidaps.R 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.UserEntry.Action 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.* -import info.nightscout.androidaps.extensions.* +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.extensions.directionToIcon +import info.nightscout.androidaps.extensions.toVisibility +import info.nightscout.androidaps.extensions.valueToUnitsString import info.nightscout.androidaps.interfaces.* import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.UserEntryLogger @@ -48,11 +51,9 @@ 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.EventUpdateOverview import info.nightscout.androidaps.plugins.general.overview.graphData.GraphData -import info.nightscout.androidaps.plugins.general.overview.graphExtensions.GlucoseValueDataPoint 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.iob.iobCobCalculator.events.EventIobCalculationProgress import info.nightscout.androidaps.plugins.pump.common.defs.PumpType import info.nightscout.androidaps.plugins.source.DexcomPlugin import info.nightscout.androidaps.plugins.source.XdripPlugin @@ -62,7 +63,6 @@ import info.nightscout.androidaps.skins.SkinProvider import info.nightscout.androidaps.utils.* import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.utils.buildHelper.BuildHelper -import info.nightscout.androidaps.utils.extensions.* import info.nightscout.androidaps.utils.protection.ProtectionCheck import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.rx.AapsSchedulers @@ -70,15 +70,11 @@ import info.nightscout.androidaps.utils.sharedPreferences.SP import info.nightscout.androidaps.utils.ui.UIRunnable import info.nightscout.androidaps.utils.wizard.QuickWizard import io.reactivex.disposables.CompositeDisposable -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext +import io.reactivex.rxkotlin.plusAssign import java.util.* import javax.inject.Inject import kotlin.collections.ArrayList import kotlin.math.abs -import kotlin.math.ceil -import kotlin.math.max import kotlin.math.min class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickListener { @@ -117,6 +113,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList @Inject lateinit var repository: AppRepository @Inject lateinit var glucoseStatusProvider: GlucoseStatusProvider @Inject lateinit var overviewData: OverviewData + @Inject lateinit var overviewPlugin: OverviewPlugin private val disposable = CompositeDisposable() @@ -124,17 +121,14 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList private var smallHeight = false private lateinit var dm: DisplayMetrics private var axisWidth: Int = 0 - private var rangeToDisplay = 6 // for graph - private lateinit var loopHandler: Handler private var refreshLoop: Runnable? = null + private lateinit var handler: Handler private val secondaryGraphs = ArrayList() private val secondaryGraphsLabel = ArrayList() private var carbAnimation: AnimationDrawable? = null - private val graphLock = Object() - private var _binding: OverviewFragmentBinding? = null // This property is only valid between onCreateView and @@ -151,7 +145,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - loopHandler = Handler(HandlerThread(this::class.simpleName + "Handler").also { it.start() }.looper) + handler = Handler(HandlerThread(this::class.simpleName + "Handler").also { it.start() }.looper) // pre-process landscape mode val screenWidth = dm.widthPixels @@ -177,13 +171,14 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList carbAnimation?.setEnterFadeDuration(1200) carbAnimation?.setExitFadeDuration(1200) - rangeToDisplay = sp.getInt(R.string.key_rangetodisplay, 6) binding.graphsLayout.bgGraph.setOnLongClickListener { - rangeToDisplay += 6 - rangeToDisplay = if (rangeToDisplay > 24) 6 else rangeToDisplay - sp.putInt(R.string.key_rangetodisplay, rangeToDisplay) - updateGUI("rangeChange") + overviewData.rangeToDisplay += 6 + overviewData.rangeToDisplay = if (overviewData.rangeToDisplay > 24) 6 else overviewData.rangeToDisplay + sp.putInt(R.string.key_rangetodisplay, overviewData.rangeToDisplay) + overviewData.initRange() + updateGUI("rangeChange", OverviewData.Property.GRAPH) + rxBus.send(EventPreferenceChange(resourceHelper, R.string.key_rangetodisplay)) sp.putBoolean(R.string.key_objectiveusescale, true) false } @@ -212,51 +207,32 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList override fun onPause() { super.onPause() disposable.clear() - loopHandler.removeCallbacksAndMessages(null) + handler.removeCallbacksAndMessages(null) } @Synchronized override fun onResume() { super.onResume() - disposable.add(rxBus - .toObservable(EventRefreshOverview::class.java) - .observeOn(aapsSchedulers.main) - .subscribe({ - if (it.now) updateGUI(it.from) - else scheduleUpdateGUI(it.from) - }, fabricPrivacy::logException)) - disposable.add(rxBus + disposable += activePlugin.activeOverview.overviewBus .toObservable(EventUpdateOverview::class.java) .observeOn(aapsSchedulers.main) - .subscribe({ updateGUI(it.what) }, fabricPrivacy::logException)) + .subscribe({ updateGUI(it.from, it.what) }, fabricPrivacy::logException) + disposable.add(rxBus - .toObservable(EventExtendedBolusChange::class.java) + .toObservable(EventRefreshOverview::class.java) .observeOn(aapsSchedulers.io) - .subscribe({ scheduleUpdateGUI("EventExtendedBolusChange") }, fabricPrivacy::logException)) - disposable.add(rxBus - .toObservable(EventTreatmentChange::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ scheduleUpdateGUI("EventTreatmentChange") }, fabricPrivacy::logException)) - disposable.add(rxBus - .toObservable(EventTempTargetChange::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ scheduleUpdateGUI("EventTempTargetChange") }, fabricPrivacy::logException)) + .subscribe({ + if (it.now) overviewPlugin.refreshLoop(it.from) + else scheduleUpdateGUI(it.from) + }, fabricPrivacy::logException)) disposable.add(rxBus .toObservable(EventAcceptOpenLoopChange::class.java) .observeOn(aapsSchedulers.io) .subscribe({ scheduleUpdateGUI("EventAcceptOpenLoopChange") }, fabricPrivacy::logException)) - disposable.add(rxBus - .toObservable(EventTherapyEventChange::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ scheduleUpdateGUI("EventCareportalEventChange") }, fabricPrivacy::logException)) disposable.add(rxBus .toObservable(EventInitializationChanged::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ scheduleUpdateGUI("EventInitializationChanged") }, fabricPrivacy::logException)) - disposable.add(rxBus - .toObservable(EventAutosensCalculationFinished::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ scheduleUpdateGUI("EventAutosensCalculationFinished") }, fabricPrivacy::logException)) + .observeOn(aapsSchedulers.main) + .subscribe({ updateGUI("EventInitializationChanged", OverviewData.Property.TIME) }, fabricPrivacy::logException)) disposable.add(rxBus .toObservable(EventPreferenceChange::class.java) .observeOn(aapsSchedulers.io) @@ -269,18 +245,14 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList .toObservable(EventPumpStatusChanged::class.java) .observeOn(aapsSchedulers.main) .subscribe({ updatePumpStatus(it) }, fabricPrivacy::logException)) - disposable.add(rxBus - .toObservable(EventIobCalculationProgress::class.java) - .observeOn(aapsSchedulers.main) - .subscribe({ binding.graphsLayout.iobCalculationProgress.text = it.progress }, fabricPrivacy::logException)) refreshLoop = Runnable { - scheduleUpdateGUI("refreshLoop") - loopHandler.postDelayed(refreshLoop, 60 * 1000L) + overviewPlugin.refreshLoop("refreshLoop") + handler.postDelayed(refreshLoop, 60 * 1000L) } - loopHandler.postDelayed(refreshLoop, 60 * 1000L) + handler.postDelayed(refreshLoop, 60 * 1000L) - updateGUI("onResume") + for (p in OverviewData.Property.values()) updateGUI("onResume", p) } @Synchronized @@ -340,7 +312,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList R.id.accept_temp_button -> { profileFunction.getProfile() ?: return if (loopPlugin.isEnabled(PluginType.LOOP)) { - loopHandler.post { + handler.post { val lastRun = loopPlugin.lastRun loopPlugin.invoke("Accept temp button", false) if (lastRun?.lastAPSRun != null && lastRun.constraintsProcessed?.isChangeRequested == true) { @@ -490,149 +462,11 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList } - private fun prepareGraphsIfNeeded(numOfGraphs: Int) { - synchronized(graphLock) { - if (numOfGraphs != secondaryGraphs.size - 1) { - //aapsLogger.debug("New secondary graph count ${numOfGraphs-1}") - // rebuild needed - secondaryGraphs.clear() - secondaryGraphsLabel.clear() - binding.graphsLayout.iobGraph.removeAllViews() - for (i in 1 until numOfGraphs) { - val relativeLayout = RelativeLayout(context) - relativeLayout.layoutParams = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) - - val graph = GraphView(context) - graph.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, resourceHelper.dpToPx(skinProvider.activeSkin().secondaryGraphHeight)).also { it.setMargins(0, resourceHelper.dpToPx(15), 0, resourceHelper.dpToPx(10)) } - graph.gridLabelRenderer?.gridColor = resourceHelper.gc(R.color.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 - relativeLayout.addView(graph) - - val label = TextView(context) - val layoutParams = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT).also { it.setMargins(resourceHelper.dpToPx(30), resourceHelper.dpToPx(25), 0, 0) } - layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP) - layoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT) - label.layoutParams = layoutParams - relativeLayout.addView(label) - secondaryGraphsLabel.add(label) - - binding.graphsLayout.iobGraph.addView(relativeLayout) - secondaryGraphs.add(graph) - } - } - } - } - - var task: Runnable? = null - - private fun scheduleUpdateGUI(from: String) { - class UpdateRunnable : Runnable { - - override fun run() { - updateGUI(from) - task = null - } - } - view?.removeCallbacks(task) - task = UpdateRunnable() - view?.postDelayed(task, 500) - } - - fun updateGUI(what: OverviewData.Property) { - when (what) { - OverviewData.Property.PROFILE -> TODO() - - OverviewData.Property.TEMPORARY_BASAL -> { - // Basal, TBR - 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, resourceHelper.gs(R.string.basal), overviewData.temporaryBasalDialogText) } - } - } - } - } - - @SuppressLint("SetTextI18n") - fun updateGUI(from: String) { - if (_binding == null) return - aapsLogger.debug("UpdateGUI from $from") - - binding.infoLayout.time.text = dateUtil.timeString(dateUtil.now()) - - if (overviewData.profile == null) { - binding.loopPumpStatusLayout.pumpStatus.setText(R.string.noprofileset) - binding.loopPumpStatusLayout.pumpStatusLayout.visibility = View.VISIBLE - binding.loopPumpStatusLayout.loopLayout.visibility = View.GONE - return - } - binding.notifications.let { notificationStore.updateNotifications(it) } - binding.loopPumpStatusLayout.pumpStatusLayout.visibility = View.GONE - binding.loopPumpStatusLayout.loopLayout.visibility = View.VISIBLE - - val profile = overviewData.profile ?: return - val actualBG = iobCobCalculator.ads.actualBg() - val lastBG = iobCobCalculator.ads.lastBg() + private fun processAps() { val pump = activePlugin.activePump - val units = profileFunction.getUnits() - val lowLine = defaultValueHelper.determineLowLine() - val highLine = defaultValueHelper.determineHighLine() - val lastRun = loopPlugin.lastRun - val predictionsAvailable = if (config.APS) lastRun?.request?.hasPredictions == true else config.NSCLIENT - - try { - updateGraph(lastRun, predictionsAvailable, lowLine, highLine, pump, profile) - } catch (e: IllegalStateException) { - return // view no longer exists - } - - //Start with updating the BG as it is unaffected by loop. - // **** BG value **** - if (lastBG != null) { - val color = when { - lastBG.valueToUnits(units) < lowLine -> resourceHelper.gc(R.color.low) - lastBG.valueToUnits(units) > highLine -> resourceHelper.gc(R.color.high) - else -> resourceHelper.gc(R.color.inrange) - } - - binding.infoLayout.bg.text = lastBG.valueToUnitsString(units) - binding.infoLayout.bg.setTextColor(color) - binding.infoLayout.arrow.setImageResource(trendCalculator.getTrendArrow(lastBG).directionToIcon()) - binding.infoLayout.arrow.setColorFilter(color) - - 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(color) - 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.delta.text = "Δ " + resourceHelper.gs(R.string.notavailable) - binding.infoLayout.avgDelta.text = "" - binding.infoLayout.longAvgDelta.text = "" - } - - // strike through if BG is old - binding.infoLayout.bg.let { overview_bg -> - var flag = overview_bg.paintFlags - flag = if (actualBG == null) { - flag or Paint.STRIKE_THRU_TEXT_FLAG - } else flag and Paint.STRIKE_THRU_TEXT_FLAG.inv() - overview_bg.paintFlags = flag - } - binding.infoLayout.timeAgo.text = dateUtil.minAgo(resourceHelper, lastBG.timestamp) - binding.infoLayout.timeAgoShort.text = "(" + dateUtil.minAgoShort(lastBG.timestamp) + ")" - - } - val closedLoopEnabled = constraintChecker.isClosedLoopAllowed() // aps mode + val closedLoopEnabled = constraintChecker.isClosedLoopAllowed() if (config.APS && pump.pumpDescription.isTempBasalCapable) { binding.infoLayout.apsMode.visibility = View.VISIBLE binding.infoLayout.timeLayout.visibility = View.GONE @@ -693,98 +527,6 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList binding.infoLayout.timeLayout.visibility = View.VISIBLE } - // temp target - val tempTarget: ValueWrapper = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet() - if (tempTarget is ValueWrapper.Existing) { - binding.loopPumpStatusLayout.tempTarget.setTextColor(resourceHelper.gc(R.color.ribbonTextWarning)) - binding.loopPumpStatusLayout.tempTarget.setBackgroundColor(resourceHelper.gc(R.color.ribbonWarning)) - binding.loopPumpStatusLayout.tempTarget.text = Profile.toTargetRangeString(tempTarget.value.lowTarget, tempTarget.value.highTarget, GlucoseUnit.MGDL, units) + " " + dateUtil.untilString(tempTarget.value.end, resourceHelper) - } else { - // If the target is not the same as set in the profile then oref has overridden it - val targetUsed = 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.loopPumpStatusLayout.tempTarget.text = Profile.toTargetRangeString(targetUsed, targetUsed, GlucoseUnit.MGDL, units) - binding.loopPumpStatusLayout.tempTarget.setTextColor(resourceHelper.gc(R.color.ribbonTextWarning)) - binding.loopPumpStatusLayout.tempTarget.setBackgroundColor(resourceHelper.gc(R.color.tempTargetBackground)) - } else { - binding.loopPumpStatusLayout.tempTarget.setTextColor(resourceHelper.gc(R.color.ribbonTextDefault)) - binding.loopPumpStatusLayout.tempTarget.setBackgroundColor(resourceHelper.gc(R.color.ribbonDefault)) - binding.loopPumpStatusLayout.tempTarget.text = Profile.toTargetRangeString(profile.getTargetLowMgdl(), profile.getTargetHighMgdl(), GlucoseUnit.MGDL, units) - } - } - - // Extended bolus - val extendedBolus = repository.getExtendedBolusActiveAt(dateUtil.now()).blockingGet() - binding.infoLayout.extendedBolus.text = - if (extendedBolus is ValueWrapper.Existing && !pump.isFakingTempsByExtendedBoluses) - resourceHelper.gs(R.string.pump_basebasalrate, extendedBolus.value.rate) - else "" - binding.infoLayout.extendedBolus.setOnClickListener { - if (extendedBolus is ValueWrapper.Existing) activity?.let { - OKDialog.show(it, resourceHelper.gs(R.string.extended_bolus), extendedBolus.value.toStringFull(dateUtil)) - } - } - binding.infoLayout.extendedLayout.visibility = (extendedBolus is ValueWrapper.Existing && !pump.isFakingTempsByExtendedBoluses).toVisibility() - - // Active profile - binding.loopPumpStatusLayout.activeProfile.text = profileFunction.getProfileNameWithRemainingTime() - if (profile.percentage != 100 || profile.timeshift != 0) { - binding.loopPumpStatusLayout.activeProfile.setBackgroundColor(resourceHelper.gc(R.color.ribbonWarning)) - binding.loopPumpStatusLayout.activeProfile.setTextColor(resourceHelper.gc(R.color.ribbonTextWarning)) - } else { - binding.loopPumpStatusLayout.activeProfile.setBackgroundColor(resourceHelper.gc(R.color.ribbonDefault)) - binding.loopPumpStatusLayout.activeProfile.setTextColor(resourceHelper.gc(R.color.ribbonTextDefault)) - } - - processButtonsVisibility() - - // iob - val bolusIob = iobCobCalculator.calculateIobFromBolus().round() - val basalIob = iobCobCalculator.calculateIobFromTempBasalsIncludingConvertedExtended().round() - binding.infoLayout.iob.text = resourceHelper.gs(R.string.formatinsulinunits, bolusIob.iob + basalIob.basaliob) - - binding.infoLayout.iobLayout.setOnClickListener { - activity?.let { - OKDialog.show(it, resourceHelper.gs(R.string.iob), - resourceHelper.gs(R.string.formatinsulinunits, bolusIob.iob + basalIob.basaliob) + "\n" + - resourceHelper.gs(R.string.bolus) + ": " + resourceHelper.gs(R.string.formatinsulinunits, bolusIob.iob) + "\n" + - resourceHelper.gs(R.string.basal) + ": " + resourceHelper.gs(R.string.formatinsulinunits, basalIob.basaliob) - ) - } - } - - // Status lights - binding.statusLightsLayout.statusLights.visibility = (sp.getBoolean(R.string.key_show_statuslights, true) || config.NSCLIENT).toVisibility() - statusLightHandler.updateStatusLights(binding.statusLightsLayout.cannulaAge, binding.statusLightsLayout.insulinAge, binding.statusLightsLayout.reservoirLevel, binding.statusLightsLayout.sensorAge, null, binding.statusLightsLayout.pbAge, binding.statusLightsLayout.batteryLevel) - - // cob - var cobText: String = resourceHelper.gs(R.string.value_unavailable_short) - val cobInfo = iobCobCalculator.getCobInfo(false, "Overview COB") - if (cobInfo.displayCob != null) { - cobText = resourceHelper.gs(R.string.format_carbs, cobInfo.displayCob!!.toInt()) - if (cobInfo.futureCarbs > 0) cobText += "(" + DecimalFormatter.to0Decimal(cobInfo.futureCarbs) + ")" - } - - if (config.APS && lastRun?.constraintsProcessed != null) { - if (lastRun.constraintsProcessed!!.carbsReq > 0) { - //only display carbsreq when carbs have not been entered recently - val lastCarb = repository.getLastCarbsRecordWrapped().blockingGet() - val lastCarbsTime = if (lastCarb is ValueWrapper.Existing) lastCarb.value.timestamp else 0L - if (lastCarbsTime < lastRun.lastAPSRun) { - cobText = cobText + " | " + lastRun.constraintsProcessed!!.carbsReq + " " + resourceHelper.gs(R.string.required) - } - binding.infoLayout.cob.text = cobText - if (carbAnimation?.isRunning == false) - carbAnimation?.start() - } else { - binding.infoLayout.cob.text = cobText - carbAnimation?.stop() - carbAnimation?.selectDrawable(0) - } - } else binding.infoLayout.cob.text = cobText - // pump status from ns binding.pump.text = nsDeviceStatus.pumpStatus binding.pump.setOnClickListener { activity?.let { OKDialog.show(it, resourceHelper.gs(R.string.pump), nsDeviceStatus.extendedPumpStatus) } } @@ -796,131 +538,255 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList // Uploader status from ns binding.uploader.text = nsDeviceStatus.uploaderStatusSpanned binding.uploader.setOnClickListener { activity?.let { OKDialog.show(it, resourceHelper.gs(R.string.uploader), nsDeviceStatus.extendedUploaderStatus) } } - - // Sensitivity - 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 { - binding.infoLayout.sensitivityIcon.setImageResource(R.drawable.ic_x_swap_vert) - } - - binding.infoLayout.sensitivity.text = - iobCobCalculator.ads.getLastAutosensData("Overview", aapsLogger, dateUtil)?.let { autosensData -> - String.format(Locale.ENGLISH, "%.0f%%", autosensData.autosensResult.ratio * 100) - } ?: "" } - private fun updateGraph(lastRun: LoopInterface.LastRun?, predictionsAvailable: Boolean, lowLine: Double, highLine: Double, pump: Pump, profile: Profile) { - viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) { - if (_binding == null) return@launch - val menuChartSettings = overviewMenus.setting - prepareGraphsIfNeeded(menuChartSettings.size) - val graphData = GraphData(injector, binding.graphsLayout.bgGraph, iobCobCalculator) - val secondaryGraphsData: ArrayList = ArrayList() + private fun prepareGraphsIfNeeded(numOfGraphs: Int) { + if (numOfGraphs != secondaryGraphs.size - 1) { + //aapsLogger.debug("New secondary graph count ${numOfGraphs-1}") + // rebuild needed + secondaryGraphs.clear() + secondaryGraphsLabel.clear() + binding.graphsLayout.iobGraph.removeAllViews() + for (i in 1 until numOfGraphs) { + val relativeLayout = RelativeLayout(context) + relativeLayout.layoutParams = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) - // do preparation in different thread - withContext(Dispatchers.Default) { - // align to hours - val calendar = Calendar.getInstance() - calendar.timeInMillis = System.currentTimeMillis() - calendar[Calendar.MILLISECOND] = 0 - calendar[Calendar.SECOND] = 0 - calendar[Calendar.MINUTE] = 0 - calendar.add(Calendar.HOUR, 1) - val hoursToFetch: Int - val toTime: Long - val fromTime: Long - val endTime: Long - val apsResult = if (config.APS) lastRun?.constraintsProcessed else nsDeviceStatus.getAPSResult(injector) - 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) - 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() + val graph = GraphView(context) + graph.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, resourceHelper.dpToPx(skinProvider.activeSkin().secondaryGraphHeight)).also { it.setMargins(0, resourceHelper.dpToPx(15), 0, resourceHelper.dpToPx(10)) } + graph.gridLabelRenderer?.gridColor = resourceHelper.gc(R.color.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 + relativeLayout.addView(graph) + + val label = TextView(context) + val layoutParams = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT).also { it.setMargins(resourceHelper.dpToPx(30), resourceHelper.dpToPx(25), 0, 0) } + layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP) + layoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT) + label.layoutParams = layoutParams + relativeLayout.addView(label) + secondaryGraphsLabel.add(label) + + binding.graphsLayout.iobGraph.addView(relativeLayout) + secondaryGraphs.add(graph) + } + } + } + + var task: Runnable? = null + + private fun scheduleUpdateGUI(from: String) { + class UpdateRunnable : Runnable { + + override fun run() { + overviewPlugin.refreshLoop(from) + task = null + } + } + handler.removeCallbacks(task) + task = UpdateRunnable() + handler.postDelayed(task, 500) + } + + @Suppress("UNUSED_PARAMETER") + @SuppressLint("SetTextI18n") + fun updateGUI(from: String, what: OverviewData.Property) { +// if (what != OverviewData.Property.CALC_PROGRESS) +// aapsLogger.debug(LTag.UI, "UpdateGui $from $what") + if (overviewData.profile == null) { + binding.loopPumpStatusLayout.pumpStatus.setText(R.string.noprofileset) + binding.loopPumpStatusLayout.pumpStatusLayout.visibility = View.VISIBLE + binding.loopPumpStatusLayout.loopLayout.visibility = View.GONE + return + } + binding.notifications.let { notificationStore.updateNotifications(it) } + binding.loopPumpStatusLayout.pumpStatusLayout.visibility = View.GONE + binding.loopPumpStatusLayout.loopLayout.visibility = View.VISIBLE + + val units = profileFunction.getUnits() + val pump = activePlugin.activePump + when (what) { + OverviewData.Property.BG -> { + binding.infoLayout.bg.text = overviewData.lastBg?.valueToUnitsString(units) + ?: resourceHelper.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 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 { - hoursToFetch = rangeToDisplay - toTime = calendar.timeInMillis + 100000 // little bit more to avoid wrong rounding - GraphView specific - fromTime = toTime - T.hours(hoursToFetch.toLong()).msecs() - endTime = toTime + binding.infoLayout.deltaLarge.text = "Δ " + resourceHelper.gs(R.string.notavailable) + binding.infoLayout.delta.text = "Δ " + resourceHelper.gs(R.string.notavailable) + binding.infoLayout.avgDelta.text = "" + binding.infoLayout.longAvgDelta.text = "" } - val now = System.currentTimeMillis() - // ------------------ 1st graph + // 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(resourceHelper, overviewData.lastBg?.timestamp) + binding.infoLayout.timeAgoShort.text = "(" + dateUtil.minAgoShort(overviewData.lastBg?.timestamp) + ")" + } - // **** In range Area **** - graphData.addInRangeArea(fromTime, endTime, lowLine, highLine) + OverviewData.Property.PROFILE -> { + binding.loopPumpStatusLayout.activeProfile.text = overviewData.profileNameWithRemainingTime + ?: "" + binding.loopPumpStatusLayout.activeProfile.setBackgroundColor(overviewData.profileBackgroudColor) + binding.loopPumpStatusLayout.activeProfile.setTextColor(overviewData.profileTextColor) + } - // **** BG **** - if (predictionsAvailable && menuChartSettings[0][OverviewMenus.CharType.PRE.ordinal]) - graphData.addBgReadings(fromTime, toTime, highLine, apsResult?.predictions?.map { bg -> GlucoseValueDataPoint(bg, defaultValueHelper, profileFunction, resourceHelper) }?.toMutableList()) - else graphData.addBgReadings(fromTime, toTime, highLine, null) - if (buildHelper.isDev()) graphData.addBucketedData(fromTime, toTime) + OverviewData.Property.TEMPORARY_BASAL -> { + 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, resourceHelper.gs(R.string.basal), overviewData.temporaryBasalDialogText) } + } + } - // Treatments - graphData.addTreatments(fromTime, endTime) + OverviewData.Property.EXTENDED_BOLUS -> { + binding.infoLayout.extendedBolus.text = overviewData.extendedBolusText + binding.infoLayout.extendedBolus.setOnClickListener { + activity?.let { OKDialog.show(it, resourceHelper.gs(R.string.extended_bolus), overviewData.extendedBolusDialogText) } + } + binding.infoLayout.extendedLayout.visibility = (overviewData.extendedBolus != null && !pump.isFakingTempsByExtendedBoluses).toVisibility() - // set manual x bounds to have nice steps - graphData.setNumVerticalLabels() - graphData.formatAxis(fromTime, endTime) + } + OverviewData.Property.TIME -> { + binding.infoLayout.time.text = dateUtil.timeString(dateUtil.now()) + // Status lights + binding.statusLightsLayout.statusLights.visibility = (sp.getBoolean(R.string.key_show_statuslights, true) || config.NSCLIENT).toVisibility() + statusLightHandler.updateStatusLights(binding.statusLightsLayout.cannulaAge, binding.statusLightsLayout.insulinAge, binding.statusLightsLayout.reservoirLevel, binding.statusLightsLayout.sensorAge, null, binding.statusLightsLayout.pbAge, binding.statusLightsLayout.batteryLevel) + processButtonsVisibility() + processAps() + } - if (menuChartSettings[0][OverviewMenus.CharType.ACT.ordinal]) - graphData.addActivity(fromTime, endTime, false, 0.8) + OverviewData.Property.IOB_COB -> { + binding.infoLayout.iob.text = overviewData.iobText + binding.infoLayout.iobLayout.setOnClickListener { + activity?.let { OKDialog.show(it, resourceHelper.gs(R.string.iob), overviewData.iobDialogText) } + } + // cob + var cobText: String = resourceHelper.gs(R.string.value_unavailable_short) + overviewData.cobInfo?.let { cobInfo -> + if (cobInfo.displayCob != null) { + cobText = resourceHelper.gs(R.string.format_carbs, cobInfo.displayCob!!.toInt()) + if (cobInfo.futureCarbs > 0) cobText += "(" + DecimalFormatter.to0Decimal(cobInfo.futureCarbs) + ")" + } + } + binding.infoLayout.cob.text = cobText - // add basal data - if (pump.pumpDescription.isTempBasalCapable && menuChartSettings[0][OverviewMenus.CharType.BAS.ordinal]) - graphData.addBasals(fromTime, now, lowLine / graphData.maxY / 1.2) - - // add target line - graphData.addTargetLine(fromTime, toTime, profile, loopPlugin.lastRun) - - // **** NOW line **** - graphData.addNowLine(now) - - // ------------------ 2nd graph - synchronized(graphLock) { - for (g in 0 until min(secondaryGraphs.size, menuChartSettings.size + 1)) { - val secondGraphData = GraphData(injector, secondaryGraphs[g], iobCobCalculator) - var useABSForScale = false - var useIobForScale = false - var useCobForScale = false - var useDevForScale = false - var useRatioForScale = false - var useDSForScale = false - var useBGIForScale = false - when { - menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] -> useABSForScale = true - menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal] -> useIobForScale = true - menuChartSettings[g + 1][OverviewMenus.CharType.COB.ordinal] -> useCobForScale = true - menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] -> useDevForScale = true - menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] -> useBGIForScale = true - menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal] -> useRatioForScale = true - menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] -> useDSForScale = true + val constraintsProcessed = loopPlugin.lastRun?.constraintsProcessed + val lastRun = loopPlugin.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 = cobText + " | " + constraintsProcessed.carbsReq + " " + resourceHelper.gs(R.string.required) } - val alignIobScale = menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal] - val alignDevBgiScale = menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] - - if (menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal]) secondGraphData.addAbsIob(fromTime, now, useABSForScale, 1.0) - if (menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal]) secondGraphData.addIob(fromTime, now, useIobForScale, 1.0, menuChartSettings[g + 1][OverviewMenus.CharType.PRE.ordinal], alignIobScale) - if (menuChartSettings[g + 1][OverviewMenus.CharType.COB.ordinal]) secondGraphData.addCob(fromTime, now, useCobForScale, if (useCobForScale) 1.0 else 0.5) - if (menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal]) secondGraphData.addDeviations(fromTime, now, useDevForScale, 1.0, alignDevBgiScale) - if (menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal]) secondGraphData.addMinusBGI(fromTime, endTime, useBGIForScale, if (alignDevBgiScale) 1.0 else 0.8, alignDevBgiScale) - if (menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal]) secondGraphData.addRatio(fromTime, now, useRatioForScale, if (useRatioForScale) 1.0 else 0.8) - if (menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] && buildHelper.isDev()) secondGraphData.addDeviationSlope(fromTime, now, useDSForScale, 1.0) - - // set manual x bounds to have nice steps - secondGraphData.formatAxis(fromTime, endTime) - secondGraphData.addNowLine(now) - secondaryGraphsData.add(secondGraphData) + if (carbAnimation?.isRunning == false) + carbAnimation?.start() + } else { + carbAnimation?.stop() + carbAnimation?.selectDrawable(0) } } } - // finally enforce drawing of graphs in UI thread - graphData.performUpdate() - synchronized(graphLock) { + + OverviewData.Property.TEMPORARY_TARGET -> { + // temp target + val tempTarget = overviewData.temporarytarget + if (tempTarget != null) { + binding.loopPumpStatusLayout.tempTarget.setTextColor(resourceHelper.gc(R.color.ribbonTextWarning)) + binding.loopPumpStatusLayout.tempTarget.setBackgroundColor(resourceHelper.gc(R.color.ribbonWarning)) + binding.loopPumpStatusLayout.tempTarget.text = Profile.toTargetRangeString(tempTarget.lowTarget, tempTarget.highTarget, GlucoseUnit.MGDL, units) + " " + dateUtil.untilString(tempTarget.end, resourceHelper) + } else { + // If the target is not the same as set in the profile then oref has overridden it + overviewData.profile?.let { profile -> + val targetUsed = loopPlugin.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.loopPumpStatusLayout.tempTarget.text = Profile.toTargetRangeString(targetUsed, targetUsed, GlucoseUnit.MGDL, units) + binding.loopPumpStatusLayout.tempTarget.setTextColor(resourceHelper.gc(R.color.ribbonTextWarning)) + binding.loopPumpStatusLayout.tempTarget.setBackgroundColor(resourceHelper.gc(R.color.tempTargetBackground)) + } else { + binding.loopPumpStatusLayout.tempTarget.setTextColor(resourceHelper.gc(R.color.ribbonTextDefault)) + binding.loopPumpStatusLayout.tempTarget.setBackgroundColor(resourceHelper.gc(R.color.ribbonDefault)) + binding.loopPumpStatusLayout.tempTarget.text = Profile.toTargetRangeString(profile.getTargetLowMgdl(), profile.getTargetHighMgdl(), GlucoseUnit.MGDL, units) + } + } + } + } + + OverviewData.Property.GRAPH -> { + val graphData = GraphData(injector, binding.graphsLayout.bgGraph) + val menuChartSettings = overviewMenus.setting + graphData.addInRangeArea(overviewData.fromTime, overviewData.endTime, defaultValueHelper.determineLowLine(), defaultValueHelper.determineHighLine()) + graphData.addBgReadings(menuChartSettings[0][OverviewMenus.CharType.PRE.ordinal]) + if (buildHelper.isDev()) graphData.addBucketedData() + graphData.addTreatments() + if (menuChartSettings[0][OverviewMenus.CharType.ACT.ordinal]) + graphData.addActivity(0.8) + if (pump.pumpDescription.isTempBasalCapable && menuChartSettings[0][OverviewMenus.CharType.BAS.ordinal]) + graphData.addBasals() + graphData.addTargetLine() + graphData.addNowLine(dateUtil.now()) + + // set manual x bounds to have nice steps + graphData.setNumVerticalLabels() + graphData.formatAxis(overviewData.fromTime, overviewData.endTime) + + graphData.performUpdate() + + // 2nd graphs + prepareGraphsIfNeeded(menuChartSettings.size) + val secondaryGraphsData: ArrayList = ArrayList() + + val now = System.currentTimeMillis() + for (g in 0 until min(secondaryGraphs.size, menuChartSettings.size + 1)) { + val secondGraphData = GraphData(injector, secondaryGraphs[g]) + var useABSForScale = false + var useIobForScale = false + var useCobForScale = false + var useDevForScale = false + var useRatioForScale = false + var useDSForScale = false + var useBGIForScale = false + when { + menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] -> useABSForScale = true + menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal] -> useIobForScale = true + menuChartSettings[g + 1][OverviewMenus.CharType.COB.ordinal] -> useCobForScale = true + menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] -> useDevForScale = true + menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] -> useBGIForScale = true + menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal] -> useRatioForScale = true + menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] -> useDSForScale = true + } + val alignDevBgiScale = menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] + + if (menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal]) secondGraphData.addAbsIob(useABSForScale, 1.0) + if (menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal]) secondGraphData.addIob(useIobForScale, 1.0) + if (menuChartSettings[g + 1][OverviewMenus.CharType.COB.ordinal]) secondGraphData.addCob(useCobForScale, if (useCobForScale) 1.0 else 0.5) + if (menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal]) secondGraphData.addDeviations(useDevForScale, 1.0) + if (menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal]) secondGraphData.addMinusBGI(useBGIForScale, if (alignDevBgiScale) 1.0 else 0.8) + if (menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal]) secondGraphData.addRatio(useRatioForScale, if (useRatioForScale) 1.0 else 0.8) + if (menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] && buildHelper.isDev()) secondGraphData.addDeviationSlope(useDSForScale, 1.0) + + // set manual x bounds to have nice steps + secondGraphData.formatAxis(overviewData.fromTime, overviewData.endTime) + secondGraphData.addNowLine(now) + secondaryGraphsData.add(secondGraphData) + } for (g in 0 until min(secondaryGraphs.size, menuChartSettings.size + 1)) { secondaryGraphsLabel[g].text = overviewMenus.enabledTypes(g + 1) secondaryGraphs[g].visibility = ( @@ -935,6 +801,23 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList secondaryGraphsData[g].performUpdate() } } + + OverviewData.Property.CALC_PROGRESS -> { + binding.graphsLayout.iobCalculationProgress.text = overviewData.calcProgress + } + + OverviewData.Property.SENSITIVITY -> { + 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 { + binding.infoLayout.sensitivityIcon.setImageResource(R.drawable.ic_x_swap_vert) + } + + binding.infoLayout.sensitivity.text = + overviewData.lastAutosensData?.let { autosensData -> + String.format(Locale.ENGLISH, "%.0f%%", autosensData.autosensResult.ratio * 100) + } ?: "" + } } } } 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 1dfa734247..70fa9ef620 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 @@ -34,18 +34,19 @@ class OverviewMenus @Inject constructor( 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) + 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) } companion object { + const val MAX_GRAPHS = 5 // including main } @@ -58,12 +59,10 @@ class OverviewMenus @Inject constructor( return r.toString() } - - private var _setting: MutableList> = ArrayList() val setting: List> - get() = _setting.toMutableList() // implicitly does a list copy + get() = _setting.toMutableList() // implicitly does a list copy private fun storeGraphConfig() { val sts = Gson().toJson(_setting) @@ -71,7 +70,7 @@ class OverviewMenus @Inject constructor( aapsLogger.debug(sts) } - private fun loadGraphConfig() { + fun loadGraphConfig() { val sts = sp.getString(R.string.key_graphconfig, "") if (sts.isNotEmpty()) { _setting = Gson().fromJson(sts, Array>::class.java).toMutableList() @@ -88,7 +87,6 @@ class OverviewMenus @Inject constructor( } fun setupChartMenu(chartButton: ImageButton) { - loadGraphConfig() val settingsCopy = setting val numOfGraphs = settingsCopy.size // 1 main + x secondary @@ -100,6 +98,8 @@ class OverviewMenus @Inject constructor( } val popup = PopupMenu(v.context, v) + val used = arrayListOf() + for (g in 0 until numOfGraphs) { if (g != 0 && g < numOfGraphs) { val dividerItem = popup.menu.add(Menu.NONE, g, Menu.NONE, "------- ${resourceHelper.gs(R.string.graph_menu_divider_header)} $g -------") @@ -112,6 +112,7 @@ class OverviewMenus @Inject constructor( var insert = true if (m == CharType.PRE) insert = predictionsAvailable if (m == CharType.DEVSLOPE) insert = buildHelper.isDev() + if (used.contains(m.ordinal)) insert = false if (insert) { val item = popup.menu.add(Menu.NONE, m.ordinal + 100 * (g + 1), Menu.NONE, resourceHelper.gs(m.nameId)) val title = item.title @@ -120,6 +121,7 @@ class OverviewMenus @Inject constructor( item.title = s item.isCheckable = true item.isChecked = settingsCopy[g][m.ordinal] + if (settingsCopy[g][m.ordinal]) used.add(m.ordinal) } } } @@ -131,16 +133,20 @@ class OverviewMenus @Inject constructor( popup.setOnMenuItemClickListener { // id < 100 graph header - divider 1, 2, 3 ..... - if (it.itemId == numOfGraphs) { - // add new empty - _setting.add(Array(CharType.values().size) { false }) - } else if (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 + 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 + } } storeGraphConfig() setupChartMenu(chartButton) @@ -153,4 +159,11 @@ class OverviewMenus @Inject constructor( } } + fun isEnabledIn(type: CharType): Int { + val settingsCopy = setting + val numOfGraphs = settingsCopy.size // 1 main + x secondary + for (g in 0 until numOfGraphs) if (settingsCopy[g][type.ordinal]) return g + return -1 + } + } \ No newline at end of file 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 673cc9598f..99da457276 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 @@ -1,31 +1,50 @@ package info.nightscout.androidaps.plugins.general.overview +import android.graphics.DashPathEffect +import android.graphics.Paint import androidx.preference.PreferenceFragmentCompat import androidx.preference.SwitchPreference +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.events.EventAppInitialized -import info.nightscout.androidaps.events.EventProfileSwitchChanged -import info.nightscout.androidaps.events.EventRefreshOverview -import info.nightscout.androidaps.events.EventTempBasalChange +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.events.* import info.nightscout.androidaps.extensions.* import info.nightscout.androidaps.interfaces.* import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.plugins.aps.events.EventLoopInvoked +import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin +import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults import info.nightscout.androidaps.plugins.bus.RxBusWrapper +import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus 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.EventUpdateOverview +import info.nightscout.androidaps.plugins.general.overview.graphExtensions.* import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationStore -import info.nightscout.androidaps.utils.DateUtil -import info.nightscout.androidaps.utils.FabricPrivacy +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventBucketedDataCreated +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress +import info.nightscout.androidaps.utils.* import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.rx.AapsSchedulers import info.nightscout.androidaps.utils.sharedPreferences.SP import io.reactivex.disposables.CompositeDisposable import io.reactivex.rxkotlin.plusAssign import org.json.JSONObject +import java.util.* import javax.inject.Inject import javax.inject.Singleton +import kotlin.math.abs +import kotlin.math.ceil +import kotlin.math.max +import kotlin.math.min @Singleton class OverviewPlugin @Inject constructor( @@ -39,9 +58,17 @@ class OverviewPlugin @Inject constructor( resourceHelper: ResourceHelper, private val config: Config, private val dateUtil: DateUtil, + private val translator: Translator, +// private val profiler: Profiler, private val profileFunction: ProfileFunction, private val iobCobCalculator: IobCobCalculator, - private val overviewData: OverviewData + private val repository: AppRepository, + private val defaultValueHelper: DefaultValueHelper, + private val loopPlugin: LoopPlugin, + private val activePlugin: ActivePlugin, + private val nsDeviceStatus: NSDeviceStatus, + private val overviewData: OverviewData, + private val overviewMenus: OverviewMenus ) : PluginBase(PluginDescription() .mainType(PluginType.GENERAL) .fragmentClass(OverviewFragment::class.qualifiedName) @@ -57,8 +84,15 @@ class OverviewPlugin @Inject constructor( private var disposable: CompositeDisposable = CompositeDisposable() + override val overviewBus = RxBusWrapper(aapsSchedulers) + + class DeviationDataPoint(x: Double, y: Double, var color: Int, scale: Scale) : ScaledDataPoint(x, y, scale) + override fun onStart() { super.onStart() + overviewMenus.loadGraphConfig() + overviewData.initRange() + notificationStore.createNotificationChannel() disposable += rxBus .toObservable(EventNewNotification::class.java) @@ -75,17 +109,62 @@ class OverviewPlugin @Inject constructor( rxBus.send(EventRefreshOverview("EventDismissNotification")) }, fabricPrivacy::logException) disposable += rxBus - .toObservable(EventAppInitialized::class.java) - .observeOn(aapsSchedulers.io) - .subscribe( { loadAll() }, fabricPrivacy::logException) + .toObservable(EventIobCalculationProgress::class.java) + .observeOn(aapsSchedulers.main) + .subscribe({ overviewData.calcProgress = it.progress; overviewBus.send(EventUpdateOverview("EventIobCalculationProgress", OverviewData.Property.CALC_PROGRESS)) }, fabricPrivacy::logException) disposable += rxBus .toObservable(EventTempBasalChange::class.java) .observeOn(aapsSchedulers.io) - .subscribe( { loadTemporaryBasal() }, fabricPrivacy::logException) + .subscribe({ loadTemporaryBasal("EventTempBasalChange") }, fabricPrivacy::logException) + disposable += rxBus + .toObservable(EventExtendedBolusChange::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ loadExtendedBolus("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") + prepareTreatmentsData("EventTreatmentChange") + overviewBus.send(EventUpdateOverview("EventTreatmentChange", OverviewData.Property.GRAPH)) + }, fabricPrivacy::logException) + disposable += rxBus + .toObservable(EventTherapyEventChange::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ + prepareTreatmentsData("EventTherapyEventChange") + overviewBus.send(EventUpdateOverview("EventTherapyEventChange", OverviewData.Property.GRAPH)) + }, fabricPrivacy::logException) + disposable += rxBus + .toObservable(EventBucketedDataCreated::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ + prepareBucketedData("EventBucketedDataCreated") + prepareBgData("EventBucketedDataCreated") + overviewBus.send(EventUpdateOverview("EventBucketedDataCreated", OverviewData.Property.GRAPH)) + }, fabricPrivacy::logException) + disposable += rxBus + .toObservable(EventLoopInvoked::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ preparePredictions("EventLoopInvoked") }, fabricPrivacy::logException) disposable.add(rxBus .toObservable(EventProfileSwitchChanged::class.java) .observeOn(aapsSchedulers.io) - .subscribe({ loadProfile() }, fabricPrivacy::logException)) + .subscribe({ loadProfile("EventProfileSwitchChanged") }, fabricPrivacy::logException)) + disposable.add(rxBus + .toObservable(EventAutosensCalculationFinished::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ refreshLoop("EventAutosensCalculationFinished") }, fabricPrivacy::logException)) + + Thread { loadAll("onResume") }.start() } override fun onStop() { @@ -160,21 +239,600 @@ class OverviewPlugin @Inject constructor( .storeDouble(R.string.key_statuslights_bat_critical, sp, resourceHelper) } - private fun loadAll() { - loadProfile() - loadTemporaryBasal() + @Volatile var runningRefresh = false + override fun refreshLoop(from: String) { + if (runningRefresh) return + runningRefresh = true + overviewBus.send(EventUpdateOverview(from, OverviewData.Property.BG)) + overviewBus.send(EventUpdateOverview(from, OverviewData.Property.TIME)) + overviewBus.send(EventUpdateOverview(from, OverviewData.Property.TEMPORARY_BASAL)) + overviewBus.send(EventUpdateOverview(from, OverviewData.Property.EXTENDED_BOLUS)) + overviewBus.send(EventUpdateOverview(from, OverviewData.Property.IOB_COB)) + overviewBus.send(EventUpdateOverview(from, OverviewData.Property.TEMPORARY_TARGET)) + overviewBus.send(EventUpdateOverview(from, OverviewData.Property.SENSITIVITY)) + loadAsData(from) + preparePredictions(from) + prepareBasalData(from) + prepareTemporaryTargetData(from) + prepareTreatmentsData(from) + prepareIobAutosensData(from) + overviewBus.send(EventUpdateOverview(from, OverviewData.Property.GRAPH)) + aapsLogger.debug(LTag.UI, "refreshLoop finished") + runningRefresh = false } - @Synchronized - private fun loadProfile() { + @Suppress("SameParameterValue") + private fun loadAll(from: String) { + loadBg(from) + loadProfile(from) + loadTemporaryBasal(from) + loadExtendedBolus(from) + loadTemporaryTarget(from) + loadIobCobResults(from) + loadAsData(from) + prepareBasalData(from) + prepareTemporaryTargetData(from) + prepareTreatmentsData(from) +// prepareIobAutosensData(from) +// preparePredictions(from) + overviewBus.send(EventUpdateOverview(from, OverviewData.Property.GRAPH)) + aapsLogger.debug(LTag.UI, "loadAll finished") + } + + private fun loadProfile(from: String) { overviewData.profile = profileFunction.getProfile() overviewData.profileName = profileFunction.getProfileName() - rxBus.send(EventUpdateOverview(OverviewData.Property.PROFILE)) + overviewData.profileNameWithRemainingTime = profileFunction.getProfileNameWithRemainingTime() + overviewBus.send(EventUpdateOverview(from, OverviewData.Property.PROFILE)) + } + + private fun loadTemporaryBasal(from: String) { + overviewData.temporaryBasal = iobCobCalculator.getTempBasalIncludingConvertedExtended(dateUtil.now()) + overviewBus.send(EventUpdateOverview(from, OverviewData.Property.TEMPORARY_BASAL)) + } + + private fun loadExtendedBolus(from: String) { + overviewData.extendedBolus = iobCobCalculator.getExtendedBolus(dateUtil.now()) + overviewBus.send(EventUpdateOverview(from, OverviewData.Property.EXTENDED_BOLUS)) + } + + 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(EventUpdateOverview(from, OverviewData.Property.TEMPORARY_TARGET)) + } + + private fun loadAsData(from: String) { + overviewData.lastAutosensData = iobCobCalculator.ads.getLastAutosensData("Overview", aapsLogger, dateUtil) + overviewBus.send(EventUpdateOverview(from, OverviewData.Property.SENSITIVITY)) + } + + 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(EventUpdateOverview(from, OverviewData.Property.BG)) + } + + private fun loadIobCobResults(from: String) { + overviewData.bolusIob = iobCobCalculator.calculateIobFromBolus().round() + overviewData.basalIob = iobCobCalculator.calculateIobFromTempBasalsIncludingConvertedExtended().round() + overviewData.cobInfo = iobCobCalculator.getCobInfo(false, "Overview COB") + val lastCarbs = repository.getLastCarbsRecordWrapped().blockingGet() + overviewData.lastCarbsTime = if (lastCarbs is ValueWrapper.Existing) lastCarbs.value.timestamp else 0L + + overviewBus.send(EventUpdateOverview(from, OverviewData.Property.IOB_COB)) } @Synchronized - private fun loadTemporaryBasal() { - overviewData.temporaryBasal = iobCobCalculator.getTempBasalIncludingConvertedExtended(dateUtil.now()) - rxBus.send(EventUpdateOverview(OverviewData.Property.TEMPORARY_BASAL)) + @Suppress("SameParameterValue", "UNUSED_PARAMETER") + private fun prepareBgData(from: String) { +// val start = dateUtil.now() + var maxBgValue = Double.MIN_VALUE + overviewData.bgReadingsArray = repository.compatGetBgReadingsDataFromTime(overviewData.fromTime, overviewData.toTime, false).blockingGet() + val bgListArray: MutableList = ArrayList() + for (bg in overviewData.bgReadingsArray) { + if (bg.timestamp < overviewData.fromTime || bg.timestamp > overviewData.toTime) continue + if (bg.value > maxBgValue) maxBgValue = bg.value + bgListArray.add(GlucoseValueDataPoint(bg, defaultValueHelper, profileFunction, resourceHelper)) + } + overviewData.bgReadingGraphSeries = PointsWithLabelGraphSeries(Array(bgListArray.size) { i -> bgListArray[i] }) + overviewData.maxBgValue = Profile.fromMgdlToUnits(maxBgValue, profileFunction.getUnits()) + if (defaultValueHelper.determineHighLine() > maxBgValue) overviewData.maxBgValue = defaultValueHelper.determineHighLine() + overviewData.maxBgValue = addUpperChartMargin(overviewData.maxBgValue) +// profiler.log(LTag.UI, "prepareBgData() $from", start) } + + @Suppress("UNUSED_PARAMETER") + @Synchronized + private fun preparePredictions(from: String) { +// val start = dateUtil.now() + val apsResult = if (config.APS) loopPlugin.lastRun?.constraintsProcessed else nsDeviceStatus.getAPSResult(injector) + val predictionsAvailable = if (config.APS) loopPlugin.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 = overviewData.rangeToDisplay - predictionHours + overviewData.toTime = calendar.timeInMillis + 100000 // little bit more to avoid wrong rounding - GraphView specific + overviewData.fromTime = overviewData.toTime - T.hours(hoursToFetch.toLong()).msecs() + overviewData.endTime = overviewData.toTime + T.hours(predictionHours.toLong()).msecs() + } else { + overviewData.toTime = calendar.timeInMillis + 100000 // little bit more to avoid wrong rounding - GraphView specific + overviewData.fromTime = overviewData.toTime - T.hours(overviewData.rangeToDisplay.toLong()).msecs() + overviewData.endTime = overviewData.toTime + } + + val bgListArray: MutableList = ArrayList() + val predictions: MutableList? = apsResult?.predictions + ?.map { bg -> GlucoseValueDataPoint(bg, defaultValueHelper, profileFunction, resourceHelper) } + ?.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) + } + overviewData.predictionsGraphSeries = PointsWithLabelGraphSeries(Array(bgListArray.size) { i -> bgListArray[i] }) +// profiler.log(LTag.UI, "preparePredictions() $from", start) + } + + @Synchronized + @Suppress("SameParameterValue", "UNUSED_PARAMETER") + private 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 = ArrayList() + for (inMemoryGlucoseValue in bucketedData) { + if (inMemoryGlucoseValue.timestamp < overviewData.fromTime || inMemoryGlucoseValue.timestamp > overviewData.toTime) continue + bucketedListArray.add(InMemoryGlucoseValueDataPoint(inMemoryGlucoseValue, profileFunction, resourceHelper)) + } + overviewData.bucketedGraphSeries = PointsWithLabelGraphSeries(Array(bucketedListArray.size) { i -> bucketedListArray[i] }) +// profiler.log(LTag.UI, "prepareBucketedData() $from", start) + } + + @Suppress("UNUSED_PARAMETER") + @Synchronized + private fun prepareBasalData(from: String) { +// val start = dateUtil.now() + overviewData.maxBasalValueFound = 0.0 + 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 = overviewData.fromTime + while (time < overviewData.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, overviewData.basalScale)) + tempBasalArray.add(ScaledDataPoint(time, tempBasalValue.also { basal = it }, overviewData.basalScale)) + } + if (lastBaseBasal != 0.0) { + baseBasalArray.add(ScaledDataPoint(time, lastBaseBasal, overviewData.basalScale)) + baseBasalArray.add(ScaledDataPoint(time, 0.0, overviewData.basalScale)) + lastBaseBasal = 0.0 + } + } else { + if (baseBasalValue != lastBaseBasal) { + baseBasalArray.add(ScaledDataPoint(time, lastBaseBasal, overviewData.basalScale)) + baseBasalArray.add(ScaledDataPoint(time, baseBasalValue.also { basal = it }, overviewData.basalScale)) + lastBaseBasal = baseBasalValue + } + if (lastTempBasal != 0.0) { + tempBasalArray.add(ScaledDataPoint(time, lastTempBasal, overviewData.basalScale)) + tempBasalArray.add(ScaledDataPoint(time, 0.0, overviewData.basalScale)) + } + } + if (baseBasalValue != lastLineBasal) { + basalLineArray.add(ScaledDataPoint(time, lastLineBasal, overviewData.basalScale)) + basalLineArray.add(ScaledDataPoint(time, baseBasalValue, overviewData.basalScale)) + } + if (absoluteLineValue != lastAbsoluteLineBasal) { + absoluteBasalLineArray.add(ScaledDataPoint(time, lastAbsoluteLineBasal, overviewData.basalScale)) + absoluteBasalLineArray.add(ScaledDataPoint(time, basal, overviewData.basalScale)) + } + lastAbsoluteLineBasal = absoluteLineValue + lastLineBasal = baseBasalValue + lastTempBasal = tempBasalValue + overviewData.maxBasalValueFound = max(overviewData.maxBasalValueFound, max(tempBasalValue, baseBasalValue)) + time += 60 * 1000L + } + + // final points + basalLineArray.add(ScaledDataPoint(overviewData.toTime, lastLineBasal, overviewData.basalScale)) + baseBasalArray.add(ScaledDataPoint(overviewData.toTime, lastBaseBasal, overviewData.basalScale)) + tempBasalArray.add(ScaledDataPoint(overviewData.toTime, lastTempBasal, overviewData.basalScale)) + absoluteBasalLineArray.add(ScaledDataPoint(overviewData.toTime, lastAbsoluteLineBasal, overviewData.basalScale)) + + // create series + overviewData.baseBasalGraphSeries = LineGraphSeries(Array(baseBasalArray.size) { i -> baseBasalArray[i] }).also { + it.isDrawBackground = true + it.backgroundColor = resourceHelper.gc(R.color.basebasal) + it.thickness = 0 + } + overviewData.tempBasalGraphSeries = LineGraphSeries(Array(tempBasalArray.size) { i -> tempBasalArray[i] }).also { + it.isDrawBackground = true + it.backgroundColor = resourceHelper.gc(R.color.tempbasal) + it.thickness = 0 + } + overviewData.basalLineGraphSeries = LineGraphSeries(Array(basalLineArray.size) { i -> basalLineArray[i] }).also { + it.setCustomPaint(Paint().also { paint -> + paint.style = Paint.Style.STROKE + paint.strokeWidth = resourceHelper.getDisplayMetrics().scaledDensity * 2 + paint.pathEffect = DashPathEffect(floatArrayOf(2f, 4f), 0f) + paint.color = resourceHelper.gc(R.color.basal) + }) + } + overviewData.absoluteBasalGraphSeries = LineGraphSeries(Array(absoluteBasalLineArray.size) { i -> absoluteBasalLineArray[i] }).also { + it.setCustomPaint(Paint().also { absolutePaint -> + absolutePaint.style = Paint.Style.STROKE + absolutePaint.strokeWidth = resourceHelper.getDisplayMetrics().scaledDensity * 2 + absolutePaint.color = resourceHelper.gc(R.color.basal) + }) + } +// profiler.log(LTag.UI, "prepareBasalData() $from", start) + } + + @Suppress("UNUSED_PARAMETER") + @Synchronized + private fun prepareTemporaryTargetData(from: String) { +// val start = dateUtil.now() + val profile = overviewData.profile ?: return + val units = profileFunction.getUnits() + var toTime = overviewData.toTime + val targetsSeriesArray: MutableList = ArrayList() + var lastTarget = -1.0 + loopPlugin.lastRun?.constraintsProcessed?.let { toTime = max(it.latestPredictionsTime, toTime) } + var time = overviewData.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 + overviewData.temporaryTargetSeries = LineGraphSeries(Array(targetsSeriesArray.size) { i -> targetsSeriesArray[i] }).also { + it.isDrawBackground = false + it.color = resourceHelper.gc(R.color.tempTargetBackground) + it.thickness = 2 + } +// profiler.log(LTag.UI, "prepareTemporaryTargetData() $from", start) + } + + @Suppress("UNUSED_PARAMETER") + @Synchronized + private fun prepareTreatmentsData(from: String) { +// val start = dateUtil.now() + overviewData.maxTreatmentsValue = 0.0 + val filteredTreatments: MutableList = ArrayList() + repository.getBolusesIncludingInvalidFromTimeToTime(overviewData.fromTime, overviewData.endTime, true).blockingGet() + .map { BolusDataPoint(it, resourceHelper, activePlugin, defaultValueHelper) } + .filter { it.data.type != Bolus.Type.SMB || it.data.isValid } + .forEach { + it.y = getNearestBg(it.x.toLong()) + filteredTreatments.add(it) + } + repository.getCarbsIncludingInvalidFromTimeToTimeExpanded(overviewData.fromTime, overviewData.endTime, true).blockingGet() + .map { CarbsDataPoint(it, resourceHelper) } + .forEach { + it.y = getNearestBg(it.x.toLong()) + filteredTreatments.add(it) + } + + // ProfileSwitch + repository.getEffectiveProfileSwitchDataFromTimeToTime(overviewData.fromTime, overviewData.endTime, true).blockingGet() + .map { EffectiveProfileSwitchDataPoint(it) } + .forEach(filteredTreatments::add) + + // Extended bolus + if (!activePlugin.activePump.isFakingTempsByExtendedBoluses) { + repository.getExtendedBolusDataFromTimeToTime(overviewData.fromTime, overviewData.endTime, true).blockingGet() + .map { ExtendedBolusDataPoint(it) } + .filter { it.duration != 0L } + .forEach { + it.y = getNearestBg(it.x.toLong()) + filteredTreatments.add(it) + } + } + + // Careportal + repository.compatGetTherapyEventDataFromToTime(overviewData.fromTime - T.hours(6).msecs(), overviewData.endTime).blockingGet() + .map { TherapyEventDataPoint(it, resourceHelper, profileFunction, translator) } + .filterTimeframe(overviewData.fromTime, overviewData.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 { overviewData.maxTreatmentsValue = maxOf(overviewData.maxTreatmentsValue, it) } + + overviewData.treatmentsSeries = PointsWithLabelGraphSeries(filteredTreatments.toTypedArray()) +// profiler.log(LTag.UI, "prepareTreatmentsData() $from", start) + } + + @Suppress("UNUSED_PARAMETER") + @Synchronized + private fun prepareIobAutosensData(from: String) { +// val start = dateUtil.now() + val iobArray: MutableList = ArrayList() + val absIobArray: MutableList = ArrayList() + overviewData.maxIobValueFound = Double.MIN_VALUE + var lastIob = 0.0 + var absLastIob = 0.0 + var time = overviewData.fromTime + + val minFailOverActiveList: MutableList = ArrayList() + val cobArray: MutableList = ArrayList() + overviewData.maxCobValueFound = Double.MIN_VALUE + var lastCob = 0 + + val actArrayHist: MutableList = ArrayList() + val actArrayPrediction: MutableList = ArrayList() + val now = dateUtil.now().toDouble() + overviewData.maxIAValue = 0.0 + + val bgiArrayHist: MutableList = ArrayList() + val bgiArrayPrediction: MutableList = ArrayList() + overviewData.maxBGIValue = Double.MIN_VALUE + + val devArray: MutableList = ArrayList() + overviewData.maxDevValueFound = Double.MIN_VALUE + + val ratioArray: MutableList = ArrayList() + overviewData.maxRatioValueFound = 5.0 //even if sens data equals 0 for all the period, minimum scale is between 95% and 105% + overviewData.minRatioValueFound = -5.0 + + val dsMaxArray: MutableList = ArrayList() + val dsMinArray: MutableList = ArrayList() + overviewData.maxFromMaxValueFound = Double.MIN_VALUE + overviewData.maxFromMinValueFound = Double.MIN_VALUE + + val adsData = iobCobCalculator.ads.clone() + + while (time <= overviewData.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, overviewData.iobScale)) + iobArray.add(ScaledDataPoint(time, iob.iob, overviewData.iobScale)) + overviewData.maxIobValueFound = maxOf(overviewData.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, overviewData.iobScale)) + absIobArray.add(ScaledDataPoint(time, absIob.iob, overviewData.iobScale)) + overviewData.maxIobValueFound = maxOf(overviewData.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(), overviewData.cobScale)) + cobArray.add(ScaledDataPoint(time, cob.toDouble(), overviewData.cobScale)) + overviewData.maxCobValueFound = max(overviewData.maxCobValueFound, cob.toDouble()) + lastCob = cob + } + if (autosensData.failoverToMinAbsorbtionRate) { + autosensData.setScale(overviewData.cobScale) + autosensData.setChartTime(time) + minFailOverActiveList.add(autosensData) + } + } + + // ACTIVITY + if (time <= now) actArrayHist.add(ScaledDataPoint(time, iob.activity, overviewData.actScale)) + else actArrayPrediction.add(ScaledDataPoint(time, iob.activity, overviewData.actScale)) + overviewData.maxIAValue = max(overviewData.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, overviewData.bgiScale)) + else bgiArrayPrediction.add(ScaledDataPoint(time, bgi, overviewData.bgiScale)) + overviewData.maxBGIValue = max(overviewData.maxBGIValue, max(abs(bgi), deviation)) + + // DEVIATIONS + if (autosensData != null) { + var color = resourceHelper.gc(R.color.deviationblack) // "=" + if (autosensData.type == "" || autosensData.type == "non-meal") { + if (autosensData.pastSensitivity == "C") color = resourceHelper.gc(R.color.deviationgrey) + if (autosensData.pastSensitivity == "+") color = resourceHelper.gc(R.color.deviationgreen) + if (autosensData.pastSensitivity == "-") color = resourceHelper.gc(R.color.deviationred) + } else if (autosensData.type == "uam") { + color = resourceHelper.gc(R.color.uam) + } else if (autosensData.type == "csf") { + color = resourceHelper.gc(R.color.deviationgrey) + } + devArray.add(DeviationDataPoint(time.toDouble(), autosensData.deviation, color, overviewData.devScale)) + overviewData.maxDevValueFound = maxOf(overviewData.maxDevValueFound, abs(autosensData.deviation), abs(bgi)) + } + + // RATIO + if (autosensData != null) { + ratioArray.add(ScaledDataPoint(time, 100.0 * (autosensData.autosensResult.ratio - 1), overviewData.ratioScale)) + overviewData.maxRatioValueFound = max(overviewData.maxRatioValueFound, 100.0 * (autosensData.autosensResult.ratio - 1)) + overviewData.minRatioValueFound = min(overviewData.minRatioValueFound, 100.0 * (autosensData.autosensResult.ratio - 1)) + } + + // DEV SLOPE + if (autosensData != null) { + dsMaxArray.add(ScaledDataPoint(time, autosensData.slopeFromMaxDeviation, overviewData.dsMaxScale)) + dsMinArray.add(ScaledDataPoint(time, autosensData.slopeFromMinDeviation, overviewData.dsMinScale)) + overviewData.maxFromMaxValueFound = max(overviewData.maxFromMaxValueFound, abs(autosensData.slopeFromMaxDeviation)) + overviewData.maxFromMinValueFound = max(overviewData.maxFromMinValueFound, abs(autosensData.slopeFromMinDeviation)) + } + + time += 5 * 60 * 1000L + } + // IOB + overviewData.iobSeries = FixedLineGraphSeries(Array(iobArray.size) { i -> iobArray[i] }).also { + it.isDrawBackground = true + it.backgroundColor = -0x7f000001 and resourceHelper.gc(R.color.iob) //50% + it.color = resourceHelper.gc(R.color.iob) + it.thickness = 3 + } + overviewData.absIobSeries = FixedLineGraphSeries(Array(absIobArray.size) { i -> absIobArray[i] }).also { + it.isDrawBackground = true + it.backgroundColor = -0x7f000001 and resourceHelper.gc(R.color.iob) //50% + it.color = resourceHelper.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 = ArrayList() + val iobPredictionArray = iobCobCalculator.calculateIobArrayForSMB(lastAutosensResult, SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget) + for (i in iobPredictionArray) { + iobPrediction.add(i.setColor(resourceHelper.gc(R.color.iobPredAS))) + overviewData.maxIobValueFound = max(overviewData.maxIobValueFound, abs(i.iob)) + } + overviewData.iobPredictions1Series = PointsWithLabelGraphSeries(Array(iobPrediction.size) { i -> iobPrediction[i] }) + val iobPrediction2: MutableList = ArrayList() + val iobPredictionArray2 = iobCobCalculator.calculateIobArrayForSMB(AutosensResult(), SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget) + for (i in iobPredictionArray2) { + iobPrediction2.add(i.setColor(resourceHelper.gc(R.color.iobPred))) + overviewData.maxIobValueFound = max(overviewData.maxIobValueFound, abs(i.iob)) + } + overviewData.iobPredictions2Series = PointsWithLabelGraphSeries(Array(iobPrediction2.size) { i -> iobPrediction2[i] }) + aapsLogger.debug(LTag.AUTOSENS, "IOB prediction for AS=" + DecimalFormatter.to2Decimal(lastAutosensResult.ratio) + ": " + iobCobCalculator.iobArrayToString(iobPredictionArray)) + aapsLogger.debug(LTag.AUTOSENS, "IOB prediction for AS=" + DecimalFormatter.to2Decimal(1.0) + ": " + iobCobCalculator.iobArrayToString(iobPredictionArray2)) + } else { + overviewData.iobPredictions1Series = PointsWithLabelGraphSeries() + overviewData.iobPredictions2Series = PointsWithLabelGraphSeries() + } + + // COB + overviewData.cobSeries = FixedLineGraphSeries(Array(cobArray.size) { i -> cobArray[i] }).also { + it.isDrawBackground = true + it.backgroundColor = -0x7f000001 and resourceHelper.gc(R.color.cob) //50% + it.color = resourceHelper.gc(R.color.cob) + it.thickness = 3 + } + overviewData.cobMinFailOverSeries = PointsWithLabelGraphSeries(Array(minFailOverActiveList.size) { i -> minFailOverActiveList[i] }) + + // ACTIVITY + overviewData.activitySeries = FixedLineGraphSeries(Array(actArrayHist.size) { i -> actArrayHist[i] }).also { + it.isDrawBackground = false + it.color = resourceHelper.gc(R.color.activity) + it.thickness = 3 + } + overviewData.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 = resourceHelper.gc(R.color.activity) + }) + } + + // BGI + overviewData.minusBgiSeries = FixedLineGraphSeries(Array(bgiArrayHist.size) { i -> bgiArrayHist[i] }).also { + it.isDrawBackground = false + it.color = resourceHelper.gc(R.color.bgi) + it.thickness = 3 + } + overviewData.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 = resourceHelper.gc(R.color.bgi) + }) + } + + // DEVIATIONS + overviewData.deviationsSeries = BarGraphSeries(Array(devArray.size) { i -> devArray[i] }).also { + it.setValueDependentColor { data: DeviationDataPoint -> data.color } + } + + // RATIO + overviewData.ratioSeries = LineGraphSeries(Array(ratioArray.size) { i -> ratioArray[i] }).also { + it.color = resourceHelper.gc(R.color.ratio) + it.thickness = 3 + } + + // DEV SLOPE + overviewData.dsMaxSeries = LineGraphSeries(Array(dsMaxArray.size) { i -> dsMaxArray[i] }).also { + it.color = resourceHelper.gc(R.color.devslopepos) + it.thickness = 3 + } + overviewData.dsMinSeries = LineGraphSeries(Array(dsMinArray.size) { i -> dsMinArray[i] }).also { + it.color = resourceHelper.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 { + overviewData.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/events/EventUpdateOverview.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverview.kt index 4bf8cc82e8..969ed4feed 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverview.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverview.kt @@ -3,4 +3,4 @@ package info.nightscout.androidaps.plugins.general.overview.events import info.nightscout.androidaps.events.Event import info.nightscout.androidaps.plugins.general.overview.OverviewData -class EventUpdateOverview(val what: OverviewData.Property) : Event() \ No newline at end of file +class EventUpdateOverview(val from: String, val what: OverviewData.Property) : 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 4d17a119be..c2055e61e8 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 @@ -4,52 +4,39 @@ import android.graphics.Color import android.graphics.DashPathEffect import android.graphics.Paint import com.jjoe64.graphview.GraphView -import com.jjoe64.graphview.series.BarGraphSeries import com.jjoe64.graphview.series.DataPoint import com.jjoe64.graphview.series.LineGraphSeries import com.jjoe64.graphview.series.Series 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.extensions.target -import info.nightscout.androidaps.interfaces.* +import info.nightscout.androidaps.interfaces.GlucoseUnit +import info.nightscout.androidaps.interfaces.ProfileFunction import info.nightscout.androidaps.logging.AAPSLogger -import info.nightscout.androidaps.logging.LTag -import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults -import info.nightscout.androidaps.plugins.general.overview.graphExtensions.* -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult -import info.nightscout.androidaps.utils.* +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.utils.DefaultValueHelper +import info.nightscout.androidaps.utils.Round import info.nightscout.androidaps.utils.resources.ResourceHelper import java.util.* import javax.inject.Inject import kotlin.math.abs import kotlin.math.max -import kotlin.math.min class GraphData( injector: HasAndroidInjector, - private val graph: GraphView, - private val iobCobCalculator: IobCobCalculator + private val graph: GraphView ) { - // IobCobCalculatorPlugin Cannot be injected: HistoryBrowser @Inject lateinit var aapsLogger: AAPSLogger @Inject lateinit var profileFunction: ProfileFunction @Inject lateinit var resourceHelper: ResourceHelper - @Inject lateinit var activePlugin: ActivePlugin - @Inject lateinit var databaseHelper: DatabaseHelperInterface - @Inject lateinit var repository: AppRepository - @Inject lateinit var dateUtil: DateUtil @Inject lateinit var defaultValueHelper: DefaultValueHelper - @Inject lateinit var translator: Translator + @Inject lateinit var overviewData: OverviewData var maxY = Double.MIN_VALUE private var minY = Double.MAX_VALUE - private var bgReadingsArray: List? = null private val units: GlucoseUnit private val series: MutableList> = ArrayList() @@ -58,585 +45,130 @@ class GraphData( units = profileFunction.getUnits() } - fun addBucketedData(fromTime: Long, toTime: Long) { - val bucketedData = iobCobCalculator.ads.getBucketedDataTableCopy() ?: return - if (bucketedData.isEmpty()) { - aapsLogger.debug("No bucketed data.") - return - } - val bucketedListArray: MutableList = ArrayList() - for (inMemoryGlucoseValue in bucketedData) { - if (inMemoryGlucoseValue.timestamp < fromTime || inMemoryGlucoseValue.timestamp > toTime) continue - bucketedListArray.add(InMemoryGlucoseValueDataPoint(inMemoryGlucoseValue, profileFunction, resourceHelper)) - } - addSeries(PointsWithLabelGraphSeries(Array(bucketedListArray.size) { i -> bucketedListArray[i] })) + fun addBucketedData() { + addSeries(overviewData.bucketedGraphSeries) } - fun addBgReadings(fromTime: Long, toTime: Long, highLine: Double, predictions: MutableList?) { - var maxBgValue = Double.MIN_VALUE - bgReadingsArray = repository.compatGetBgReadingsDataFromTime(fromTime, toTime, false).blockingGet() - if (bgReadingsArray?.isEmpty() != false) { - aapsLogger.debug("No BG data.") - maxY = if (units == GlucoseUnit.MGDL) 180.0 else 10.0 - minY = 0.0 - return - } - val bgListArray: MutableList = 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, resourceHelper)) - } - if (predictions != null) { - predictions.sortWith(Comparator { o1: GlucoseValueDataPoint, o2: GlucoseValueDataPoint -> o1.x.compareTo(o2.x) }) - for (prediction in predictions) if (prediction.data.value >= 40) bgListArray.add(prediction) - } - maxBgValue = Profile.fromMgdlToUnits(maxBgValue, units) - maxBgValue = addUpperChartMargin(maxBgValue) - if (highLine > maxBgValue) maxBgValue = highLine - maxY = maxBgValue + fun addBgReadings(addPredictions: Boolean) { + maxY = if (overviewData.bgReadingsArray.isEmpty()) { + if (units == GlucoseUnit.MGDL) 180.0 else 10.0 + } else overviewData.maxBgValue minY = 0.0 - addSeries(PointsWithLabelGraphSeries(Array(bgListArray.size) { i -> bgListArray[i] })) + addSeries(overviewData.bgReadingGraphSeries) + if (addPredictions) addSeries(overviewData.predictionsGraphSeries) } - private fun addUpperChartMargin(maxBgValue: Double) = - if (units == GlucoseUnit.MGDL) Round.roundTo(maxBgValue, 40.0) + 80 else Round.roundTo(maxBgValue, 2.0) + 4 - fun addInRangeArea(fromTime: Long, toTime: Long, lowLine: Double, highLine: Double) { - val inRangeAreaSeries: AreaGraphSeries val inRangeAreaDataPoints = arrayOf( DoubleDataPoint(fromTime.toDouble(), lowLine, highLine), DoubleDataPoint(toTime.toDouble(), lowLine, highLine) ) - inRangeAreaSeries = AreaGraphSeries(inRangeAreaDataPoints) - inRangeAreaSeries.color = 0 - inRangeAreaSeries.isDrawBackground = true - inRangeAreaSeries.backgroundColor = resourceHelper.gc(R.color.inrangebackground) - addSeries(inRangeAreaSeries) - } - - // scale in % of vertical size (like 0.3) - fun addBasals(fromTime: Long, toTime: Long, scale: Double) { - var maxBasalValueFound = 0.0 - val basalScale = Scale() - 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 = 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 - addSeries(LineGraphSeries(Array(baseBasalArray.size) { i -> baseBasalArray[i] }).also { + addSeries(AreaGraphSeries(inRangeAreaDataPoints).also { + it.color = 0 it.isDrawBackground = true - it.backgroundColor = resourceHelper.gc(R.color.basebasal) - it.thickness = 0 - }) - addSeries(LineGraphSeries(Array(tempBasalArray.size) { i -> tempBasalArray[i] }).also { - it.isDrawBackground = true - it.backgroundColor = resourceHelper.gc(R.color.tempbasal) - it.thickness = 0 - }) - addSeries(LineGraphSeries(Array(basalLineArray.size) { i -> basalLineArray[i] }).also { - it.setCustomPaint(Paint().also { paint -> - paint.style = Paint.Style.STROKE - paint.strokeWidth = resourceHelper.getDisplayMetrics().scaledDensity * 2 - paint.pathEffect = DashPathEffect(floatArrayOf(2f, 4f), 0f) - paint.color = resourceHelper.gc(R.color.basal) - }) - }) - addSeries(LineGraphSeries(Array(absoluteBasalLineArray.size) { i -> absoluteBasalLineArray[i] }).also { - it.setCustomPaint(Paint().also { absolutePaint -> - absolutePaint.style = Paint.Style.STROKE - absolutePaint.strokeWidth = resourceHelper.getDisplayMetrics().scaledDensity * 2 - absolutePaint.color = resourceHelper.gc(R.color.basal) - }) - }) - basalScale.setMultiplier(maxY * scale / maxBasalValueFound) - } - - fun addTargetLine(fromTime: Long, toTimeParam: Long, profile: Profile, lastRun: LoopInterface.LastRun?) { - var toTime = toTimeParam - val targetsSeriesArray: MutableList = ArrayList() - var lastTarget = -1.0 - 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 - addSeries(LineGraphSeries(Array(targetsSeriesArray.size) { i -> targetsSeriesArray[i] }).also { - it.isDrawBackground = false - it.color = resourceHelper.gc(R.color.tempTargetBackground) - it.thickness = 2 + it.backgroundColor = resourceHelper.gc(R.color.inrangebackground) }) } - fun addTreatments(fromTime: Long, endTime: Long) { - val filteredTreatments: MutableList = ArrayList() - repository.getBolusesIncludingInvalidFromTimeToTime(fromTime, endTime, true).blockingGet() - .map { BolusDataPoint(it, resourceHelper, activePlugin, defaultValueHelper) } - .filter { it.data.type != Bolus.Type.SMB || it.data.isValid } - .forEach { - it.y = getNearestBg(it.x.toLong()) - filteredTreatments.add(it) - } - repository.getCarbsIncludingInvalidFromTimeToTimeExpanded(fromTime, endTime, true).blockingGet() - .map { CarbsDataPoint(it, resourceHelper) } - .forEach { - it.y = getNearestBg(it.x.toLong()) - filteredTreatments.add(it) - } - - // ProfileSwitch - repository.getEffectiveProfileSwitchDataFromTimeToTime(fromTime, endTime, true).blockingGet() - .map { EffectiveProfileSwitchDataPoint(it) } - .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, resourceHelper, 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 { maxY = maxOf(maxY, it) } - - addSeries(PointsWithLabelGraphSeries(filteredTreatments.toTypedArray())) + fun addBasals() { + val scale = defaultValueHelper.determineLowLine() / maxY / 1.2 + addSeries(overviewData.baseBasalGraphSeries) + addSeries(overviewData.tempBasalGraphSeries) + addSeries(overviewData.basalLineGraphSeries) + addSeries(overviewData.absoluteBasalGraphSeries) + overviewData.basalScale.setMultiplier(maxY * scale / overviewData.maxBasalValueFound) } - private fun getNearestBg(date: Long): Double { - bgReadingsArray?.let { bgReadingsArray -> - for (reading in bgReadingsArray) { - if (reading.timestamp > date) continue - return Profile.fromMgdlToUnits(reading.value, units) - } - return if (bgReadingsArray.isNotEmpty()) Profile.fromMgdlToUnits(bgReadingsArray[0].value, units) else Profile.fromMgdlToUnits(100.0, units) - } ?: return Profile.fromMgdlToUnits(100.0, units) + fun addTargetLine() { + addSeries(overviewData.temporaryTargetSeries) } - fun addActivity(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) { - val actArrayHist: MutableList = ArrayList() - val actArrayPrediction: MutableList = ArrayList() - val now = System.currentTimeMillis().toDouble() - val actScale = Scale() - var total: IobTotal - var maxIAValue = 0.0 - var time = fromTime - while (time <= toTime) { - val profile = profileFunction.getProfile(time) - if (profile == null) { - time += 5 * 60 * 1000L - continue - } - total = iobCobCalculator.calculateFromTreatmentsAndTemps(time, profile) - val act: Double = total.activity - if (time <= now) actArrayHist.add(ScaledDataPoint(time, act, actScale)) else actArrayPrediction.add(ScaledDataPoint(time, act, actScale)) - maxIAValue = max(maxIAValue, abs(act)) - time += 5 * 60 * 1000L - } - addSeries(FixedLineGraphSeries(Array(actArrayHist.size) { i -> actArrayHist[i] }).also { - it.isDrawBackground = false - it.color = resourceHelper.gc(R.color.activity) - it.thickness = 3 - }) - addSeries(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 = resourceHelper.gc(R.color.activity) - }) - }) - if (useForScale) { - maxY = maxIAValue - minY = -maxIAValue - } - actScale.setMultiplier(maxY * scale / maxIAValue) + fun addTreatments() { + maxY = maxOf(maxY, overviewData.maxTreatmentsValue) + addSeries(overviewData.treatmentsSeries) + } + + fun addActivity(scale: Double) { + addSeries(overviewData.activitySeries) + addSeries(overviewData.activityPredictionSeries) + overviewData.actScale.setMultiplier(maxY * scale / overviewData.maxIAValue) } //Function below show -BGI to be able to compare curves with deviations - fun addMinusBGI(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double, devBgiScale: Boolean) { - val bgiArrayHist: MutableList = ArrayList() - val bgiArrayPrediction: MutableList = ArrayList() - val now = System.currentTimeMillis().toDouble() - val bgiScale = Scale() - var total: IobTotal - var maxBGIValue = 0.0 - var time = fromTime - while (time <= toTime) { - val profile = profileFunction.getProfile(time) - if (profile == null) { - time += 5 * 60 * 1000L - continue - } - val deviation = if (devBgiScale) iobCobCalculator.ads.getAutosensDataAtTime(time)?.deviation - ?: 0.0 else 0.0 - - total = iobCobCalculator.calculateFromTreatmentsAndTemps(time, profile) - val bgi: Double = total.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)) - time += 5 * 60 * 1000L - } - addSeries(FixedLineGraphSeries(Array(bgiArrayHist.size) { i -> bgiArrayHist[i] }).also { - it.isDrawBackground = false - it.color = resourceHelper.gc(R.color.bgi) - it.thickness = 3 - }) - addSeries(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 = resourceHelper.gc(R.color.bgi) - }) - }) + fun addMinusBGI(useForScale: Boolean, scale: Double) { if (useForScale) { - maxY = maxBGIValue - minY = -maxBGIValue + maxY = overviewData.maxBGIValue + minY = -overviewData.maxBGIValue } - bgiScale.setMultiplier(maxY * scale / maxBGIValue) + overviewData.bgiScale.setMultiplier(maxY * scale / overviewData.maxBGIValue) + addSeries(overviewData.minusBgiSeries) + addSeries(overviewData.minusBgiHistSeries) } // scale in % of vertical size (like 0.3) - fun addIob(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double, showPrediction: Boolean, absScale: Boolean) { - val iobSeries: FixedLineGraphSeries - val iobArray: MutableList = ArrayList() - var maxIobValueFound = Double.MIN_VALUE - var lastIob = 0.0 - val iobScale = Scale() - var time = fromTime - while (time <= toTime) { - val profile = profileFunction.getProfile(time) - if (profile == null) { - time += 5 * 60 * 1000L - continue - } - var absIob = 0.0 - val iob: Double = iobCobCalculator.calculateFromTreatmentsAndTemps(time, profile).iob - if (absScale) absIob = iobCobCalculator.calculateAbsInsulinFromTreatmentsAndTemps(time).iob - if (abs(lastIob - iob) > 0.02) { - if (abs(lastIob - iob) > 0.2) iobArray.add(ScaledDataPoint(time, lastIob, iobScale)) - iobArray.add(ScaledDataPoint(time, iob, iobScale)) - maxIobValueFound = if (absScale) max(maxIobValueFound, abs(absIob)) else max(maxIobValueFound, abs(iob)) - lastIob = iob - } - time += 5 * 60 * 1000L - } - iobSeries = FixedLineGraphSeries(Array(iobArray.size) { i -> iobArray[i] }).also { - it.isDrawBackground = true - it.backgroundColor = -0x7f000001 and resourceHelper.gc(R.color.iob) //50% - it.color = resourceHelper.gc(R.color.iob) - it.thickness = 3 - } - if (showPrediction) { - val autosensData = iobCobCalculator.getLastAutosensDataWithWaitForCalculationFinish("GraphData") - val lastAutosensResult = autosensData?.autosensResult ?: AutosensResult() - val isTempTarget = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet() is ValueWrapper.Existing - val iobPrediction: MutableList = ArrayList() - val iobPredictionArray = iobCobCalculator.calculateIobArrayForSMB(lastAutosensResult, SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget) - for (i in iobPredictionArray) { - iobPrediction.add(i.setColor(resourceHelper.gc(R.color.iobPredAS))) - maxIobValueFound = max(maxIobValueFound, abs(i.iob)) - } - addSeries(PointsWithLabelGraphSeries(Array(iobPrediction.size) { i -> iobPrediction[i] })) - val iobPrediction2: MutableList = ArrayList() - val iobPredictionArray2 = iobCobCalculator.calculateIobArrayForSMB(AutosensResult(), SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget) - for (i in iobPredictionArray2) { - iobPrediction2.add(i.setColor(resourceHelper.gc(R.color.iobPred))) - maxIobValueFound = max(maxIobValueFound, abs(i.iob)) - } - addSeries(PointsWithLabelGraphSeries(Array(iobPrediction2.size) { i -> iobPrediction2[i] })) - aapsLogger.debug(LTag.AUTOSENS, "IOB prediction for AS=" + DecimalFormatter.to2Decimal(lastAutosensResult.ratio) + ": " + iobCobCalculator.iobArrayToString(iobPredictionArray)) - aapsLogger.debug(LTag.AUTOSENS, "IOB prediction for AS=" + DecimalFormatter.to2Decimal(1.0) + ": " + iobCobCalculator.iobArrayToString(iobPredictionArray2)) - } + fun addIob(useForScale: Boolean, scale: Double) { if (useForScale) { - maxY = maxIobValueFound - minY = -maxIobValueFound + maxY = overviewData.maxIobValueFound + minY = -overviewData.maxIobValueFound } - iobScale.setMultiplier(maxY * scale / maxIobValueFound) - addSeries(iobSeries) + overviewData.iobScale.setMultiplier(maxY * scale / overviewData.maxIobValueFound) + addSeries(overviewData.iobSeries) + addSeries(overviewData.iobPredictions1Series) + addSeries(overviewData.iobPredictions2Series) } // scale in % of vertical size (like 0.3) - fun addAbsIob(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) { - val iobSeries: FixedLineGraphSeries - val iobArray: MutableList = ArrayList() - var maxIobValueFound = Double.MIN_VALUE - var lastIob = 0.0 - val iobScale = Scale() - var time = fromTime - while (time <= toTime) { - val profile = profileFunction.getProfile(time) - if (profile == null) { - time += 5 * 60 * 1000L - continue - } - val iob: Double = iobCobCalculator.calculateAbsInsulinFromTreatmentsAndTemps(time).iob - if (abs(lastIob - iob) > 0.02) { - if (abs(lastIob - iob) > 0.2) iobArray.add(ScaledDataPoint(time, lastIob, iobScale)) - iobArray.add(ScaledDataPoint(time, iob, iobScale)) - maxIobValueFound = max(maxIobValueFound, abs(iob)) - lastIob = iob - } - time += 5 * 60 * 1000L - } - iobSeries = FixedLineGraphSeries(Array(iobArray.size) { i -> iobArray[i] }).also { - it.isDrawBackground = true - it.backgroundColor = -0x7f000001 and resourceHelper.gc(R.color.iob) //50% - it.color = resourceHelper.gc(R.color.iob) - it.thickness = 3 - } + fun addAbsIob(useForScale: Boolean, scale: Double) { if (useForScale) { - maxY = maxIobValueFound - minY = -maxIobValueFound + maxY = overviewData.maxIobValueFound + minY = -overviewData.maxIobValueFound } - iobScale.setMultiplier(maxY * scale / maxIobValueFound) - addSeries(iobSeries) + overviewData.iobScale.setMultiplier(maxY * scale / overviewData.maxIobValueFound) + addSeries(overviewData.absIobSeries) } // scale in % of vertical size (like 0.3) - fun addCob(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) { - val minFailOverActiveList: MutableList = ArrayList() - val cobArray: MutableList = ArrayList() - var maxCobValueFound = 0.0 - var lastCob = 0 - val cobScale = Scale() - var time = fromTime - while (time <= toTime) { - iobCobCalculator.ads.getAutosensDataAtTime(time)?.let { autosensData -> - 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.failoverToMinAbsorbtionRate) { - autosensData.setScale(cobScale) - autosensData.setChartTime(time) - minFailOverActiveList.add(autosensData) - } - } - time += 5 * 60 * 1000L - } - - // COB - addSeries(FixedLineGraphSeries(Array(cobArray.size) { i -> cobArray[i] }).also { - it.isDrawBackground = true - it.backgroundColor = -0x7f000001 and resourceHelper.gc(R.color.cob) //50% - it.color = resourceHelper.gc(R.color.cob) - it.thickness = 3 - }) + fun addCob(useForScale: Boolean, scale: Double) { if (useForScale) { - maxY = maxCobValueFound + maxY = overviewData.maxCobValueFound minY = 0.0 } - cobScale.setMultiplier(maxY * scale / maxCobValueFound) - addSeries(PointsWithLabelGraphSeries(Array(minFailOverActiveList.size) { i -> minFailOverActiveList[i] })) + overviewData.cobScale.setMultiplier(maxY * scale / overviewData.maxCobValueFound) + addSeries(overviewData.cobSeries) + addSeries(overviewData.cobMinFailOverSeries) } // scale in % of vertical size (like 0.3) - fun addDeviations(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double, devBgiScale: Boolean) { - class DeviationDataPoint(x: Double, y: Double, var color: Int, scale: Scale) : ScaledDataPoint(x, y, scale) - - val devArray: MutableList = ArrayList() - var maxDevValueFound = 0.0 - val devScale = Scale() - var time = fromTime - var total: IobTotal - - while (time <= toTime) { - // if align Dev Scale with BGI scale, then calculate BGI value, else bgi = 0.0 - val bgi: Double = if (devBgiScale) { - val profile = profileFunction.getProfile(time) - if (profile == null) { - time += 5 * 60 * 1000L - continue - } - total = iobCobCalculator.calculateFromTreatmentsAndTemps(time, profile) - total.activity * profile.getIsfMgdl(time) * 5.0 - } else 0.0 - - iobCobCalculator.ads.getAutosensDataAtTime(time)?.let { autosensData -> - var color = resourceHelper.gc(R.color.deviationblack) // "=" - if (autosensData.type == "" || autosensData.type == "non-meal") { - if (autosensData.pastSensitivity == "C") color = resourceHelper.gc(R.color.deviationgrey) - if (autosensData.pastSensitivity == "+") color = resourceHelper.gc(R.color.deviationgreen) - if (autosensData.pastSensitivity == "-") color = resourceHelper.gc(R.color.deviationred) - } else if (autosensData.type == "uam") { - color = resourceHelper.gc(R.color.uam) - } else if (autosensData.type == "csf") { - color = resourceHelper.gc(R.color.deviationgrey) - } - devArray.add(DeviationDataPoint(time.toDouble(), autosensData.deviation, color, devScale)) - maxDevValueFound = max(maxDevValueFound, max(abs(autosensData.deviation), abs(bgi))) - } - time += 5 * 60 * 1000L - } - - // DEVIATIONS - addSeries(BarGraphSeries(Array(devArray.size) { i -> devArray[i] }).also { - it.setValueDependentColor { data: DeviationDataPoint -> data.color } - }) + fun addDeviations(useForScale: Boolean, scale: Double) { if (useForScale) { - maxY = maxDevValueFound + maxY = overviewData.maxDevValueFound minY = -maxY } - devScale.setMultiplier(maxY * scale / maxDevValueFound) + overviewData.devScale.setMultiplier(maxY * scale / overviewData.maxDevValueFound) + addSeries(overviewData.deviationsSeries) } // scale in % of vertical size (like 0.3) - fun addRatio(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) { - val ratioArray: MutableList = ArrayList() - var maxRatioValueFound = 5.0 //even if sens data equals 0 for all the period, minimum scale is between 95% and 105% - var minRatioValueFound = -maxRatioValueFound - val ratioScale = if (useForScale) Scale(100.0) else Scale() - var time = fromTime - while (time <= toTime) { - iobCobCalculator.ads.getAutosensDataAtTime(time)?.let { autosensData -> - 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)) - } - time += 5 * 60 * 1000L - } - - // RATIOS - addSeries(LineGraphSeries(Array(ratioArray.size) { i -> ratioArray[i] }).also { - it.color = resourceHelper.gc(R.color.ratio) - it.thickness = 3 - }) + fun addRatio(useForScale: Boolean, scale: Double) { if (useForScale) { - maxY = 100.0 + max(maxRatioValueFound, abs(minRatioValueFound)) - minY = 100.0 - max(maxRatioValueFound, abs(minRatioValueFound)) - ratioScale.setMultiplier(1.0) + maxY = 100.0 + max(overviewData.maxRatioValueFound, abs(overviewData.minRatioValueFound)) + minY = 100.0 - max(overviewData.maxRatioValueFound, abs(overviewData.minRatioValueFound)) + overviewData.ratioScale.setMultiplier(1.0) } else - ratioScale.setMultiplier(maxY * scale / max(maxRatioValueFound, abs(minRatioValueFound))) + overviewData.ratioScale.setMultiplier(maxY * scale / max(overviewData.maxRatioValueFound, abs(overviewData.minRatioValueFound))) + addSeries(overviewData.ratioSeries) } // scale in % of vertical size (like 0.3) - fun addDeviationSlope(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) { - val dsMaxArray: MutableList = ArrayList() - val dsMinArray: MutableList = ArrayList() - var maxFromMaxValueFound = 0.0 - var maxFromMinValueFound = 0.0 - val dsMaxScale = Scale() - val dsMinScale = Scale() - var time = fromTime - while (time <= toTime) { - iobCobCalculator.ads.getAutosensDataAtTime(time)?.let { autosensData -> - 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 - } - - // Slopes - addSeries(LineGraphSeries(Array(dsMaxArray.size) { i -> dsMaxArray[i] }).also { - it.color = resourceHelper.gc(R.color.devslopepos) - it.thickness = 3 - }) - addSeries(LineGraphSeries(Array(dsMinArray.size) { i -> dsMinArray[i] }).also { - it.color = resourceHelper.gc(R.color.devslopeneg) - it.thickness = 3 - }) + fun addDeviationSlope(useForScale: Boolean, scale: Double) { if (useForScale) { - maxY = max(maxFromMaxValueFound, maxFromMinValueFound) + maxY = max(overviewData.maxFromMaxValueFound, overviewData.maxFromMinValueFound) minY = -maxY } - dsMaxScale.setMultiplier(maxY * scale / maxFromMaxValueFound) - dsMinScale.setMultiplier(maxY * scale / maxFromMinValueFound) + overviewData.dsMaxScale.setMultiplier(maxY * scale / overviewData.maxFromMaxValueFound) + overviewData.dsMinScale.setMultiplier(maxY * scale / overviewData.maxFromMinValueFound) + addSeries(overviewData.dsMaxSeries) + addSeries(overviewData.dsMinSeries) } // scale in % of vertical size (like 0.3) @@ -669,7 +201,7 @@ class GraphData( graph.gridLabelRenderer.numHorizontalLabels = 7 // only 7 because of the space } - private fun addSeries(s: Series<*>) = series.add(s) + internal fun addSeries(s: Series<*>) = series.add(s) fun performUpdate() { // clear old data @@ -693,5 +225,3 @@ class GraphData( } } -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/iob/iobCobCalculator/IobCobCalculatorPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPlugin.kt index e5662653c0..6a0d5bb2de 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 @@ -7,7 +7,6 @@ import info.nightscout.androidaps.Constants import info.nightscout.androidaps.R import info.nightscout.androidaps.data.IobTotal import info.nightscout.androidaps.data.MealData -import info.nightscout.androidaps.interfaces.Profile import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.ValueWrapper import info.nightscout.androidaps.database.entities.Bolus @@ -71,7 +70,6 @@ open class IobCobCalculatorPlugin @Inject constructor( private val disposable = CompositeDisposable() private var iobTable = LongSparseArray() // oldest at index 0 - private var absIobTable = LongSparseArray() // oldest at index 0, absolute insulin in the body private var basalDataTable = LongSparseArray() // oldest at index 0 override var ads: AutosensDataStore = AutosensDataStore() @@ -169,56 +167,37 @@ open class IobCobCalculatorPlugin @Inject constructor( return getBGDataFrom } - override fun calculateFromTreatmentsAndTemps(fromTime: Long, profile: Profile): IobTotal { - synchronized(dataLock) { - val now = System.currentTimeMillis() - val time = ads.roundUpTime(fromTime) - val cacheHit = iobTable[time] - if (time < now && cacheHit != null) { - //og.debug(">>> calculateFromTreatmentsAndTemps Cache hit " + new Date(time).toLocaleString()); - return cacheHit - } // else log.debug(">>> calculateFromTreatmentsAndTemps Cache miss " + new Date(time).toLocaleString()); - val bolusIob = calculateIobFromBolusToTime(time).round() - val basalIob = calculateIobToTimeFromTempBasalsIncludingConvertedExtended(time).round() - // OpenAPSSMB only - // Add expected zero temp basal for next 240 minutes - val basalIobWithZeroTemp = basalIob.copy() - val t = TemporaryBasal( - timestamp = now + 60 * 1000L, - duration = 240, - rate = 0.0, - isAbsolute = true, - type = TemporaryBasal.Type.NORMAL) - if (t.timestamp < time) { - val calc = t.iobCalc(time, profile, activePlugin.activeInsulin) - basalIobWithZeroTemp.plus(calc) - } - basalIob.iobWithZeroTemp = IobTotal.combine(bolusIob, basalIobWithZeroTemp).round() - val iobTotal = IobTotal.combine(bolusIob, basalIob).round() - if (time < System.currentTimeMillis()) { + override fun calculateFromTreatmentsAndTemps(toTime: Long, profile: Profile): IobTotal { + val now = System.currentTimeMillis() + val time = ads.roundUpTime(toTime) + val cacheHit = iobTable[time] + if (time < now && cacheHit != null) { + //og.debug(">>> calculateFromTreatmentsAndTemps Cache hit " + new Date(time).toLocaleString()); + return cacheHit + } // else log.debug(">>> calculateFromTreatmentsAndTemps Cache miss " + new Date(time).toLocaleString()); + val bolusIob = calculateIobFromBolusToTime(time).round() + val basalIob = calculateIobToTimeFromTempBasalsIncludingConvertedExtended(time).round() + // OpenAPSSMB only + // Add expected zero temp basal for next 240 minutes + val basalIobWithZeroTemp = basalIob.copy() + val t = TemporaryBasal( + timestamp = now + 60 * 1000L, + duration = 240, + rate = 0.0, + isAbsolute = true, + type = TemporaryBasal.Type.NORMAL) + if (t.timestamp < time) { + val calc = t.iobCalc(time, profile, activePlugin.activeInsulin) + basalIobWithZeroTemp.plus(calc) + } + basalIob.iobWithZeroTemp = IobTotal.combine(bolusIob, basalIobWithZeroTemp).round() + val iobTotal = IobTotal.combine(bolusIob, basalIob).round() + if (time < System.currentTimeMillis()) { + synchronized(dataLock) { iobTable.put(time, iobTotal) } - return iobTotal - } - } - - override fun calculateAbsInsulinFromTreatmentsAndTemps(fromTime: Long): IobTotal { - synchronized(dataLock) { - val now = System.currentTimeMillis() - val time = ads.roundUpTime(fromTime) - val cacheHit = absIobTable[time] - if (time < now && cacheHit != null) { - //log.debug(">>> calculateFromTreatmentsAndTemps Cache hit " + new Date(time).toLocaleString()); - return cacheHit - } // else log.debug(">>> calculateFromTreatmentsAndTemps Cache miss " + new Date(time).toLocaleString()); - val bolusIob = calculateIobFromBolusToTime(time).round() - val basalIob = calculateAbsoluteIobTempBasals(time).round() - val iobTotal = IobTotal.combine(bolusIob, basalIob).round() - if (time < System.currentTimeMillis()) { - absIobTable.put(time, iobTotal) - } - return iobTotal } + return iobTotal } private fun calculateFromTreatmentsAndTemps(time: Long, lastAutosensResult: AutosensResult, exercise_mode: Boolean, half_basal_exercise_target: Int, isTempTarget: Boolean): IobTotal { @@ -246,28 +225,28 @@ open class IobCobCalculatorPlugin @Inject constructor( } override fun getBasalData(profile: Profile, fromTime: Long): BasalData { - synchronized(dataLock) { - val now = System.currentTimeMillis() - val time = ads.roundUpTime(fromTime) - var retVal = basalDataTable[time] - if (retVal == null) { - //log.debug(">>> getBasalData Cache miss " + new Date(time).toLocaleString()); - retVal = BasalData() - val tb = getTempBasalIncludingConvertedExtended(time) - retVal.basal = profile.getBasal(time) - if (tb != null) { - retVal.isTempBasalRunning = true - retVal.tempBasalAbsolute = tb.convertedToAbsolute(time, profile) - } else { - retVal.isTempBasalRunning = false - retVal.tempBasalAbsolute = retVal.basal - } - if (time < now) { + val now = System.currentTimeMillis() + val time = ads.roundUpTime(fromTime) + var retVal = basalDataTable[time] + if (retVal == null) { + //log.debug(">>> getBasalData Cache miss " + new Date(time).toLocaleString()); + retVal = BasalData() + val tb = getTempBasalIncludingConvertedExtended(time) + retVal.basal = profile.getBasal(time) + if (tb != null) { + retVal.isTempBasalRunning = true + retVal.tempBasalAbsolute = tb.convertedToAbsolute(time, profile) + } else { + retVal.isTempBasalRunning = false + retVal.tempBasalAbsolute = retVal.basal + } + if (time < now) { + synchronized(dataLock) { basalDataTable.append(time, retVal) } - } //else log.debug(">>> getBasalData Cache hit " + new Date(time).toLocaleString()); - return retVal - } + } + } //else log.debug(">>> getBasalData Cache hit " + new Date(time).toLocaleString()); + return retVal } override fun getLastAutosensDataWithWaitForCalculationFinish(reason: String): AutosensData? { @@ -408,14 +387,6 @@ open class IobCobCalculatorPlugin @Inject constructor( break } } - for (index in absIobTable.size() - 1 downTo 0) { - if (absIobTable.keyAt(index) > time) { - aapsLogger.debug(LTag.AUTOSENS, "Removing from absIobTable: " + dateUtil.dateAndTimeAndSecondsString(absIobTable.keyAt(index))) - absIobTable.removeAt(index) - } else { - break - } - } for (index in basalDataTable.size() - 1 downTo 0) { if (basalDataTable.keyAt(index) > time) { aapsLogger.debug(LTag.AUTOSENS, "Removing from basalDataTable: " + dateUtil.dateAndTimeAndSecondsString(basalDataTable.keyAt(index))) @@ -459,8 +430,8 @@ open class IobCobCalculatorPlugin @Inject constructor( * Time range to the past for IOB calculation * @return milliseconds */ - fun range(): Long = ((profileFunction.getProfile()?.dia - ?: Constants.defaultDIA) * 60 * 60 * 1000).toLong() + fun range(): Long = ((/*overviewData.rangeToDisplay + */(profileFunction.getProfile()?.dia + ?: Constants.defaultDIA)) * 60 * 60 * 1000).toLong() override fun calculateIobFromBolus(): IobTotal = calculateIobFromBolusToTime(dateUtil.now()) @@ -533,6 +504,7 @@ open class IobCobCalculatorPlugin @Inject constructor( } override fun getTempBasalIncludingConvertedExtended(timestamp: Long): TemporaryBasal? { + val tb = repository.getTemporaryBasalActiveAt(timestamp).blockingGet() if (tb is ValueWrapper.Existing) return tb.value val eb = repository.getExtendedBolusActiveAt(timestamp).blockingGet() @@ -542,7 +514,7 @@ open class IobCobCalculatorPlugin @Inject constructor( return null } - override fun calculateAbsoluteIobTempBasals(toTime: Long): IobTotal { + override fun calculateAbsoluteIobFromBaseBasals(toTime: Long): IobTotal { val total = IobTotal(toTime) var i = toTime - range() while (i < toTime) { @@ -551,8 +523,7 @@ open class IobCobCalculatorPlugin @Inject constructor( i += T.mins(5).msecs() continue } - val runningTBR = getTempBasalIncludingConvertedExtended(i) - val running = runningTBR?.convertedToAbsolute(i, profile) ?: profile.getBasal(i) + val running = profile.getBasal(i) val bolus = Bolus( timestamp = i, amount = running * 5.0 / 60.0, 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/IobCobOref1Thread.kt index e77ff521a0..d577666d27 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/IobCobOref1Thread.kt @@ -82,7 +82,7 @@ class IobCobOref1Thread internal constructor( //log.debug("Locking calculateSensitivityData"); val oldestTimeWithData = iobCobCalculatorPlugin.calculateDetectionStart(end, limitDataToOldestAvailable) if (bgDataReload) { - iobCobCalculatorPlugin.ads.loadBgData(end, repository, aapsLogger, dateUtil) + iobCobCalculatorPlugin.ads.loadBgData(end, repository, aapsLogger, dateUtil, rxBus) iobCobCalculatorPlugin.clearCache() } // work on local copy and set back when finished 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/IobCobThread.kt index 18389b33f9..e6fa48cca2 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/IobCobThread.kt @@ -81,7 +81,7 @@ class IobCobThread @Inject internal constructor( //log.debug("Locking calculateSensitivityData"); val oldestTimeWithData = iobCobCalculatorPlugin.calculateDetectionStart(end, limitDataToOldestAvailable) if (bgDataReload) { - iobCobCalculatorPlugin.ads.loadBgData(end, repository, aapsLogger, dateUtil) + iobCobCalculatorPlugin.ads.loadBgData(end, repository, aapsLogger, dateUtil, rxBus) iobCobCalculatorPlugin.clearCache() } // work on local copy and set back when finished 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 c21cc86463..42afbb4c5c 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 @@ -27,6 +27,7 @@ import info.nightscout.androidaps.utils.sharedPreferences.SP import org.json.JSONArray import org.json.JSONException import org.json.JSONObject +import java.lang.Integer.min import java.util.* import javax.inject.Inject import javax.inject.Singleton @@ -413,7 +414,7 @@ class LocalProfilePlugin @Inject constructor( if (sp.getBoolean(R.string.key_ns_receive_profile_store, false) || config.NSCLIENT) { localProfilePlugin.loadFromStore(ProfileStore(injector, profileJson, dateUtil)) aapsLogger.debug(LTag.PROFILE, "Received profileStore: $profileJson") - return Result.success(workDataOf("Data" to profileJson.toString().substring(1..5000))) + return Result.success(workDataOf("Data" to profileJson.toString().substring(0..min(5000, profileJson.length())))) } return Result.success() } 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 ac7738043d..a5f2dc2dcb 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/TrendCalculator.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/TrendCalculator.kt @@ -10,9 +10,12 @@ class TrendCalculator @Inject constructor( private val repository: AppRepository ) { - fun getTrendArrow(glucoseValue: GlucoseValue): GlucoseValue.TrendArrow = - if (glucoseValue.trendArrow != GlucoseValue.TrendArrow.NONE) glucoseValue.trendArrow - else calculateDirection(glucoseValue) + fun getTrendArrow(glucoseValue: GlucoseValue?): GlucoseValue.TrendArrow = + when { + glucoseValue?.trendArrow == null -> GlucoseValue.TrendArrow.NONE + glucoseValue.trendArrow != GlucoseValue.TrendArrow.NONE -> glucoseValue.trendArrow + else -> calculateDirection(glucoseValue) + } private fun calculateDirection(glucoseValue: GlucoseValue): GlucoseValue.TrendArrow { diff --git a/core/src/main/java/info/nightscout/androidaps/data/IobTotal.kt b/core/src/main/java/info/nightscout/androidaps/data/IobTotal.kt index 93ee6f5f64..5f13bfe351 100644 --- a/core/src/main/java/info/nightscout/androidaps/data/IobTotal.kt +++ b/core/src/main/java/info/nightscout/androidaps/data/IobTotal.kt @@ -6,7 +6,6 @@ import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.Round import org.json.JSONException import org.json.JSONObject -import java.util.* @Suppress("SpellCheckingInspection") class IobTotal(var time: Long) : DataPointWithLabelInterface { @@ -22,7 +21,6 @@ class IobTotal(var time: Long) : DataPointWithLabelInterface { @JvmField var lastBolusTime: Long = 0 var iobWithZeroTemp: IobTotal? = null @JvmField var netInsulin = 0.0 // for calculations from temp basals only - @JvmField var netRatio = 0.0 // net ratio at start of temp basal @JvmField var extendedBolusInsulin = 0.0 // total insulin for extended bolus fun copy(): IobTotal { val i = IobTotal(time) @@ -33,9 +31,8 @@ class IobTotal(var time: Long) : DataPointWithLabelInterface { i.netbasalinsulin = netbasalinsulin i.hightempinsulin = hightempinsulin i.lastBolusTime = lastBolusTime - if (iobWithZeroTemp != null) i.iobWithZeroTemp = iobWithZeroTemp!!.copy() + i.iobWithZeroTemp = iobWithZeroTemp?.copy() i.netInsulin = netInsulin - i.netRatio = netRatio i.extendedBolusInsulin = extendedBolusInsulin return i } diff --git a/core/src/main/java/info/nightscout/androidaps/extensions/TemporaryBasalExtension.kt b/core/src/main/java/info/nightscout/androidaps/extensions/TemporaryBasalExtension.kt index 2522f930b4..48de5abd87 100644 --- a/core/src/main/java/info/nightscout/androidaps/extensions/TemporaryBasalExtension.kt +++ b/core/src/main/java/info/nightscout/androidaps/extensions/TemporaryBasalExtension.kt @@ -165,7 +165,6 @@ fun TemporaryBasal.iobCalc(time: Long, profile: Profile, insulinInterface: Insul result.hightempinsulin += tempBolusPart.amount } } - result.netRatio = netBasalRate // ratio at the end of interval } } result.netInsulin = netBasalAmount @@ -217,7 +216,6 @@ fun TemporaryBasal.iobCalc(time: Long, profile: Profile, lastAutosensResult: Aut result.hightempinsulin += tempBolusPart.amount } } - result.netRatio = netBasalRate // ratio at the end of interval } } result.netInsulin = netBasalAmount diff --git a/core/src/main/java/info/nightscout/androidaps/interfaces/IobCobCalculator.kt b/core/src/main/java/info/nightscout/androidaps/interfaces/IobCobCalculator.kt index 0dbe420b24..e0e3dfbb0a 100644 --- a/core/src/main/java/info/nightscout/androidaps/interfaces/IobCobCalculator.kt +++ b/core/src/main/java/info/nightscout/androidaps/interfaces/IobCobCalculator.kt @@ -2,7 +2,6 @@ package info.nightscout.androidaps.interfaces import info.nightscout.androidaps.data.IobTotal import info.nightscout.androidaps.data.MealData -import info.nightscout.androidaps.interfaces.Profile import info.nightscout.androidaps.database.entities.ExtendedBolus import info.nightscout.androidaps.database.entities.TemporaryBasal import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensDataStore @@ -19,8 +18,7 @@ interface IobCobCalculator { fun getMealDataWithWaitingForCalculationFinish(): MealData fun getLastAutosensDataWithWaitForCalculationFinish(reason: String): AutosensData? - fun calculateAbsInsulinFromTreatmentsAndTemps(fromTime: Long): IobTotal - fun calculateFromTreatmentsAndTemps(fromTime: Long, profile: Profile): IobTotal + fun calculateFromTreatmentsAndTemps(toTime: Long, profile: Profile): IobTotal fun getBasalData(profile: Profile, fromTime: Long): BasalData @@ -72,14 +70,12 @@ interface IobCobCalculator { fun getExtendedBolus(timestamp: Long): ExtendedBolus? /** - * Calculate IOB of all insulin in the body to the time - * - * Running basal is added to the IOB !!! + * Calculate IOB of base basal insulin (usualy not accounted towards IOB) * * @param toTime * @return IobTotal */ - fun calculateAbsoluteIobTempBasals(toTime: Long): IobTotal + fun calculateAbsoluteIobFromBaseBasals(toTime: Long): IobTotal /** * Calculate IOB from Temporary basals and Extended boluses (if emulation is enabled) to the the time specified diff --git a/core/src/main/java/info/nightscout/androidaps/interfaces/Overview.kt b/core/src/main/java/info/nightscout/androidaps/interfaces/Overview.kt index 23d43b35b0..1639ec79aa 100644 --- a/core/src/main/java/info/nightscout/androidaps/interfaces/Overview.kt +++ b/core/src/main/java/info/nightscout/androidaps/interfaces/Overview.kt @@ -1,3 +1,9 @@ package info.nightscout.androidaps.interfaces -interface Overview : ConfigExportImport \ No newline at end of file +import info.nightscout.androidaps.plugins.bus.RxBusWrapper + +interface Overview : ConfigExportImport { + + fun refreshLoop(from: String) + val overviewBus: RxBusWrapper +} \ No newline at end of file diff --git a/core/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/AutosensDataStore.kt b/core/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/AutosensDataStore.kt index ee892ac0ed..5cbfe021a3 100644 --- a/core/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/AutosensDataStore.kt +++ b/core/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/AutosensDataStore.kt @@ -6,7 +6,9 @@ import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.entities.GlucoseValue import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.iob.iobCobCalculator.data.AutosensData +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventBucketedDataCreated import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.T import kotlin.math.abs @@ -151,7 +153,7 @@ class AutosensDataStore { return someTime + diff } - fun loadBgData(to: Long, repository: AppRepository, aapsLogger: AAPSLogger, dateUtil: DateUtil) { + fun loadBgData(to: Long, repository: AppRepository, aapsLogger: AAPSLogger, dateUtil: DateUtil, rxBus: RxBusWrapper) { synchronized(dataLock) { val start = to - T.hours((24 + 10 /* max dia */).toLong()).msecs() // there can be some readings with time in close future (caused by wrong time setting on sensor) @@ -162,6 +164,7 @@ class AutosensDataStore { .filter { it.value >= 39 } aapsLogger.debug(LTag.AUTOSENS, "BG data loaded. Size: " + bgReadings.size + " Start date: " + dateUtil.dateAndTimeString(start) + " End date: " + dateUtil.dateAndTimeString(to)) createBucketedData(aapsLogger, dateUtil) + rxBus.send(EventBucketedDataCreated()) } } diff --git a/core/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/events/EventBucketedDataCreated.kt b/core/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/events/EventBucketedDataCreated.kt new file mode 100644 index 0000000000..c9411eb259 --- /dev/null +++ b/core/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/events/EventBucketedDataCreated.kt @@ -0,0 +1,5 @@ +package info.nightscout.androidaps.plugins.iob.iobCobCalculator.events + +import info.nightscout.androidaps.events.Event + +class EventBucketedDataCreated : Event() \ No newline at end of file diff --git a/core/src/main/java/info/nightscout/androidaps/utils/DateUtil.kt b/core/src/main/java/info/nightscout/androidaps/utils/DateUtil.kt index d74fa72876..a65f6de701 100644 --- a/core/src/main/java/info/nightscout/androidaps/utils/DateUtil.kt +++ b/core/src/main/java/info/nightscout/androidaps/utils/DateUtil.kt @@ -137,12 +137,14 @@ open class DateUtil @Inject constructor(private val context: Context) { return if (mills == 0L) "" else dateString(mills) + " " + timeStringWithSeconds(mills) } - fun minAgo(resourceHelper: ResourceHelper, time: Long): String { + fun minAgo(resourceHelper: ResourceHelper, time: Long?): String { + if (time == null) return "" val mins = ((now() - time) / 1000 / 60).toInt() return resourceHelper.gs(R.string.minago, mins) } - fun minAgoShort(time: Long): String { + fun minAgoShort(time: Long?): String { + if (time == null) return "" val mins = ((time - now()) / 1000 / 60).toInt() return (if (mins > 0) "+" else "") + mins } diff --git a/database/src/main/java/info/nightscout/androidaps/database/AppRepository.kt b/database/src/main/java/info/nightscout/androidaps/database/AppRepository.kt index 5529d23655..93d92392d1 100644 --- a/database/src/main/java/info/nightscout/androidaps/database/AppRepository.kt +++ b/database/src/main/java/info/nightscout/androidaps/database/AppRepository.kt @@ -7,8 +7,6 @@ import io.reactivex.Completable import io.reactivex.Maybe import io.reactivex.Observable import io.reactivex.Single -import io.reactivex.internal.operators.maybe.MaybeJust -import io.reactivex.rxkotlin.subscribeBy import io.reactivex.schedulers.Schedulers import io.reactivex.subjects.PublishSubject import java.util.concurrent.Callable @@ -83,6 +81,11 @@ open class AppRepository @Inject internal constructor( .subscribeOn(Schedulers.io()) .toWrappedSingle() + fun getLastGlucoseValueWrapped(): Single> = + database.glucoseValueDao.getLast() + .subscribeOn(Schedulers.io()) + .toWrappedSingle() + /* * returns a Pair of the next entity to sync and the ID of the "update". * The update id might either be the entry id itself if it is a new entry - or the id diff --git a/database/src/main/java/info/nightscout/androidaps/database/DatabaseModule.kt b/database/src/main/java/info/nightscout/androidaps/database/DatabaseModule.kt index 2c9f768e68..ec1f24e00b 100644 --- a/database/src/main/java/info/nightscout/androidaps/database/DatabaseModule.kt +++ b/database/src/main/java/info/nightscout/androidaps/database/DatabaseModule.kt @@ -2,6 +2,7 @@ package info.nightscout.androidaps.database import android.content.Context import androidx.room.Room +import androidx.room.RoomDatabase.Callback import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import dagger.Module @@ -25,6 +26,15 @@ open class DatabaseModule { // .addMigrations(migration6to7) // .addMigrations(migration7to8) // .addMigrations(migration11to12) + .addCallback(object : Callback() { + override fun onOpen(db: SupportSQLiteDatabase) { + super.onOpen(db) + db.execSQL("CREATE INDEX IF NOT EXISTS `index_temporaryBasals_end` ON `temporaryBasals` (`timestamp` + `duration`)") + db.execSQL("CREATE INDEX IF NOT EXISTS `index_extendedBoluses_end` ON `extendedBoluses` (`timestamp` + `duration`)") + db.execSQL("CREATE INDEX IF NOT EXISTS `index_temporaryTargets_end` ON `temporaryTargets` (`timestamp` + `duration`)") + db.execSQL("CREATE INDEX IF NOT EXISTS `index_carbs_end` ON `carbs` (`timestamp` + `duration`)") + } + }) .fallbackToDestructiveMigration() .build() diff --git a/database/src/main/java/info/nightscout/androidaps/database/daos/GlucoseValueDao.kt b/database/src/main/java/info/nightscout/androidaps/database/daos/GlucoseValueDao.kt index 608eec3093..27cde3dbe4 100644 --- a/database/src/main/java/info/nightscout/androidaps/database/daos/GlucoseValueDao.kt +++ b/database/src/main/java/info/nightscout/androidaps/database/daos/GlucoseValueDao.kt @@ -16,6 +16,9 @@ internal interface GlucoseValueDao : TraceableDao { @Query("DELETE FROM $TABLE_GLUCOSE_VALUES") override fun deleteAllEntries() + @Query("SELECT * FROM $TABLE_GLUCOSE_VALUES ORDER BY id DESC limit 1") + fun getLast(): Maybe + @Query("SELECT id FROM $TABLE_GLUCOSE_VALUES ORDER BY id DESC limit 1") fun getLastId(): Maybe diff --git a/database/src/main/java/info/nightscout/androidaps/database/transactions/InvalidateAAPSStartedTherapyEventTransaction.kt b/database/src/main/java/info/nightscout/androidaps/database/transactions/InvalidateAAPSStartedTherapyEventTransaction.kt index 248b51d412..2a152c408c 100644 --- a/database/src/main/java/info/nightscout/androidaps/database/transactions/InvalidateAAPSStartedTherapyEventTransaction.kt +++ b/database/src/main/java/info/nightscout/androidaps/database/transactions/InvalidateAAPSStartedTherapyEventTransaction.kt @@ -8,7 +8,7 @@ class InvalidateAAPSStartedTherapyEventTransaction(private val note: String) : T val result = TransactionResult() val therapyEvents = database.therapyEventDao.getValidByType(TherapyEvent.Type.NOTE) for (event in therapyEvents) { - if (event.note?.contains(note) == true) { + if (event.note?.contains(note) == true && event.isValid) { event.isValid = false database.therapyEventDao.updateExistingEntry(event) result.invalidated.add(event)