Merge branch 'dev' into eopatch2

This commit is contained in:
Milos Kozak 2022-07-19 11:27:58 +02:00 committed by GitHub
commit 763f54ec6f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
1982 changed files with 91666 additions and 39773 deletions

View file

@ -9,11 +9,15 @@
<option name="BLANK_LINES_AROUND_BLOCK_WHEN_BRANCHES" value="1" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<XML>
<option name="XML_TEXT_WRAP" value="0" />
</XML>
<codeStyleSettings language="JAVA">
<option name="METHOD_ANNOTATION_WRAP" value="0" />
<option name="FIELD_ANNOTATION_WRAP" value="0" />
</codeStyleSettings>
<codeStyleSettings language="XML">
<option name="WRAP_ON_TYPING" value="0" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>

View file

@ -5,11 +5,13 @@
<w>abcdef</w>
<w>acked</w>
<w>actionstring</w>
<w>aidex</w>
<w>allowednumbers</w>
<w>androidaps</w>
<w>autosens</w>
<w>autosensdata</w>
<w>autosense</w>
<w>autotune</w>
<w>bage</w>
<w>basaliob</w>
<w>basals</w>
@ -17,6 +19,7 @@
<w>bgsource</w>
<w>boluscalc</w>
<w>bolusing</w>
<w>boluswizard</w>
<w>carb</w>
<w>carbratio</w>
<w>carbs</w>
@ -28,8 +31,12 @@
<w>danars</w>
<w>dataset</w>
<w>datasets</w>
<w>devicestatus</w>
<w>devicestatuses</w>
<w>devslope</w>
<w>dexcom</w>
<w>dexdrip</w>
<w>diaconn</w>
<w>enteredby</w>
<w>enteredinsulin</w>
<w>eveningoutpost</w>
@ -43,11 +50,14 @@
<w>iage</w>
<w>insulet</w>
<w>iobtotal</w>
<w>joda</w>
<w>libre</w>
<w>listdelimiter</w>
<w>localprofile</w>
<w>mdtp</w>
<w>medtronic</w>
<w>mgdl</w>
<w>misformatted</w>
<w>mmol</w>
<w>motol</w>
<w>multiwave</w>
@ -76,6 +86,7 @@
<w>rileylink</w>
<w>roboelectric</w>
<w>sgvs</w>
<w>shortgramm</w>
<w>sitechange</w>
<w>smscommunicator</w>
<w>sntp</w>
@ -96,7 +107,8 @@
<w>timeshift</w>
<w>tirs</w>
<w>totp</w>
<w>uart</w>
<w>tunedays</w>
<w>uart</w>
<w>wizzardpage</w>
<w>xdrip</w>
<w>xstream</w>

View file

@ -44,5 +44,5 @@ Hints
* Start small, it is easier to review smaller changes that affect fewer parts of code
* Take a look into Issues list (https://github.com/nightscout/AndroidAPS/issues) - maybe there is something you can fix or implement
* For new features, make sure there is Issue to track progress and have on-topic discussion
* Reach out to community, discuss idea on Gitter (https://gitter.im/MilosKozak/AndroidAPS)
* Reach out to community, discuss idea on Discord (https://discord.gg/4fQUWHZ4Mw)
* Speak with other developers to minimise merge conflicts. Find out who worked, working or plan to work on speciffic issue or part of app

View file

@ -6,8 +6,8 @@ apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.hiya.jacoco-android'
apply plugin: 'com.google.firebase.crashlytics'
apply from: "${project.rootDir}/gradle/android_dependencies.gradle"
apply from: "${project.rootDir}/gradle/jacoco_global.gradle"
apply from: "${project.rootDir}/core/android_dependencies.gradle"
apply from: "${project.rootDir}/core/jacoco_global.gradle"
repositories {
mavenCentral()
@ -97,19 +97,15 @@ def allCommitted = { ->
return stringBuilder.toString().isEmpty()
}
tasks.matching { it instanceof Test }.all {
testLogging.events = ["failed", "skipped", "started"]
// testLogging.events = ["failed", "skipped", "started", "standard_out"] use to display stdout in travis
testLogging.exceptionFormat = "full"
}
android {
namespace 'info.nightscout.androidaps'
ndkVersion "21.1.6352462"
defaultConfig {
multiDexEnabled true
versionCode 1500
version "3.0.0.1-dev"
version "3.1.0"
buildConfigField "String", "VERSION", '"' + version + '"'
buildConfigField "String", "BUILDVERSION", '"' + generateGitBuild() + '-' + generateDate() + '"'
buildConfigField "String", "REMOTE", '"' + generateGitRemote() + '"'
@ -171,9 +167,6 @@ android {
allprojects {
repositories {
flatDir {
dirs 'libs'
}
}
}
@ -212,7 +205,7 @@ dependencies {
kapt "com.google.dagger:dagger-compiler:$dagger_version"
}
apply from: "${project.rootDir}/gradle/test_dependencies.gradle"
apply from: "${project.rootDir}/core/test_dependencies.gradle"
/*

View file

@ -1,7 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="info.nightscout.androidaps">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.BLUETOOTH" />
@ -34,17 +32,46 @@
<!-- To receive data from xdrip. -->
<uses-permission android:name="com.eveningoutpost.dexdrip.permissions.RECEIVE_BG_ESTIMATE" />
<!-- To receive data from Aidex -->
<uses-permission android:name="com.microtechmd.cgms.aidex.permissions.RECEIVE_BG_ESTIMATE" />
<application
android:name=".MainApp"
android:allowBackup="true"
android:backupAgent=".utils.SPBackupAgent"
android:fullBackupOnly="false"
android:icon="${appIcon}"
android:label="@string/app_name"
android:restoreAnyVersion="true"
android:roundIcon="${appIconRound}"
android:supportsRtl="true"
android:theme="@style/AppTheme.Launcher"
android:fullBackupOnly="false"
android:backupAgent=".utils.SPBackupAgent"
android:restoreAnyVersion="true">
android:theme="@style/AppTheme.Launcher">
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
<activity
android:name=".widget.WidgetConfigureActivity"
android:theme="@android:style/Theme.Material.Dialog.NoActionBar"
android:exported="false">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>
<receiver
android:name=".widget.Widget"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_info" />
</receiver>
<meta-data
android:name="com.google.android.backup.api_key"
@ -54,74 +81,89 @@
android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc" />
<activity android:name=".MainActivity"
android:exported="true">
<activity
android:name=".MainActivity"
android:exported="true"
android:theme="@style/AppTheme">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".activities.PreferencesActivity" />
<activity android:name=".plugins.general.overview.activities.QuickWizardListActivity"
android:exported="false">
<activity
android:name=".activities.PreferencesActivity"
android:exported="false" />
<activity
android:name=".plugins.general.overview.activities.QuickWizardListActivity"
android:exported="false"
android:theme="@style/AppTheme">
<intent-filter>
<action android:name="info.nightscout.androidaps.plugins.general.overview.activities.QuickWizardListActivity" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity android:name=".plugins.general.maintenance.activities.PrefImportListActivity" />
<activity android:name=".activities.HistoryBrowseActivity" />
<activity android:name=".activities.TreatmentsActivity" />
<activity android:name=".activities.SurveyActivity" />
<activity android:name=".activities.ProfileHelperActivity"
android:theme="@style/ProfileHelperAppTheme" />
<activity android:name=".activities.StatsActivity" />
<activity
android:name="com.google.firebase.auth.internal.FederatedSignInActivity"
android:excludeFromRecents="true"
android:exported="true"
android:launchMode="singleInstance"
android:permission="com.google.firebase.auth.api.gms.permission.LAUNCH_FEDERATED_SIGN_IN"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
tools:replace="android:launchMode" />
android:name=".plugins.general.maintenance.activities.PrefImportListActivity"
android:exported="false"
android:theme="@style/AppTheme" />
<activity
android:name=".activities.HistoryBrowseActivity"
android:exported="false"
android:theme="@style/AppTheme" />
<activity
android:name=".activities.TreatmentsActivity"
android:exported="false"
android:theme="@style/AppTheme" />
<activity
android:name=".activities.SurveyActivity"
android:exported="false"
android:theme="@style/AppTheme" />
<activity
android:name=".activities.ProfileHelperActivity"
android:exported="false"
android:theme="@style/AppTheme" />
<activity
android:name=".activities.StatsActivity"
android:exported="false"
android:theme="@style/AppTheme" />
<!-- Receive new BG readings from other local apps -->
<receiver
android:name=".receivers.DataReceiver"
android:enabled="true"
android:exported="true">
android:name=".receivers.DataReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<!-- Receiver from xDrip -->
<action android:name="com.eveningoutpost.dexdrip.BgEstimate"/>
<action android:name="com.eveningoutpost.dexdrip.BgEstimate" />
<!-- Receiver from 640g uploader -->
<action android:name="com.eveningoutpost.dexdrip.NS_EMULATOR"/>
<action android:name="com.eveningoutpost.dexdrip.NS_EMULATOR" />
<!-- Receiver from glimp -->
<action android:name="it.ct.glicemia.ACTION_GLUCOSE_MEASURED"/>
<action android:name="it.ct.glicemia.ACTION_GLUCOSE_MEASURED" />
<!-- Receiver from Dexcom -->
<action android:name="com.dexcom.cgm.EXTERNAL_BROADCAST"/>
<action android:name="com.dexcom.cgm.EXTERNAL_BROADCAST" />
<!-- Receiver from Poctech -->
<action android:name="com.china.poctech.data"/>
<action android:name="com.china.poctech.data" />
<!-- Receiver from Tomato -->
<action android:name="com.fanqies.tomatofn.BgEstimate"/>
<action android:name="com.fanqies.tomatofn.BgEstimate" />
<!-- Receiver from GlucoRx Aidex -->
<action android:name="com.microtechmd.cgms.aidex.action.BgEstimate" />
</intent-filter>
</receiver>
<!-- Receive new SMS messages -->
<receiver
android:name=".receivers.SmsReceiver"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BROADCAST_SMS">
android:name=".receivers.SmsReceiver"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BROADCAST_SMS">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED"/>
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
<!-- Receiver keep alive, scheduled every 30 min -->
<receiver android:name=".receivers.KeepAliveReceiver" />
<!-- Receive ignore 5m, 15m, 30m requests for carb notifications -->
<receiver android:name=".plugins.aps.loop.CarbSuggestionReceiver" />
@ -146,65 +188,25 @@
</provider>
<service
android:name=".plugins.general.wear.wearintegration.WatchUpdaterService"
android:name=".plugins.general.wear.wearintegration.DataLayerListenerServiceMobile"
android:exported="true">
<intent-filter>
<!-- <action android:name="com.google.android.gms.wearable.BIND_LISTENER" /> -->
<!-- listeners receive events that match the action and data filters -->
<action android:name="com.google.android.gms.wearable.CAPABILITY_CHANGED" />
<action android:name="com.google.android.gms.wearable.CHANNEL_EVENT" />
<action android:name="com.google.android.gms.wearable.DATA_CHANGED" />
</intent-filter>
<intent-filter>
<action android:name="com.google.android.gms.wearable.CAPABILITY_CHANGED" />
<data
android:host="*"
android:scheme="wear" />
</intent-filter>
<intent-filter>
<!-- listeners receive events that match the action and data filters -->
<action android:name="com.google.android.gms.wearable.MESSAGE_RECEIVED" />
<data
android:host="*"
android:pathPrefix="/nightscout_watch_data"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/nightscout_watch_data_resend"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/nightscout_watch_cancel_bolus"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/nightscout_watch_confirmactionstring"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/nightscout_watch_initiateactionstring"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/openwearsettings"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/sendstatustowear"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/sendpreferencestowear"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/nightscout_watch_basal"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/nightscout_watch_bolusprogress"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/nightscout_watch_actionconfirmationrequest"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/nightscout_watch_changeconfirmationrequest"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/nightscout_watch_cancelnotificationrequest"
android:pathPrefix="@string/path_rx_bridge"
android:scheme="wear" />
</intent-filter>
</service>
@ -222,22 +224,26 @@
<service android:name=".plugins.general.persistentNotification.DummyService" />
<meta-data
android:name="io.fabric.ApiKey"
android:value="59d462666c664c57b29e1d79ea123e01f8057cfa" />
<activity
android:name=".setupwizard.SetupWizardActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:exported="false"
android:label="@string/title_activity_setup_wizard"
android:theme="@style/AppTheme.NoActionBar" />
android:theme="@style/AppTheme" />
<activity
android:name=".activities.SingleFragmentActivity"
android:exported="false"
android:theme="@style/AppTheme" />
<activity android:name=".plugins.general.maintenance.activities.LogSettingActivity" />
<activity android:name=".activities.RequestDexcomPermissionActivity" />
<activity android:name=".plugins.general.smsCommunicator.activities.SmsCommunicatorOtpActivity">
<activity
android:name=".plugins.general.maintenance.activities.LogSettingActivity"
android:exported="false" />
<activity
android:name=".activities.RequestDexcomPermissionActivity"
android:exported="false" />
<activity
android:name=".plugins.general.smsCommunicator.activities.SmsCommunicatorOtpActivity"
android:exported="false">
<intent-filter>
<action android:name="info.nightscout.androidaps.plugins.general.smsCommunicator.activities.SmsCommunicatorOtpActivity" />
@ -245,7 +251,9 @@
</intent-filter>
</activity>
<uses-library android:name="org.apache.http.legacy" android:required="false"/>
<uses-library
android:name="org.apache.http.legacy"
android:required="false" />
</application>

View file

@ -324,7 +324,7 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
rT.reason += ", but Min. Delta " + minDelta.toFixed(2) + " > Exp. Delta " + expectedDelta;
}
if (currenttemp.duration > 15 && (round_basal(basal, profile) === round_basal(currenttemp.rate, profile))) {
rT.reason += ", temp " + currenttemp.rate + " ~ req " + basal + "U/hr";
rT.reason += ", temp " + currenttemp.rate + " ~ req " + round(basal, 2) + "U/hr";
return rT;
} else {
rT.reason += "; setting current basal of " + round(basal, 2) + " as temp";
@ -367,10 +367,10 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp);
}
if (typeof currenttemp.rate !== 'undefined' && (currenttemp.duration > 5 && rate >= currenttemp.rate * 0.8)) {
rT.reason += ", temp " + currenttemp.rate + " ~< req " + rate + "U/hr";
rT.reason += ", temp " + (currenttemp.rate).toFixed(3) + " ~< req " + round(rate, 2) + "U/hr";
return rT;
} else {
rT.reason += ", setting " + rate + "U/hr";
rT.reason += ", setting " + round(rate, 2) + "U/hr";
return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp);
}
}
@ -476,22 +476,22 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
var insulinScheduled = currenttemp.duration * (currenttemp.rate - basal) / 60;
if (insulinScheduled >= insulinReq * 2) { // if current temp would deliver >2x more than the required insulin, lower the rate
rT.reason += currenttemp.duration + "m@" + (currenttemp.rate - basal).toFixed(3) + " = " + insulinScheduled.toFixed(3) + " > 2 * req " + insulinReq + ". Setting temp basal of " + rate + "U/hr";
rT.reason += currenttemp.duration + "m@" + (currenttemp.rate - basal).toFixed(3) + " = " + insulinScheduled.toFixed(3) + " > 2 * req " + insulinReq + ". Setting temp basal of " + round(rate, 2) + "U/hr";
return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp);
}
if (typeof currenttemp.duration == 'undefined' || currenttemp.duration == 0) { // no temp is set
rT.reason += "no temp, setting " + rate + "U/hr";
rT.reason += "no temp, setting " + round(rate, 2) + "U/hr";
return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp);
}
if (currenttemp.duration > 5 && (round_basal(rate, profile) <= round_basal(currenttemp.rate, profile))) { // if required temp <~ existing temp basal
rT.reason += "temp " + currenttemp.rate + " >~ req " + rate + "U/hr";
rT.reason += "temp " + (currenttemp.rate).toFixed(3) + " >~ req " + round(rate, 2) + "U/hr";
return rT;
}
// required temp > existing temp basal
rT.reason += "temp " + currenttemp.rate + "<" + rate + "U/hr";
rT.reason += "temp " + (currenttemp.rate).toFixed(3) + " < " + round(rate, 2) + "U/hr";
return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp);
}

View file

@ -287,7 +287,7 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
}
//console.log(" (autosens ratio "+sensitivityRatio+")");
}
console.error("; CR:",profile.carb_ratio);
console.error("CR:",profile.carb_ratio);
// compare currenttemp to iob_data.lastTemp and cancel temp if they don't match
var lastTempAge;
@ -298,7 +298,7 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
}
//console.error("currenttemp:",currenttemp,"lastTemp:",JSON.stringify(iob_data.lastTemp),"lastTempAge:",lastTempAge,"m");
var tempModulus = (lastTempAge + currenttemp.duration) % 30;
console.error("currenttemp:",currenttemp,"lastTempAge:",lastTempAge,"m","tempModulus:",tempModulus,"m");
console.error("currenttemp:",round(currenttemp.rate,2),"lastTempAge:",lastTempAge,"m","tempModulus:",tempModulus,"m");
rT.temp = 'absolute';
rT.deliverAt = deliverAt;
if ( microBolusAllowed && currenttemp && iob_data.lastTemp && currenttemp.rate !== iob_data.lastTemp.rate && lastTempAge > 10 && currenttemp.duration ) {
@ -955,7 +955,7 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp);
}
if (typeof currenttemp.rate !== 'undefined' && (currenttemp.duration > 5 && rate >= currenttemp.rate * 0.8)) {
rT.reason += ", temp " + currenttemp.rate + " ~< req " + rate + "U/hr. ";
rT.reason += ", temp " + currenttemp.rate + " ~< req " + round(rate, 2) + "U/hr. ";
return rT;
} else {
// calculate a long enough zero temp to eventually correct back up to target
@ -976,7 +976,7 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
return tempBasalFunctions.setTempBasal(rate, durationReq, profile, rT, currenttemp);
}
} else {
rT.reason += ", setting " + rate + "U/hr. ";
rT.reason += ", setting " + round(rate, 2) + "U/hr. ";
}
return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp);
}
@ -1143,22 +1143,22 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
insulinScheduled = currenttemp.duration * (currenttemp.rate - basal) / 60;
if (insulinScheduled >= insulinReq * 2) { // if current temp would deliver >2x more than the required insulin, lower the rate
rT.reason += currenttemp.duration + "m@" + (currenttemp.rate).toFixed(2) + " > 2 * insulinReq. Setting temp basal of " + rate + "U/hr. ";
rT.reason += currenttemp.duration + "m@" + (currenttemp.rate).toFixed(2) + " > 2 * insulinReq. Setting temp basal of " + round(rate, 2) + "U/hr. ";
return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp);
}
if (typeof currenttemp.duration === 'undefined' || currenttemp.duration === 0) { // no temp is set
rT.reason += "no temp, setting " + rate + "U/hr. ";
rT.reason += "no temp, setting " + round(rate, 2) + "U/hr. ";
return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp);
}
if (currenttemp.duration > 5 && (round_basal(rate, profile) <= round_basal(currenttemp.rate, profile))) { // if required temp <~ existing temp basal
rT.reason += "temp " + currenttemp.rate + " >~ req " + rate + "U/hr. ";
rT.reason += "temp " + (currenttemp.rate).toFixed(2) + " >~ req " + round(rate, 2) + "U/hr. ";
return rT;
}
// required temp > existing temp basal
rT.reason += "temp " + currenttemp.rate + "<" + rate + "U/hr. ";
rT.reason += "temp " + (currenttemp.rate).toFixed(2) + " < " + round(rate, 2) + "U/hr. ";
return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp);
}

File diff suppressed because it is too large Load diff

View file

@ -7,6 +7,7 @@ import android.os.Bundle
import android.os.PersistableBundle
import android.text.SpannableString
import android.text.method.LinkMovementMethod
import android.text.style.ForegroundColorSpan
import android.text.util.Linkify
import android.util.TypedValue
import android.view.Menu
@ -19,10 +20,10 @@ import android.widget.EditText
import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.Toolbar
import androidx.core.view.GravityCompat
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.tabs.TabLayoutMediator
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.joanzapata.iconify.Iconify
@ -33,11 +34,11 @@ import info.nightscout.androidaps.database.entities.UserEntry.Action
import info.nightscout.androidaps.database.entities.UserEntry.Sources
import info.nightscout.androidaps.databinding.ActivityMainBinding
import info.nightscout.androidaps.events.EventAppExit
import info.nightscout.androidaps.events.EventInitializationChanged
import info.nightscout.androidaps.events.EventPreferenceChange
import info.nightscout.androidaps.events.EventRebuildTabs
import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.androidaps.plugins.constraints.signatureVerifier.SignatureVerifierPlugin
import info.nightscout.androidaps.plugins.constraints.versionChecker.VersionCheckerUtils
@ -47,17 +48,18 @@ import info.nightscout.androidaps.setupwizard.SetupWizardActivity
import info.nightscout.androidaps.utils.AndroidPermission
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.buildHelper.BuildHelper
import info.nightscout.androidaps.interfaces.BuildHelper
import info.nightscout.androidaps.utils.extensions.isRunningRealPumpTest
import info.nightscout.androidaps.utils.locale.LocaleHelper
import info.nightscout.androidaps.utils.protection.PasswordCheck
import info.nightscout.androidaps.utils.protection.ProtectionCheck
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.androidaps.utils.tabs.TabPageAdapter
import info.nightscout.androidaps.utils.ui.UIRunnable
import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import java.util.*
import javax.inject.Inject
import kotlin.system.exitProcess
@ -67,7 +69,6 @@ class MainActivity : NoSplashAppCompatActivity() {
private val disposable = CompositeDisposable()
@Inject lateinit var aapsSchedulers: AapsSchedulers
@Inject lateinit var rxBus: RxBus
@Inject lateinit var androidPermission: AndroidPermission
@Inject lateinit var sp: SP
@Inject lateinit var versionCheckerUtils: VersionCheckerUtils
@ -84,12 +85,13 @@ class MainActivity : NoSplashAppCompatActivity() {
@Inject lateinit var config: Config
@Inject lateinit var uel: UserEntryLogger
@Inject lateinit var profileFunction: ProfileFunction
@Inject lateinit var passwordCheck: PasswordCheck
private lateinit var actionBarDrawerToggle: ActionBarDrawerToggle
private var pluginPreferencesMenuItem: MenuItem? = null
private var menu: Menu? = null
private var menuOpen = false
private var isProtectionCheckActive = false
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
@ -115,6 +117,7 @@ class MainActivity : NoSplashAppCompatActivity() {
override fun onPageSelected(position: Int) {
setPluginPreferenceMenuName()
checkPluginPreferences(binding.mainPager)
setDisabledMenuItemColorPluginPreferences()
}
})
@ -134,6 +137,12 @@ class MainActivity : NoSplashAppCompatActivity() {
.toObservable(EventPreferenceChange::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({ processPreferenceChange(it) }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventInitializationChanged::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({
passwordCheck.passwordResetCheck(this)
}, fabricPrivacy::logException)
if (startWizard() && !isRunningRealPumpTest()) {
protectionCheck.queryProtection(this, ProtectionCheck.Protection.PREFERENCES, {
startActivity(Intent(this, SetupWizardActivity::class.java))
@ -168,10 +177,13 @@ class MainActivity : NoSplashAppCompatActivity() {
override fun onResume() {
super.onResume()
protectionCheck.queryProtection(this, ProtectionCheck.Protection.APPLICATION, null,
UIRunnable { OKDialog.show(this, "", rh.gs(R.string.authorizationfailed)) { finish() } },
UIRunnable { OKDialog.show(this, "", rh.gs(R.string.authorizationfailed)) { finish() } }
)
if (!isProtectionCheckActive) {
isProtectionCheckActive = true
protectionCheck.queryProtection(this, ProtectionCheck.Protection.APPLICATION, UIRunnable { isProtectionCheckActive = false },
UIRunnable { OKDialog.show(this, "", rh.gs(R.string.authorizationfailed)) { isProtectionCheckActive = false; finish() } },
UIRunnable { OKDialog.show(this, "", rh.gs(R.string.authorizationfailed)) { isProtectionCheckActive = false; finish() } }
)
}
}
private fun setWakeLock() {
@ -252,6 +264,14 @@ class MainActivity : NoSplashAppCompatActivity() {
return super.dispatchTouchEvent(event)
}
private fun setDisabledMenuItemColorPluginPreferences() {
if (pluginPreferencesMenuItem?.isEnabled == false) {
val spanString = SpannableString(this.menu?.findItem(R.id.nav_plugin_preferences)?.title.toString())
spanString.setSpan(ForegroundColorSpan(rh.gac(R.attr.disabledTextColor)), 0, spanString.length, 0)
this.menu?.findItem(R.id.nav_plugin_preferences)?.title = spanString
}
}
private fun setPluginPreferenceMenuName() {
if (binding.mainPager.currentItem >= 0) {
val plugin = (binding.mainPager.adapter as TabPageAdapter).getPluginAt(binding.mainPager.currentItem)
@ -270,16 +290,18 @@ class MainActivity : NoSplashAppCompatActivity() {
}
override fun onPanelClosed(featureId: Int, menu: Menu) {
menuOpen = false;
menuOpen = false
super.onPanelClosed(featureId, menu)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menu.setGroupDividerEnabled(true)
this.menu = menu
menuInflater.inflate(R.menu.menu_main, menu)
pluginPreferencesMenuItem = menu.findItem(R.id.nav_plugin_preferences)
setPluginPreferenceMenuName()
checkPluginPreferences(binding.mainPager)
setDisabledMenuItemColorPluginPreferences()
return true
}
@ -320,7 +342,7 @@ class MainActivity : NoSplashAppCompatActivity() {
message += rh.gs(R.string.about_link_urls)
val messageSpanned = SpannableString(message)
Linkify.addLinks(messageSpanned, Linkify.WEB_URLS)
AlertDialog.Builder(this)
MaterialAlertDialogBuilder(this, R.style.DialogTheme)
.setTitle(rh.gs(R.string.app_name) + " " + BuildConfig.VERSION)
.setIcon(iconsProvider.getIcon())
.setMessage(messageSpanned)
@ -417,6 +439,8 @@ class MainActivity : NoSplashAppCompatActivity() {
// Add to crash log too
FirebaseCrashlytics.getInstance().setCustomKey("HEAD", BuildConfig.HEAD)
FirebaseCrashlytics.getInstance().setCustomKey("Version", BuildConfig.VERSION)
FirebaseCrashlytics.getInstance().setCustomKey("BuildType", BuildConfig.BUILD_TYPE)
FirebaseCrashlytics.getInstance().setCustomKey("BuildFlavor", BuildConfig.FLAVOR)
FirebaseCrashlytics.getInstance().setCustomKey("Remote", remote)
FirebaseCrashlytics.getInstance().setCustomKey("Committed", BuildConfig.COMMITTED)
FirebaseCrashlytics.getInstance().setCustomKey("Hash", hashes[0])

View file

@ -6,7 +6,13 @@ import android.content.IntentFilter
import android.net.ConnectivityManager
import android.net.wifi.WifiManager
import android.os.Build
import com.uber.rxdogtag.RxDogTag
import android.os.Handler
import android.os.HandlerThread
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.work.Data
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.PeriodicWorkRequest
import androidx.work.WorkManager
import dagger.android.AndroidInjector
import dagger.android.DaggerApplication
import info.nightscout.androidaps.database.AppRepository
@ -20,33 +26,39 @@ import info.nightscout.androidaps.di.StaticInjector
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.interfaces.ConfigBuilder
import info.nightscout.androidaps.interfaces.PluginBase
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.configBuilder.PluginStore
import info.nightscout.androidaps.plugins.constraints.versionChecker.VersionCheckerUtils
import info.nightscout.androidaps.plugins.general.overview.notifications.Notification
import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationStore
import info.nightscout.androidaps.plugins.general.themes.ThemeSwitcherPlugin
import info.nightscout.androidaps.receivers.BTReceiver
import info.nightscout.androidaps.receivers.ChargingStateReceiver
import info.nightscout.androidaps.receivers.KeepAliveReceiver.KeepAliveManager
import info.nightscout.androidaps.receivers.KeepAliveWorker
import info.nightscout.androidaps.receivers.NetworkChangeReceiver
import info.nightscout.androidaps.receivers.TimeDateOrTZChangeReceiver
import info.nightscout.androidaps.services.AlarmSoundServiceHelper
import info.nightscout.androidaps.utils.ActivityMonitor
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.buildHelper.BuildHelper
import info.nightscout.androidaps.utils.LocalAlertUtils
import info.nightscout.androidaps.utils.ProcessLifecycleListener
import info.nightscout.androidaps.interfaces.BuildHelper
import info.nightscout.androidaps.utils.locale.LocaleHelper
import info.nightscout.androidaps.utils.protection.PasswordCheck
import info.nightscout.androidaps.widget.updateWidget
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.exceptions.UndeliverableException
import io.reactivex.plugins.RxJavaPlugins
import io.reactivex.rxkotlin.plusAssign
import net.danlew.android.joda.JodaTimeAndroid
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.exceptions.UndeliverableException
import io.reactivex.rxjava3.kotlin.plusAssign
import io.reactivex.rxjava3.plugins.RxJavaPlugins
import rxdogtag2.RxDogTag
import java.io.IOException
import java.net.SocketException
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import javax.inject.Provider
class MainApp : DaggerApplication() {
@ -60,16 +72,21 @@ class MainApp : DaggerApplication() {
@Inject lateinit var config: Config
@Inject lateinit var buildHelper: BuildHelper
@Inject lateinit var configBuilder: ConfigBuilder
@Inject lateinit var keepAliveManager: KeepAliveManager
@Inject lateinit var plugins: List<@JvmSuppressWildcards PluginBase>
@Inject lateinit var compatDBHelper: CompatDBHelper
@Inject lateinit var repository: AppRepository
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var staticInjector: StaticInjector// TODO avoid , here fake only to initialize
@Inject lateinit var uel: UserEntryLogger
@Inject lateinit var passwordCheck: PasswordCheck
@Inject lateinit var alarmSoundServiceHelper: AlarmSoundServiceHelper
@Inject lateinit var notificationStore: NotificationStore
@Inject lateinit var processLifecycleListener: ProcessLifecycleListener
@Inject lateinit var profileSwitchPlugin: ThemeSwitcherPlugin
@Inject lateinit var localAlertUtils: LocalAlertUtils
@Inject lateinit var rh: Provider<ResourceHelper>
private var handler = Handler(HandlerThread(this::class.simpleName + "Handler").also { it.start() }.looper)
private lateinit var refreshWidget: Runnable
override fun onCreate() {
super.onCreate()
@ -77,6 +94,7 @@ class MainApp : DaggerApplication() {
RxDogTag.install()
setRxErrorHandler()
LocaleHelper.update(this)
ProcessLifecycleOwner.get().lifecycle.addObserver(processLifecycleListener)
var gitRemote: String? = BuildConfig.REMOTE
var commitHash: String? = BuildConfig.HEAD
@ -84,21 +102,9 @@ class MainApp : DaggerApplication() {
gitRemote = null
commitHash = null
}
disposable += repository.runTransaction(VersionChangeTransaction(BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE, gitRemote, commitHash)).subscribe()
if (sp.getBoolean(R.string.key_ns_logappstartedevent, config.APS))
disposable += repository
.runTransaction(
InsertIfNewByTimestampTherapyEventTransaction(
timestamp = dateUtil.now(),
type = TherapyEvent.Type.NOTE,
note = getString(info.nightscout.androidaps.core.R.string.androidaps_start) + " - " + Build.MANUFACTURER + " " + Build.MODEL,
glucoseUnit = TherapyEvent.GlucoseUnit.MGDL
)
)
.subscribe()
disposable += compatDBHelper.dbChangeDisposable()
registerActivityLifecycleCallbacks(activityMonitor)
JodaTimeAndroid.init(this)
profileSwitchPlugin.setThemeMode()
aapsLogger.debug("Version: " + BuildConfig.VERSION_NAME)
aapsLogger.debug("BuildVersion: " + BuildConfig.BUILDVERSION)
aapsLogger.debug("Remote: " + BuildConfig.REMOTE)
@ -106,17 +112,52 @@ class MainApp : DaggerApplication() {
// trigger here to see the new version on app start after an update
versionCheckersUtils.triggerCheckVersion()
// check if identification is set
if (buildHelper.isDev() && sp.getStringOrNull(R.string.key_email_for_crash_report, null).isNullOrBlank())
notificationStore.add(Notification(Notification.IDENTIFICATION_NOT_SET, getString(R.string.identification_not_set), Notification.INFO))
// Register all tabs in app here
pluginStore.plugins = plugins
configBuilder.initialize()
keepAliveManager.setAlarm(this)
// delayed actions to make rh context updated for translations
handler.postDelayed(
{
// check if identification is set
if (buildHelper.isDev() && sp.getStringOrNull(R.string.key_email_for_crash_report, null).isNullOrBlank())
notificationStore.add(Notification(Notification.IDENTIFICATION_NOT_SET, rh.get().gs(R.string.identification_not_set), Notification.INFO))
// log version
disposable += repository.runTransaction(VersionChangeTransaction(BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE, gitRemote, commitHash)).subscribe()
// log app start
if (sp.getBoolean(R.string.key_ns_logappstartedevent, config.APS))
disposable += repository
.runTransaction(
InsertIfNewByTimestampTherapyEventTransaction(
timestamp = dateUtil.now(),
type = TherapyEvent.Type.NOTE,
note = rh.get().gs(info.nightscout.androidaps.core.R.string.androidaps_start) + " - " + Build.MANUFACTURER + " " + Build.MODEL,
glucoseUnit = TherapyEvent.GlucoseUnit.MGDL
)
)
.subscribe()
}, 10000
)
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
"KeepAlive",
ExistingPeriodicWorkPolicy.REPLACE,
PeriodicWorkRequest.Builder(KeepAliveWorker::class.java, 15, TimeUnit.MINUTES)
.setInputData(Data.Builder().putString("schedule", "KeepAlive").build())
.setInitialDelay(5, TimeUnit.SECONDS)
.build()
)
localAlertUtils.shortenSnoozeInterval()
localAlertUtils.preSnoozeAlarms()
doMigrations()
uel.log(UserEntry.Action.START_AAPS, UserEntry.Sources.Aaps)
passwordCheck.passwordResetCheck(this)
// schedule widget update
refreshWidget = Runnable {
handler.postDelayed(refreshWidget, 60000)
updateWidget(this)
}
handler.postDelayed(refreshWidget, 60000)
}
private fun setRxErrorHandler() {
@ -147,11 +188,33 @@ class MainApp : DaggerApplication() {
}
}
@Suppress("SpellCheckingInspection")
private fun doMigrations() {
// set values for different builds
if (!sp.contains(R.string.key_ns_alarms)) sp.putBoolean(R.string.key_ns_alarms, config.NSCLIENT)
if (!sp.contains(R.string.key_ns_announcements)) sp.putBoolean(R.string.key_ns_announcements, config.NSCLIENT)
if (!sp.contains(R.string.key_language)) sp.putString(R.string.key_language, "default")
// 3.1.0
if (sp.contains("ns_wifionly")) {
if (sp.getBoolean("ns_wifionly", false)) {
sp.putBoolean(R.string.key_ns_cellular, false)
sp.putBoolean(R.string.key_ns_wifi, true)
} else {
sp.putBoolean(R.string.key_ns_cellular, true)
sp.putBoolean(R.string.key_ns_wifi, false)
}
sp.remove("ns_wifionly")
}
if (sp.contains("ns_charginonly")) {
if (sp.getBoolean("ns_charginonly", false)) {
sp.putBoolean(R.string.key_ns_battery, false)
sp.putBoolean(R.string.key_ns_charging, true)
} else {
sp.putBoolean(R.string.key_ns_battery, true)
sp.putBoolean(R.string.key_ns_charging, true)
}
sp.remove("ns_charginonly")
}
}
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
@ -186,8 +249,7 @@ class MainApp : DaggerApplication() {
override fun onTerminate() {
aapsLogger.debug(LTag.CORE, "onTerminate")
unregisterActivityLifecycleCallbacks(activityMonitor)
keepAliveManager.cancelAlarm(this)
alarmSoundServiceHelper.stopService(this)
alarmSoundServiceHelper.stopService(this, "onTerminate")
super.onTerminate()
}
}

View file

@ -1,14 +1,14 @@
package info.nightscout.androidaps.activities
import android.annotation.SuppressLint
import android.app.DatePickerDialog
import android.graphics.Color
import android.content.Context
import android.os.Bundle
import android.util.DisplayMetrics
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.RelativeLayout
import android.widget.TextView
import com.google.android.material.datepicker.MaterialDatePicker
import com.jjoe64.graphview.GraphView
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R
@ -17,33 +17,33 @@ import info.nightscout.androidaps.databinding.ActivityHistorybrowseBinding
import info.nightscout.androidaps.events.EventAutosensCalculationFinished
import info.nightscout.androidaps.events.EventCustomCalculationFinished
import info.nightscout.androidaps.events.EventRefreshOverview
import info.nightscout.androidaps.events.EventScale
import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.extensions.toVisibilityKeepSpace
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.interfaces.Loop
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus
import info.nightscout.androidaps.plugins.general.overview.OverviewData
import info.nightscout.androidaps.plugins.general.overview.OverviewMenus
import info.nightscout.androidaps.plugins.general.overview.events.EventUpdateOverviewGraph
import info.nightscout.androidaps.plugins.general.overview.graphData.GraphData
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventBucketedDataCreated
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress
import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin
import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref1Plugin
import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin
import info.nightscout.androidaps.receivers.DataWorker
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.DefaultValueHelper
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.Translator
import info.nightscout.androidaps.utils.buildHelper.BuildHelper
import info.nightscout.androidaps.interfaces.BuildHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.androidaps.workflow.CalculationWorkflow
import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import java.util.*
import javax.inject.Inject
import kotlin.math.min
@ -52,15 +52,11 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() {
@Inject lateinit var injector: HasAndroidInjector
@Inject lateinit var aapsSchedulers: AapsSchedulers
@Inject lateinit var rxBus: RxBus
@Inject lateinit var sp: SP
@Inject lateinit var profileFunction: ProfileFunction
@Inject lateinit var defaultValueHelper: DefaultValueHelper
@Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var buildHelper: BuildHelper
@Inject lateinit var sensitivityOref1Plugin: SensitivityOref1Plugin
@Inject lateinit var sensitivityAAPSPlugin: SensitivityAAPSPlugin
@Inject lateinit var sensitivityWeightedAveragePlugin: SensitivityWeightedAveragePlugin
@Inject lateinit var repository: AppRepository
@Inject lateinit var fabricPrivacy: FabricPrivacy
@Inject lateinit var overviewMenus: OverviewMenus
@ -69,6 +65,9 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() {
@Inject lateinit var loop: Loop
@Inject lateinit var nsDeviceStatus: NSDeviceStatus
@Inject lateinit var translator: Translator
@Inject lateinit var context: Context
@Inject lateinit var dataWorker: DataWorker
@Inject lateinit var calculationWorkflow: CalculationWorkflow
private val disposable = CompositeDisposable()
@ -91,6 +90,18 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() {
setContentView(binding.root)
// We don't want to use injected singletons but own instance working on top of different data
overviewData =
OverviewData(
aapsLogger,
rh,
dateUtil,
sp,
activePlugin,
defaultValueHelper,
profileFunction,
repository,
fabricPrivacy
)
iobCobCalculator =
IobCobCalculatorPlugin(
injector,
@ -101,30 +112,11 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() {
rh,
profileFunction,
activePlugin,
sensitivityOref1Plugin,
sensitivityAAPSPlugin,
sensitivityWeightedAveragePlugin,
fabricPrivacy,
dateUtil,
repository
)
overviewData =
OverviewData(
injector,
aapsLogger,
rh,
dateUtil,
sp,
activePlugin,
defaultValueHelper,
profileFunction,
config,
loop,
nsDeviceStatus,
repository,
overviewMenus,
iobCobCalculator,
translator
overviewData,
calculationWorkflow
)
binding.left.setOnClickListener {
@ -140,10 +132,9 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() {
loadAll("onClickEnd")
}
binding.zoom.setOnClickListener {
rangeToDisplay += 6
rangeToDisplay = if (rangeToDisplay > 24) 6 else rangeToDisplay
setTime(overviewData.fromTime)
loadAll("rangeChange")
var hours = rangeToDisplay + 6
hours = if (hours > 24) 6 else hours
rxBus.send(EventScale(hours))
}
binding.zoom.setOnLongClickListener {
Calendar.getInstance().also { calendar ->
@ -158,47 +149,34 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() {
true
}
// create an OnDateSetListener
val dateSetListener = DatePickerDialog.OnDateSetListener { _, year, monthOfYear, dayOfMonth ->
Calendar.getInstance().also { calendar ->
calendar.timeInMillis = overviewData.fromTime
calendar[Calendar.YEAR] = year
calendar[Calendar.MONTH] = monthOfYear
calendar[Calendar.DAY_OF_MONTH] = dayOfMonth
calendar[Calendar.MILLISECOND] = 0
calendar[Calendar.SECOND] = 0
calendar[Calendar.MINUTE] = 0
calendar[Calendar.HOUR_OF_DAY] = 0
setTime(calendar.timeInMillis)
binding.date.text = dateUtil.dateAndTimeString(overviewData.fromTime)
}
loadAll("onClickDate")
}
binding.date.setOnClickListener {
val cal = Calendar.getInstance()
cal.timeInMillis = overviewData.fromTime
DatePickerDialog(
this, dateSetListener,
cal.get(Calendar.YEAR),
cal.get(Calendar.MONTH),
cal.get(Calendar.DAY_OF_MONTH)
).show()
MaterialDatePicker.Builder.datePicker()
.setSelection(dateUtil.timeStampToUtcDateMillis(overviewData.fromTime))
.setTheme(R.style.DatePicker)
.build()
.apply {
addOnPositiveButtonClickListener { selection ->
setTime(dateUtil.mergeUtcDateToTimestamp(overviewData.fromTime, selection))
binding.date.text = dateUtil.dateAndTimeString(overviewData.fromTime)
loadAll("onClickDate")
}
}
.show(supportFragmentManager, "history_date_picker")
}
val dm = DisplayMetrics()
@Suppress("DEPRECATION")
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R)
display?.getRealMetrics(dm)
else
@Suppress("DEPRECATION") windowManager.defaultDisplay.getMetrics(dm)
windowManager.defaultDisplay.getMetrics(dm)
axisWidth = if (dm.densityDpi <= 120) 3 else if (dm.densityDpi <= 160) 10 else if (dm.densityDpi <= 320) 35 else if (dm.densityDpi <= 420) 50 else if (dm.densityDpi <= 560) 70 else 80
binding.bgGraph.gridLabelRenderer?.gridColor = rh.gc(R.color.graphgrid)
binding.bgGraph.gridLabelRenderer?.gridColor = rh.gac(this, R.attr.graphGrid)
binding.bgGraph.gridLabelRenderer?.reloadStyles()
binding.bgGraph.gridLabelRenderer?.labelVerticalWidth = axisWidth
overviewMenus.setupChartMenu(binding.chartMenuButton)
overviewMenus.setupChartMenu(context, binding.chartMenuButton)
prepareGraphsIfNeeded(overviewMenus.setting.size)
savedInstanceState?.let { bundle ->
rangeToDisplay = bundle.getInt("rangeToDisplay", 0)
@ -210,7 +188,7 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() {
override fun onPause() {
super.onPause()
disposable.clear()
iobCobCalculator.stopCalculation("onPause")
calculationWorkflow.stopCalculation(CalculationWorkflow.HISTORY_CALCULATION, "onPause")
}
@Synchronized
@ -232,21 +210,18 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() {
disposable += rxBus
.toObservable(EventIobCalculationProgress::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({
if (it.cause is EventCustomCalculationFinished)
binding.overviewIobcalculationprogess.text = it.progress
}, fabricPrivacy::logException)
.subscribe({ updateCalcProgress(it.pass.finalPercent(it.progressPct)) }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventRefreshOverview::class.java)
.toObservable(EventUpdateOverviewGraph::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({ updateGUI("EventRefreshOverview") }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventBucketedDataCreated::class.java)
.observeOn(aapsSchedulers.io)
.toObservable(EventScale::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({
overviewData.prepareBucketedData("EventBucketedDataCreated")
overviewData.prepareBgData("EventBucketedDataCreated")
rxBus.send(EventRefreshOverview("EventBucketedDataCreated"))
rangeToDisplay = it.hours
setTime(overviewData.fromTime)
loadAll("rangeChange")
}, fabricPrivacy::logException)
if (overviewData.fromTime == 0L) {
@ -278,12 +253,12 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() {
val graph = GraphView(this)
graph.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, rh.dpToPx(100)).also { it.setMargins(0, rh.dpToPx(15), 0, rh.dpToPx(10)) }
graph.gridLabelRenderer?.gridColor = rh.gc(R.color.graphgrid)
graph.gridLabelRenderer?.gridColor = rh.gac(R.attr.graphGrid)
graph.gridLabelRenderer?.reloadStyles()
graph.gridLabelRenderer?.isHorizontalLabelsVisible = false
graph.gridLabelRenderer?.labelVerticalWidth = axisWidth
graph.gridLabelRenderer?.numVerticalLabels = 3
graph.viewport.backgroundColor = Color.argb(20, 255, 255, 255) // 8% of gray
graph.viewport.backgroundColor = rh.gac(this, R.attr.viewPortBackgroundColor)
relativeLayout.addView(graph)
val label = TextView(this)
@ -303,17 +278,7 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() {
@Suppress("SameParameterValue")
private fun loadAll(from: String) {
updateDate()
Thread {
overviewData.prepareBgData("$from")
overviewData.prepareTreatmentsData(from)
rxBus.send(EventRefreshOverview("loadAll_$from"))
overviewData.prepareTemporaryTargetData(from)
rxBus.send(EventRefreshOverview("loadAll_$from"))
overviewData.prepareBasalData(from)
rxBus.send(EventRefreshOverview(from))
aapsLogger.debug(LTag.UI, "loadAll $from finished")
runCalculation(from)
}.start()
runCalculation(from)
}
private fun setTime(start: Long) {
@ -334,25 +299,32 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() {
}
private fun runCalculation(from: String) {
Thread {
iobCobCalculator.stopCalculation(from)
iobCobCalculator.stopCalculationTrigger = false
iobCobCalculator.runCalculation(from, overviewData.toTime, bgDataReload = true, limitDataToOldestAvailable = false, cause = EventCustomCalculationFinished())
}.start()
calculationWorkflow.runCalculation(
CalculationWorkflow.HISTORY_CALCULATION,
iobCobCalculator,
overviewData,
from,
overviewData.toTime,
bgDataReload = true,
limitDataToOldestAvailable = false,
cause = EventCustomCalculationFinished(),
runLoop = false
)
}
@Volatile
var runningRefresh = false
@Suppress("SameParameterValue")
private fun refreshLoop(from: String) {
if (runningRefresh) return
runningRefresh = true
overviewData.prepareIobAutosensData(from)
rxBus.send(EventRefreshOverview(from))
aapsLogger.debug(LTag.UI, "refreshLoop finished")
runningRefresh = false
}
fun updateDate() {
private fun updateDate() {
binding.date.text = dateUtil.dateAndTimeString(overviewData.fromTime)
binding.zoom.text = rangeToDisplay.toString()
}
@ -368,9 +340,12 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() {
val graphData = GraphData(injector, binding.bgGraph, overviewData)
val menuChartSettings = overviewMenus.setting
graphData.addInRangeArea(overviewData.fromTime, overviewData.endTime, defaultValueHelper.determineLowLine(), defaultValueHelper.determineHighLine())
graphData.addBgReadings(menuChartSettings[0][OverviewMenus.CharType.PRE.ordinal])
graphData.addBgReadings(menuChartSettings[0][OverviewMenus.CharType.PRE.ordinal], context)
if (buildHelper.isDev()) graphData.addBucketedData()
graphData.addTreatments()
graphData.addTreatments(context)
graphData.addEps(context, 0.95)
if (menuChartSettings[0][OverviewMenus.CharType.TREAT.ordinal])
graphData.addTherapyEvents()
if (menuChartSettings[0][OverviewMenus.CharType.ACT.ordinal])
graphData.addActivity(0.8)
if (pump.pumpDescription.isTempBasalCapable && menuChartSettings[0][OverviewMenus.CharType.BAS.ordinal])
@ -399,12 +374,12 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() {
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.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]
@ -436,4 +411,9 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() {
secondaryGraphsData[g].performUpdate()
}
}
}
private fun updateCalcProgress(percent: Int) {
binding.progressBar.progress = percent
binding.progressBar.visibility = (percent != 100).toVisibilityKeepSpace()
}
}

View file

@ -8,17 +8,17 @@ import android.os.Bundle
import androidx.annotation.XmlRes
import androidx.preference.*
import dagger.android.support.AndroidSupportInjection
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.R
import info.nightscout.androidaps.danaRKorean.DanaRKoreanPlugin
import info.nightscout.androidaps.danaRv2.DanaRv2Plugin
import info.nightscout.androidaps.danar.DanaRPlugin
import info.nightscout.androidaps.danars.DanaRSPlugin
import info.nightscout.androidaps.diaconn.DiaconnG8Plugin
import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.events.EventPreferenceChange
import info.nightscout.androidaps.events.EventRebuildTabs
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.interfaces.PluginBase
import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.plugin.general.openhumans.OpenHumansUploader
import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin
@ -28,6 +28,7 @@ import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.configBuilder.PluginStore
import info.nightscout.androidaps.plugins.constraints.safety.SafetyPlugin
import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin
import info.nightscout.androidaps.plugins.general.autotune.AutotunePlugin
import info.nightscout.androidaps.plugins.general.maintenance.MaintenancePlugin
import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin
import info.nightscout.androidaps.plugins.general.nsclient.data.NSSettingsStatus
@ -44,17 +45,13 @@ import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin
import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin
import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref1Plugin
import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin
import info.nightscout.androidaps.plugins.source.DexcomPlugin
import info.nightscout.androidaps.plugins.source.EversensePlugin
import info.nightscout.androidaps.plugins.source.GlimpPlugin
import info.nightscout.androidaps.plugins.source.PoctechPlugin
import info.nightscout.androidaps.plugins.source.TomatoPlugin
import info.nightscout.androidaps.plugins.source.GlunovoPlugin
import info.nightscout.shared.SafeParse
import info.nightscout.androidaps.plugins.source.*
import info.nightscout.androidaps.utils.alertDialogs.OKDialog.show
import info.nightscout.androidaps.utils.protection.PasswordCheck
import info.nightscout.androidaps.utils.protection.ProtectionCheck
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.protection.ProtectionCheck.ProtectionType.*
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.plugins.aps.openAPSSMBDynamicISF.OpenAPSSMBDynamicISFPlugin
import info.nightscout.shared.SafeParse
import info.nightscout.shared.sharedPreferences.SP
import javax.inject.Inject
@ -71,6 +68,7 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
@Inject lateinit var config: Config
@Inject lateinit var automationPlugin: AutomationPlugin
@Inject lateinit var autotunePlugin: AutotunePlugin
@Inject lateinit var danaRPlugin: DanaRPlugin
@Inject lateinit var danaRKoreanPlugin: DanaRKoreanPlugin
@Inject lateinit var danaRv2Plugin: DanaRv2Plugin
@ -83,6 +81,7 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
@Inject lateinit var nsClientPlugin: NSClientPlugin
@Inject lateinit var openAPSAMAPlugin: OpenAPSAMAPlugin
@Inject lateinit var openAPSSMBPlugin: OpenAPSSMBPlugin
@Inject lateinit var openAPSSMBDynamicISFPlugin: OpenAPSSMBDynamicISFPlugin
@Inject lateinit var safetyPlugin: SafetyPlugin
@Inject lateinit var sensitivityAAPSPlugin: SensitivityAAPSPlugin
@Inject lateinit var sensitivityOref1Plugin: SensitivityOref1Plugin
@ -93,6 +92,7 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
@Inject lateinit var poctechPlugin: PoctechPlugin
@Inject lateinit var tomatoPlugin: TomatoPlugin
@Inject lateinit var glunovoPlugin: GlunovoPlugin
@Inject lateinit var aidexPlugin: AidexPlugin
@Inject lateinit var smsCommunicatorPlugin: SmsCommunicatorPlugin
@Inject lateinit var statusLinePlugin: StatusLinePlugin
@Inject lateinit var tidepoolPlugin: TidepoolPlugin
@ -125,9 +125,11 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
override fun onDestroy() {
super.onDestroy()
PreferenceManager
.getDefaultSharedPreferences(context)
.unregisterOnSharedPreferenceChangeListener(this)
context?.let { context ->
PreferenceManager
.getDefaultSharedPreferences(context)
.unregisterOnSharedPreferenceChangeListener(this)
}
}
private fun addPreferencesFromResourceIfEnabled(p: PluginBase?, rootKey: String?, enabled: Boolean) {
@ -141,9 +143,11 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
PreferenceManager
.getDefaultSharedPreferences(context)
.registerOnSharedPreferenceChangeListener(this)
context?.let { context ->
PreferenceManager
.getDefaultSharedPreferences(context)
.registerOnSharedPreferenceChangeListener(this)
}
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
@ -166,10 +170,12 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
addPreferencesFromResourceIfEnabled(tomatoPlugin, rootKey)
addPreferencesFromResourceIfEnabled(glunovoPlugin, rootKey)
addPreferencesFromResourceIfEnabled(poctechPlugin, rootKey)
addPreferencesFromResourceIfEnabled(aidexPlugin, rootKey)
addPreferencesFromResourceIfEnabled(glimpPlugin, rootKey)
addPreferencesFromResourceIfEnabled(loopPlugin, rootKey, config.APS)
addPreferencesFromResourceIfEnabled(openAPSAMAPlugin, rootKey, config.APS)
addPreferencesFromResourceIfEnabled(openAPSSMBPlugin, rootKey, config.APS)
addPreferencesFromResourceIfEnabled(openAPSSMBDynamicISFPlugin, rootKey, config.APS)
addPreferencesFromResourceIfEnabled(sensitivityAAPSPlugin, rootKey)
addPreferencesFromResourceIfEnabled(sensitivityWeightedAveragePlugin, rootKey)
addPreferencesFromResourceIfEnabled(sensitivityOref1Plugin, rootKey)
@ -189,6 +195,7 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
addPreferencesFromResourceIfEnabled(tidepoolPlugin, rootKey)
addPreferencesFromResourceIfEnabled(smsCommunicatorPlugin, rootKey)
addPreferencesFromResourceIfEnabled(automationPlugin, rootKey)
addPreferencesFromResourceIfEnabled(autotunePlugin, rootKey)
addPreferencesFromResourceIfEnabled(wearPlugin, rootKey)
addPreferencesFromResourceIfEnabled(statusLinePlugin, rootKey)
addPreferencesFromResource(R.xml.pref_alerts, rootKey)
@ -238,7 +245,7 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
rh.gs(R.string.key_application_protection) == key ||
rh.gs(R.string.key_bolus_protection) == key) &&
sp.getString(R.string.key_master_password, "") == "" &&
sp.getInt(key, ProtectionCheck.ProtectionType.NONE.ordinal) == ProtectionCheck.ProtectionType.BIOMETRIC.ordinal
sp.getInt(key, NONE.ordinal) == BIOMETRIC.ordinal
) {
activity?.let {
val title = rh.gs(R.string.unsecure_fallback_biometric)
@ -248,9 +255,9 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
}
// Master password erased with activated Biometric protection
val isBiometricActivated = sp.getInt(R.string.key_settings_protection, ProtectionCheck.ProtectionType.NONE.ordinal) == ProtectionCheck.ProtectionType.BIOMETRIC.ordinal ||
sp.getInt(R.string.key_application_protection, ProtectionCheck.ProtectionType.NONE.ordinal) == ProtectionCheck.ProtectionType.BIOMETRIC.ordinal ||
sp.getInt(R.string.key_bolus_protection, ProtectionCheck.ProtectionType.NONE.ordinal) == ProtectionCheck.ProtectionType.BIOMETRIC.ordinal
val isBiometricActivated = sp.getInt(R.string.key_settings_protection, NONE.ordinal) == BIOMETRIC.ordinal ||
sp.getInt(R.string.key_application_protection, NONE.ordinal) == BIOMETRIC.ordinal ||
sp.getInt(R.string.key_bolus_protection, NONE.ordinal) == BIOMETRIC.ordinal
if (rh.gs(R.string.key_master_password) == key && sp.getString(key, "") == "" && isBiometricActivated) {
activity?.let {
val title = rh.gs(R.string.unsecure_fallback_biometric)
@ -266,19 +273,19 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
@SuppressLint("RestrictedApi")
private fun addPreferencesFromResource(@XmlRes preferencesResId: Int, key: String?) {
val xmlRoot = preferenceManager.inflateFromResource(context,
preferencesResId, null)
val root: Preference?
if (key != null) {
root = xmlRoot.findPreference(key)
if (root == null) return
require(root is PreferenceScreen) {
("Preference object with key " + key
+ " is not a PreferenceScreen")
context?.let { context ->
val xmlRoot = preferenceManager.inflateFromResource(context, preferencesResId, null)
val root: Preference?
if (key != null) {
root = xmlRoot.findPreference(key)
if (root == null) return
require(root is PreferenceScreen) {
("Preference object with key $key is not a PreferenceScreen")
}
preferenceScreen = root
} else {
addPreferencesFromResource(preferencesResId)
}
preferenceScreen = root
} else {
addPreferencesFromResource(preferencesResId)
}
}
@ -308,15 +315,9 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
p.initialExpandedChildrenCount = Int.MAX_VALUE
}
} else {
if (p.key != null) {
visible = visible || p.key.contains(filter, true)
}
if (p.title != null) {
visible = visible || p.title.contains(filter, true)
}
if (p.summary != null) {
visible = visible || p.summary.contains(filter, true)
}
visible = visible || p.key?.contains(filter, true) == true
visible = visible || p.title?.contains(filter, true) == true
visible = visible || p.summary?.contains(filter, true) == true
}
p.isVisible = visible
@ -327,26 +328,35 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
if (pref is ListPreference) {
pref.setSummary(pref.entry)
// Preferences
// Preferences
if (pref.getKey() == rh.gs(R.string.key_settings_protection)) {
val pass: Preference? = findPreference(rh.gs(R.string.key_settings_password))
if (pass != null) pass.isEnabled = pref.value == ProtectionCheck.ProtectionType.CUSTOM_PASSWORD.ordinal.toString()
val usePassword = pref.value == CUSTOM_PASSWORD.ordinal.toString()
pass?.let { it.isVisible = usePassword }
val pin: Preference? = findPreference(rh.gs(R.string.key_settings_pin))
val usePin = pref.value == CUSTOM_PIN.ordinal.toString()
pin?.let { it.isVisible = usePin }
}
// Application
// Application
if (pref.getKey() == rh.gs(R.string.key_application_protection)) {
val pass: Preference? = findPreference(rh.gs(R.string.key_application_password))
if (pass != null) pass.isEnabled = pref.value == ProtectionCheck.ProtectionType.CUSTOM_PASSWORD.ordinal.toString()
val usePassword = pref.value == CUSTOM_PASSWORD.ordinal.toString()
pass?.let { it.isVisible = usePassword }
val pin: Preference? = findPreference(rh.gs(R.string.key_application_pin))
val usePin = pref.value == CUSTOM_PIN.ordinal.toString()
pin?.let { it.isVisible = usePin }
}
// Bolus
// Bolus
if (pref.getKey() == rh.gs(R.string.key_bolus_protection)) {
val pass: Preference? = findPreference(rh.gs(R.string.key_bolus_password))
if (pass != null) pass.isEnabled = pref.value == ProtectionCheck.ProtectionType.CUSTOM_PASSWORD.ordinal.toString()
val usePassword = pref.value == CUSTOM_PASSWORD.ordinal.toString()
pass?.let { it.isVisible = usePassword }
val pin: Preference? = findPreference(rh.gs(R.string.key_bolus_pin))
val usePin = pref.value == CUSTOM_PIN.ordinal.toString()
pin?.let { it.isVisible = usePin }
}
}
if (pref is EditTextPreference) {
if (pref.getKey().contains("password") || pref.getKey().contains("secret")) {
if (pref.getKey().contains("password") || pref.getKey().contains("pin") || pref.getKey().contains("secret") || pref.getKey().contains("token")) {
pref.setSummary("******")
} else if (pref.text != null) {
pref.dialogMessage = pref.dialogMessage
@ -362,7 +372,10 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
rh.gs(R.string.key_bolus_password),
rh.gs(R.string.key_master_password),
rh.gs(R.string.key_application_password),
rh.gs(R.string.key_settings_password)
rh.gs(R.string.key_settings_password),
rh.gs(R.string.key_bolus_pin),
rh.gs(R.string.key_application_pin),
rh.gs(R.string.key_settings_pin)
)
if (pref is Preference) {
@ -370,7 +383,11 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
if (sp.getString(pref.key, "").startsWith("hmac:")) {
pref.summary = "******"
} else {
pref.summary = rh.gs(R.string.password_not_set)
if (pref.key.contains("pin")) {
pref.summary = rh.gs(R.string.pin_not_set)
}else {
pref.summary = rh.gs(R.string.password_not_set)
}
}
}
}
@ -396,32 +413,42 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
// We use Preference and custom editor instead of EditTextPreference
// to hash password while it is saved and never have to show it, even hashed
override fun onPreferenceTreeClick(preference: Preference?): Boolean {
override fun onPreferenceTreeClick(preference: Preference): Boolean {
context?.let { context ->
if (preference != null) {
if (preference.key == rh.gs(R.string.key_master_password)) {
passwordCheck.queryPassword(context, R.string.current_master_password, R.string.key_master_password, {
passwordCheck.setPassword(context, R.string.master_password, R.string.key_master_password)
})
return true
}
if (preference.key == rh.gs(R.string.key_settings_password)) {
passwordCheck.setPassword(context, R.string.settings_password, R.string.key_settings_password)
return true
}
if (preference.key == rh.gs(R.string.key_bolus_password)) {
passwordCheck.setPassword(context, R.string.bolus_password, R.string.key_bolus_password)
return true
}
if (preference.key == rh.gs(R.string.key_application_password)) {
passwordCheck.setPassword(context, R.string.application_password, R.string.key_application_password)
return true
}
// NSClient copy settings
if (preference.key == rh.gs(R.string.key_statuslights_copy_ns)) {
nsSettingStatus.copyStatusLightsNsSettings(context)
return true
}
if (preference.key == rh.gs(R.string.key_master_password)) {
passwordCheck.queryPassword(context, R.string.current_master_password, R.string.key_master_password, {
passwordCheck.setPassword(context, R.string.master_password, R.string.key_master_password)
})
return true
}
if (preference.key == rh.gs(R.string.key_settings_password)) {
passwordCheck.setPassword(context, R.string.settings_password, R.string.key_settings_password)
return true
}
if (preference.key == rh.gs(R.string.key_bolus_password)) {
passwordCheck.setPassword(context, R.string.bolus_password, R.string.key_bolus_password)
return true
}
if (preference.key == rh.gs(R.string.key_application_password)) {
passwordCheck.setPassword(context, R.string.application_password, R.string.key_application_password)
return true
}
if (preference.key == rh.gs(R.string.key_settings_pin)) {
passwordCheck.setPassword(context, R.string.settings_pin, R.string.key_settings_pin, pinInput = true)
return true
}
if (preference.key == rh.gs(R.string.key_bolus_pin)) {
passwordCheck.setPassword(context, R.string.bolus_pin, R.string.key_bolus_pin, pinInput = true)
return true
}
if (preference.key == rh.gs(R.string.key_application_pin)) {
passwordCheck.setPassword(context, R.string.application_pin, R.string.key_application_pin, pinInput = true)
return true
}
// NSClient copy settings
if (preference.key == rh.gs(R.string.key_statuslights_copy_ns)) {
nsSettingStatus.copyStatusLightsNsSettings(context)
return true
}
}
return super.onPreferenceTreeClick(preference)
@ -431,4 +458,4 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
this.filter = filter
preferenceManager?.preferenceScreen?.let { updateFilterVisibility(filter, it) }
}
}
}

View file

@ -2,8 +2,9 @@ package info.nightscout.androidaps.activities
import android.content.Context
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.widget.SearchView
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceScreen
import info.nightscout.androidaps.R
@ -14,23 +15,16 @@ class PreferencesActivity : NoSplashAppCompatActivity(), PreferenceFragmentCompa
private var preferenceId = 0
private var myPreferenceFragment: MyPreferenceFragment? = null
private var searchView: SearchView? = null
private lateinit var binding: ActivityPreferencesBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setTheme(R.style.AppTheme)
binding = ActivityPreferencesBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.prefFilter.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
filterPreferences()
}
override fun afterTextChanged(s: Editable) {}
})
title = rh.gs(R.string.nav_preferences)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
@ -38,12 +32,29 @@ class PreferencesActivity : NoSplashAppCompatActivity(), PreferenceFragmentCompa
preferenceId = intent.getIntExtra("id", -1)
myPreferenceFragment?.arguments = Bundle().also {
it.putInt("id", preferenceId)
it.putString("filter", binding.prefFilter.text.toString())
}
if (savedInstanceState == null)
supportFragmentManager.beginTransaction().replace(R.id.frame_layout, myPreferenceFragment!!).commit()
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_preferences, menu)
val searchItem = menu.findItem(R.id.menu_search)
searchView = searchItem.actionView as SearchView
searchView?.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextChange(newText: String): Boolean {
myPreferenceFragment?.setFilter(newText)
return false
}
override fun onQueryTextSubmit(query: String): Boolean {
return false
}
})
return super.onCreateOptionsMenu(menu)
}
override fun onPreferenceStartScreen(caller: PreferenceFragmentCompat, pref: PreferenceScreen): Boolean {
val fragment = MyPreferenceFragment()
fragment.arguments = Bundle().also {
@ -58,7 +69,13 @@ class PreferencesActivity : NoSplashAppCompatActivity(), PreferenceFragmentCompa
super.attachBaseContext(LocaleHelper.wrap(newBase))
}
private fun filterPreferences() {
myPreferenceFragment?.setFilter(binding.prefFilter.text.toString())
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> {
onBackPressed()
return true
}
}
return super.onOptionsItemSelected(item)
}
}

View file

@ -1,12 +1,13 @@
package info.nightscout.androidaps.activities
import android.annotation.SuppressLint
import android.content.res.ColorStateList
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.Menu
import android.widget.PopupMenu
import android.widget.ArrayAdapter
import android.widget.TextView
import com.google.android.material.tabs.TabLayout
import com.google.common.collect.Lists
import info.nightscout.androidaps.R
import info.nightscout.androidaps.data.ProfileSealed
import info.nightscout.androidaps.data.PureProfile
@ -17,7 +18,6 @@ import info.nightscout.androidaps.dialogs.ProfileViewerDialog
import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin
import info.nightscout.androidaps.plugins.profile.local.events.EventLocalProfileChanged
import info.nightscout.androidaps.utils.DateUtil
@ -37,7 +37,6 @@ class ProfileHelperActivity : NoSplashAppCompatActivity() {
@Inject lateinit var defaultProfile: DefaultProfile
@Inject lateinit var defaultProfileDPV: DefaultProfileDPV
@Inject lateinit var localProfilePlugin: LocalProfilePlugin
@Inject lateinit var rxBus: RxBus
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var repository: AppRepository
@ -66,71 +65,59 @@ class ProfileHelperActivity : NoSplashAppCompatActivity() {
private lateinit var binding: ActivityProfilehelperBinding
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityProfilehelperBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.menu1.setOnClickListener {
switchTab(0, typeSelected[0])
}
binding.menu2.setOnClickListener {
switchTab(1, typeSelected[1])
}
binding.tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
switchTab(tab.position, typeSelected[tab.position])
}
binding.profiletype.setOnClickListener {
PopupMenu(this, binding.profiletype).apply {
menuInflater.inflate(R.menu.menu_profilehelper, menu)
setOnMenuItemClickListener { item ->
binding.profiletype.setText(item.title)
when (item.itemId) {
R.id.menu_default -> switchTab(tabSelected, ProfileType.MOTOL_DEFAULT)
R.id.menu_default_dpv -> switchTab(tabSelected, ProfileType.DPV_DEFAULT)
R.id.menu_current -> switchTab(tabSelected, ProfileType.CURRENT)
R.id.menu_available -> switchTab(tabSelected, ProfileType.AVAILABLE_PROFILE)
R.id.menu_profileswitch -> switchTab(tabSelected, ProfileType.PROFILE_SWITCH)
}
true
}
show()
override fun onTabUnselected(tab: TabLayout.Tab) {}
override fun onTabReselected(tab: TabLayout.Tab) {}
})
val profileTypeList = Lists.newArrayList(
rh.gs(R.string.motoldefaultprofile),
rh.gs(R.string.dpvdefaultprofile),
rh.gs(R.string.currentprofile),
rh.gs(R.string.availableprofile),
rh.gs(R.string.careportal_profileswitch)
)
binding.profileType.setAdapter(ArrayAdapter(this, R.layout.spinner_centered, profileTypeList))
binding.profileType.setOnItemClickListener { _, _, _, _ ->
when (binding.profileType.text.toString()) {
rh.gs(R.string.motoldefaultprofile) -> switchTab(tabSelected, ProfileType.MOTOL_DEFAULT)
rh.gs(R.string.dpvdefaultprofile) -> switchTab(tabSelected, ProfileType.DPV_DEFAULT)
rh.gs(R.string.currentprofile) -> switchTab(tabSelected, ProfileType.CURRENT)
rh.gs(R.string.availableprofile) -> switchTab(tabSelected, ProfileType.AVAILABLE_PROFILE)
rh.gs(R.string.careportal_profileswitch) -> switchTab(tabSelected, ProfileType.PROFILE_SWITCH)
}
}
// Active profile
profileList = activePlugin.activeProfileSource.profile?.getProfileList() ?: ArrayList()
binding.availableProfileList.setOnClickListener {
PopupMenu(this, binding.availableProfileList).apply {
var order = 0
for (name in profileList) menu.add(Menu.NONE, order, order++, name)
setOnMenuItemClickListener { item ->
binding.availableProfileList.setText(item.title)
profileUsed[tabSelected] = item.itemId
true
}
show()
}
binding.availableProfileList.setAdapter(ArrayAdapter(this, R.layout.spinner_centered, profileList))
binding.availableProfileList.setOnItemClickListener { _, _, index, _ ->
profileUsed[tabSelected] = index
}
// Profile switch
profileSwitch = repository.getEffectiveProfileSwitchDataFromTime(dateUtil.now() - T.months(2).msecs(), true).blockingGet()
binding.profileswitchList.setOnClickListener {
PopupMenu(this, binding.profileswitchList).apply {
var order = 0
for (name in profileSwitch) menu.add(Menu.NONE, order, order++, name.originalCustomizedName)
setOnMenuItemClickListener { item ->
binding.profileswitchList.setText(item.title)
profileSwitchUsed[tabSelected] = item.itemId
true
}
show()
}
val profileswitchListNames = profileSwitch.map { it.originalCustomizedName }
binding.profileswitchList.setAdapter(ArrayAdapter(this, R.layout.spinner_centered, profileswitchListNames))
binding.profileswitchList.setOnItemClickListener { _, _, index, _ ->
profileSwitchUsed[tabSelected] = index
}
// Default profile
binding.copytolocalprofile.setOnClickListener {
binding.copyToLocalProfile.setOnClickListener {
storeValues()
val age = ageUsed[tabSelected]
val weight = weightUsed[tabSelected]
@ -168,20 +155,22 @@ class ProfileHelperActivity : NoSplashAppCompatActivity() {
}
})
binding.basalpctfromtdd.setParams(32.0, 32.0, 37.0, 1.0, DecimalFormat("0"), false, null)
binding.basalPctFromTdd.setParams(32.0, 32.0, 37.0, 1.0, DecimalFormat("0"), false, null)
@SuppressLint("SetTextI18n")
binding.tdds.text = getString(R.string.tdd) + ": " + rh.gs(R.string.calculation_in_progress)
binding.tdds.addView(TextView(this).apply { text = rh.gs(R.string.tdd) + ": " + rh.gs(R.string.calculation_in_progress) })
Thread {
val tdds = tddCalculator.stats()
runOnUiThread { binding.tdds.text = tdds }
val tdds = tddCalculator.stats(this)
runOnUiThread {
binding.tdds.removeAllViews()
binding.tdds.addView(tdds)
}
}.start()
// Current profile
binding.currentProfileText.text = profileFunction.getProfileName()
// General
binding.compareprofile.setOnClickListener {
binding.compareProfiles.setOnClickListener {
storeValues()
for (i in 0..1) {
if (typeSelected[i] == ProfileType.MOTOL_DEFAULT) {
@ -239,12 +228,16 @@ class ProfileHelperActivity : NoSplashAppCompatActivity() {
}
ToastUtils.showToastInUiThread(this, R.string.invalidinput)
}
binding.ageLabel.labelFor = binding.age.editTextId
binding.tddLabel.labelFor = binding.tdd.editTextId
binding.weightLabel.labelFor = binding.weight.editTextId
binding.basalPctFromTddLabel.labelFor = binding.basalPctFromTdd.editTextId
switchTab(0, typeSelected[0], false)
}
private fun getProfile(age: Double, tdd: Double, weight: Double, basalPct: Double, tab: Int): PureProfile? =
try { // profile must not exist
try { // Profile must not exist
when (typeSelected[tab]) {
ProfileType.MOTOL_DEFAULT -> defaultProfile.profile(age, tdd, weight, profileFunction.getUnits())
ProfileType.DPV_DEFAULT -> defaultProfileDPV.profile(age, tdd, basalPct, profileFunction.getUnits())
@ -269,48 +262,45 @@ class ProfileHelperActivity : NoSplashAppCompatActivity() {
ageUsed[tabSelected] = binding.age.value
weightUsed[tabSelected] = binding.weight.value
tddUsed[tabSelected] = binding.tdd.value
pctUsed[tabSelected] = binding.basalpctfromtdd.value
pctUsed[tabSelected] = binding.basalPctFromTdd.value
}
private fun switchTab(tab: Int, newContent: ProfileType, storeOld: Boolean = true) {
setBackgroundColorOnSelected(tab)
// Store values for selected tab. listBox values are stored on selection change
if (storeOld) storeValues()
tabSelected = tab
typeSelected[tabSelected] = newContent
binding.profiletypeTitle.defaultHintTextColor = ColorStateList.valueOf(rh.gc(if (tab == 0) R.color.tabBgColorSelected else R.color.examinedProfile))
// show new content
binding.profiletype.setText(
// Show new content
binding.profileType.setText(
when (typeSelected[tabSelected]) {
ProfileType.MOTOL_DEFAULT -> rh.gs(R.string.motoldefaultprofile)
ProfileType.DPV_DEFAULT -> rh.gs(R.string.dpvdefaultprofile)
ProfileType.CURRENT -> rh.gs(R.string.currentprofile)
ProfileType.AVAILABLE_PROFILE -> rh.gs(R.string.availableprofile)
ProfileType.PROFILE_SWITCH -> rh.gs(R.string.careportal_profileswitch)
}
},
false
)
binding.defaultProfile.visibility = (newContent == ProfileType.MOTOL_DEFAULT || newContent == ProfileType.DPV_DEFAULT).toVisibility()
binding.currentProfile.visibility = (newContent == ProfileType.CURRENT).toVisibility()
binding.availableProfile.visibility = (newContent == ProfileType.AVAILABLE_PROFILE).toVisibility()
binding.profileSwitch.visibility = (newContent == ProfileType.PROFILE_SWITCH).toVisibility()
// restore selected values
// Restore selected values
binding.age.value = ageUsed[tabSelected]
binding.weight.value = weightUsed[tabSelected]
binding.tdd.value = tddUsed[tabSelected]
binding.basalpctfromtdd.value = pctUsed[tabSelected]
binding.basalPctFromTdd.value = pctUsed[tabSelected]
binding.basalpctfromtddRow.visibility = (newContent == ProfileType.DPV_DEFAULT).toVisibility()
if (profileList.isNotEmpty())
binding.availableProfileList.setText(profileList[profileUsed[tabSelected]].toString())
if (profileSwitch.isNotEmpty())
binding.profileswitchList.setText(profileSwitch[profileSwitchUsed[tabSelected]].originalCustomizedName)
binding.basalPctFromTddRow.visibility = (newContent == ProfileType.DPV_DEFAULT).toVisibility()
if (profileList.isNotEmpty()) {
binding.availableProfileList.setText(profileList[profileUsed[tabSelected]].toString(), false)
}
if (profileSwitch.isNotEmpty()) {
binding.profileswitchList.setText(profileSwitch[profileSwitchUsed[tabSelected]].originalCustomizedName, false)
}
}
private fun setBackgroundColorOnSelected(tab: Int) {
binding.menu1.setBackgroundColor(rh.gc(if (tab == 1) R.color.defaultbackground else R.color.tempbasal))
binding.menu2.setBackgroundColor(rh.gc(if (tab == 0) R.color.defaultbackground else R.color.examinedProfile))
}
}

View file

@ -2,6 +2,7 @@ package info.nightscout.androidaps.activities
import android.annotation.SuppressLint
import android.os.Bundle
import android.widget.TextView
import info.nightscout.androidaps.R
import info.nightscout.androidaps.database.entities.UserEntry.Action
import info.nightscout.androidaps.database.entities.UserEntry.Sources
@ -9,7 +10,7 @@ import info.nightscout.androidaps.databinding.ActivityStatsBinding
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.utils.ActivityMonitor
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.stats.DexcomTirCalculator
import info.nightscout.androidaps.utils.stats.TddCalculator
import info.nightscout.androidaps.utils.stats.TirCalculator
import javax.inject.Inject
@ -18,6 +19,7 @@ class StatsActivity : NoSplashAppCompatActivity() {
@Inject lateinit var tddCalculator: TddCalculator
@Inject lateinit var tirCalculator: TirCalculator
@Inject lateinit var dexcomTirCalculator: DexcomTirCalculator
@Inject lateinit var activityMonitor: ActivityMonitor
@Inject lateinit var uel: UserEntryLogger
@ -29,21 +31,37 @@ class StatsActivity : NoSplashAppCompatActivity() {
binding = ActivityStatsBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.tdds.text = getString(R.string.tdd) + ": " + rh.gs(R.string.calculation_in_progress)
binding.tir.text = getString(R.string.tir) + ": " + rh.gs(R.string.calculation_in_progress)
binding.activity.text = rh.gs(R.string.activitymonitor) + ": " + rh.gs(R.string.calculation_in_progress)
binding.tdds.addView(TextView(this).apply { text = getString(R.string.tdd) + ": " + rh.gs(R.string.calculation_in_progress) })
binding.tir.addView(TextView(this).apply { text = getString(R.string.tir) + ": " + rh.gs(R.string.calculation_in_progress) })
binding.activity.addView(TextView(this).apply { text = getString(R.string.activitymonitor) + ": " + rh.gs(R.string.calculation_in_progress) })
Thread {
val tdds = tddCalculator.stats()
runOnUiThread { binding.tdds.text = tdds }
val tdds = tddCalculator.stats(this)
runOnUiThread {
binding.tdds.removeAllViews()
binding.tdds.addView(tdds)
}
}.start()
Thread {
val tir = tirCalculator.stats()
runOnUiThread { binding.tir.text = tir }
val tir = tirCalculator.stats(this)
runOnUiThread {
binding.tir.removeAllViews()
binding.tir.addView(tir)
}
}.start()
Thread {
val activity = activityMonitor.stats()
runOnUiThread { binding.activity.text = activity }
val dexcomTir = dexcomTirCalculator.stats(this)
runOnUiThread {
binding.dexcomTir.removeAllViews()
binding.dexcomTir.addView(dexcomTir)
}
}.start()
Thread {
val activity = activityMonitor.stats(this)
runOnUiThread {
binding.activity.removeAllViews()
binding.activity.addView(activity)
}
}.start()
binding.ok.setOnClickListener { finish() }

View file

@ -37,15 +37,15 @@ class SurveyActivity : NoSplashAppCompatActivity() {
binding = ActivitySurveyBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.id.text = InstanceId.instanceId()
binding.id.text = InstanceId.instanceId
val profileStore = activePlugin.activeProfileSource.profile
val profileList = profileStore?.getProfileList() ?: return
binding.spinner.adapter = ArrayAdapter(this, R.layout.spinner_centered, profileList)
binding.tdds.text = tddCalculator.stats()
binding.tir.text = tirCalculator.stats()
binding.activity.text = activityMonitor.stats()
//binding.tdds.text = tddCalculator.stats()
//binding.tir.text = tirCalculator.stats()
//binding.activity.text = activityMonitor.stats()
binding.profile.setOnClickListener {
val age = SafeParse.stringToDouble(binding.age.text.toString())
@ -80,7 +80,7 @@ class SurveyActivity : NoSplashAppCompatActivity() {
binding.submit.setOnClickListener {
val r = FirebaseRecord()
r.id = InstanceId.instanceId()
r.id = InstanceId.instanceId
r.age = SafeParse.stringToInt(binding.age.text.toString())
r.weight = SafeParse.stringToInt(binding.weight.text.toString())
if (r.age < 1 || r.age > 120) {
@ -104,7 +104,7 @@ class SurveyActivity : NoSplashAppCompatActivity() {
.addOnCompleteListener(this) { task ->
if (task.isSuccessful) {
aapsLogger.debug(LTag.CORE, "signInAnonymously:success")
//val user = auth.currentUser // TODO: do we need this, seems unused?
//val user = auth.currentUser // do we need this, seems unused?
val database = FirebaseDatabase.getInstance().reference
database.child("survey").child(r.id).setValue(r)

View file

@ -1,15 +1,17 @@
package info.nightscout.androidaps.activities
import android.os.Bundle
import android.view.View
import android.view.MenuItem
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayout.OnTabSelectedListener
import info.nightscout.androidaps.R
import info.nightscout.androidaps.activities.fragments.*
import info.nightscout.androidaps.databinding.TreatmentsFragmentBinding
import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.utils.buildHelper.BuildHelper
import info.nightscout.androidaps.interfaces.BuildHelper
import javax.inject.Inject
class TreatmentsActivity : NoSplashAppCompatActivity() {
@ -23,40 +25,45 @@ class TreatmentsActivity : NoSplashAppCompatActivity() {
super.onCreate(savedInstanceState)
binding = TreatmentsFragmentBinding.inflate(layoutInflater)
setContentView(binding.root)
// Use index, TabItems crashes with an id
val useFakeTempBasal = activePlugin.activePump.isFakingTempsByExtendedBoluses
binding.treatmentsTabs.getTabAt(1)?.view?.visibility = useFakeTempBasal.toVisibility()
//binding.tempBasals.visibility = buildHelper.isEngineeringMode().toVisibility()
//binding.extendedBoluses.visibility = (buildHelper.isEngineeringMode() && !activePlugin.activePump.isFakingTempsByExtendedBoluses).toVisibility()
binding.treatments.setOnClickListener {
setFragment(TreatmentsBolusCarbsFragment())
setBackgroundColorOnSelected(it)
}
binding.extendedBoluses.setOnClickListener {
setFragment(TreatmentsExtendedBolusesFragment())
setBackgroundColorOnSelected(it)
}
binding.tempBasals.setOnClickListener {
setFragment(TreatmentsTemporaryBasalsFragment())
setBackgroundColorOnSelected(it)
}
binding.tempTargets.setOnClickListener {
setFragment(TreatmentsTempTargetFragment())
setBackgroundColorOnSelected(it)
}
binding.profileSwitches.setOnClickListener {
setFragment(TreatmentsProfileSwitchFragment())
setBackgroundColorOnSelected(it)
}
binding.careportal.setOnClickListener {
setFragment(TreatmentsCareportalFragment())
setBackgroundColorOnSelected(it)
}
binding.userentry.setOnClickListener {
setFragment(TreatmentsUserEntryFragment())
setBackgroundColorOnSelected(it)
}
setFragment(TreatmentsBolusCarbsFragment())
setBackgroundColorOnSelected(binding.treatments)
setSupportActionBar(binding.toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.title = rh.gs(R.string.carbs_and_bolus)
binding.treatmentsTabs.addOnTabSelectedListener(object : OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
val fragment = when (tab.position) {
0 -> TreatmentsBolusCarbsFragment::class.java
1 -> TreatmentsExtendedBolusesFragment::class.java
2 -> TreatmentsTemporaryBasalsFragment::class.java
3 -> TreatmentsTempTargetFragment::class.java
4 -> TreatmentsProfileSwitchFragment::class.java
5 -> TreatmentsCareportalFragment::class.java
else -> TreatmentsUserEntryFragment::class.java
}
setFragment(fragment.newInstance())
supportActionBar?.title = tab.contentDescription
}
override fun onTabUnselected(tab: TabLayout.Tab) {}
override fun onTabReselected(tab: TabLayout.Tab) {}
})
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
android.R.id.home -> {
finish()
true
}
else -> false
}
}
private fun setFragment(selectedFragment: Fragment) {
@ -66,15 +73,4 @@ class TreatmentsActivity : NoSplashAppCompatActivity() {
.commit()
}
private fun setBackgroundColorOnSelected(selected: View) {
binding.treatments.setBackgroundColor(rh.gc(R.color.defaultbackground))
binding.extendedBoluses.setBackgroundColor(rh.gc(R.color.defaultbackground))
binding.tempBasals.setBackgroundColor(rh.gc(R.color.defaultbackground))
binding.tempTargets.setBackgroundColor(rh.gc(R.color.defaultbackground))
binding.profileSwitches.setBackgroundColor(rh.gc(R.color.defaultbackground))
binding.careportal.setBackgroundColor(rh.gc(R.color.defaultbackground))
binding.userentry.setBackgroundColor(rh.gc(R.color.defaultbackground))
selected.setBackgroundColor(rh.gc(R.color.tabBgColorSelected))
}
}

View file

@ -3,9 +3,9 @@ package info.nightscout.androidaps.activities.fragments
import android.annotation.SuppressLint
import android.graphics.Paint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.util.SparseArray
import android.view.*
import androidx.core.util.forEach
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import dagger.android.support.DaggerFragment
@ -24,31 +24,31 @@ import info.nightscout.androidaps.database.transactions.InvalidateCarbsTransacti
import info.nightscout.androidaps.databinding.TreatmentsBolusCarbsFragmentBinding
import info.nightscout.androidaps.databinding.TreatmentsBolusCarbsItemBinding
import info.nightscout.androidaps.dialogs.WizardInfoDialog
import info.nightscout.androidaps.events.EventAutosensCalculationFinished
import info.nightscout.androidaps.events.EventTreatmentChange
import info.nightscout.androidaps.events.EventTreatmentUpdateGui
import info.nightscout.androidaps.extensions.iobCalc
import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventNewHistoryData
import info.nightscout.androidaps.utils.ActionModeHelper
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.ToastUtils
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.buildHelper.BuildHelper
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.interfaces.BuildHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import io.reactivex.Completable
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import io.reactivex.rxkotlin.subscribeBy
import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import io.reactivex.rxjava3.kotlin.subscribeBy
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@ -67,6 +67,12 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() {
@Inject lateinit var repository: AppRepository
@Inject lateinit var activePlugin: ActivePlugin
private var _binding: TreatmentsBolusCarbsFragmentBinding? = null
// This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!!
private var menu: Menu? = null
class MealLink(
val bolus: Bolus? = null,
val carbs: Carbs? = null,
@ -74,107 +80,24 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() {
)
private val disposable = CompositeDisposable()
private lateinit var actionHelper: ActionModeHelper<MealLink>
private val millsToThePast = T.days(30).msecs()
private var _binding: TreatmentsBolusCarbsFragmentBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
private var showInvalidated = false
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
TreatmentsBolusCarbsFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root
@SuppressLint("CheckResult")
@SuppressLint("NotifyDataSetChanged")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
actionHelper = ActionModeHelper(rh, activity, this)
actionHelper.setUpdateListHandler { binding.recyclerview.adapter?.notifyDataSetChanged() }
actionHelper.setOnRemoveHandler { removeSelected(it) }
setHasOptionsMenu(true)
binding.recyclerview.setHasFixedSize(true)
binding.recyclerview.layoutManager = LinearLayoutManager(view.context)
binding.refreshFromNightscout.setOnClickListener {
activity?.let { activity ->
OKDialog.showConfirmation(activity, rh.gs(R.string.refresheventsfromnightscout) + "?") {
uel.log(Action.TREATMENTS_NS_REFRESH, Sources.Treatments)
disposable +=
Completable.fromAction {
repository.deleteAllBolusCalculatorResults()
repository.deleteAllBoluses()
repository.deleteAllCarbs()
}
.subscribeOn(aapsSchedulers.io)
.observeOn(aapsSchedulers.main)
.subscribeBy(
onError = { aapsLogger.error("Error removing entries", it) },
onComplete = {
rxBus.send(EventTreatmentChange())
rxBus.send(EventNewHistoryData(0, false))
}
)
rxBus.send(EventNSClientRestart())
}
}
}
binding.deleteFutureTreatments.setOnClickListener {
activity?.let { activity ->
OKDialog.showConfirmation(activity, rh.gs(R.string.overview_treatment_label), rh.gs(R.string.deletefuturetreatments) + "?", Runnable {
uel.log(Action.DELETE_FUTURE_TREATMENTS, Sources.Treatments)
repository
.getBolusesDataFromTime(dateUtil.now(), false)
.observeOn(aapsSchedulers.main)
.subscribe { list ->
list.forEach { bolus ->
disposable += repository.runTransactionForResult(InvalidateBolusTransaction(bolus.id))
.subscribe(
{ result -> result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated bolus $it") } },
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating bolus", it) }
)
}
}
repository
.getCarbsDataFromTimeNotExpanded(dateUtil.now(), false)
.observeOn(aapsSchedulers.main)
.subscribe { list ->
list.forEach { carb ->
if (carb.duration == 0L)
disposable += repository.runTransactionForResult(InvalidateCarbsTransaction(carb.id))
.subscribe(
{ result -> result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated carbs $it") } },
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating carbs", it) }
)
else {
disposable += repository.runTransactionForResult(CutCarbsTransaction(carb.id, dateUtil.now()))
.subscribe(
{ result ->
result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated carbs $it") }
result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated (cut end) carbs $it") }
},
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating carbs", it) }
)
}
}
}
repository
.getBolusCalculatorResultsDataFromTime(dateUtil.now(), false)
.observeOn(aapsSchedulers.main)
.subscribe { list ->
list.forEach { bolusCalc ->
disposable += repository.runTransactionForResult(InvalidateBolusCalculatorResultTransaction(bolusCalc.id))
.subscribe(
{ result -> result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated bolusCalculatorResult $it") } },
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating bolusCalculatorResult", it) }
)
}
}
binding.deleteFutureTreatments.visibility = View.GONE
})
}
}
val nsUploadOnly = !sp.getBoolean(R.string.key_ns_receive_insulin, false) || !sp.getBoolean(R.string.key_ns_receive_carbs, false) || !buildHelper.isEngineeringMode()
if (nsUploadOnly) binding.refreshFromNightscout.visibility = View.GONE
binding.showInvalidated.setOnCheckedChangeListener { _, _ ->
rxBus.send(EventTreatmentUpdateGui())
}
binding.recyclerview.emptyView = binding.noRecordsText
binding.recyclerview.loadingView = binding.progressBar
}
private fun bolusMealLinksWithInvalid(now: Long) = repository
@ -203,37 +126,36 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() {
fun swapAdapter() {
val now = System.currentTimeMillis()
if (binding.showInvalidated.isChecked)
disposable += carbsMealLinksWithInvalid(now)
.zipWith(bolusMealLinksWithInvalid(now)) { first, second -> first + second }
.zipWith(calcResultMealLinksWithInvalid(now)) { first, second -> first + second }
.map { ml ->
ml.sortedByDescending {
it.carbs?.timestamp ?: it.bolus?.timestamp
?: it.bolusCalculatorResult?.timestamp
binding.recyclerview.isLoading = true
disposable +=
if (showInvalidated)
carbsMealLinksWithInvalid(now)
.zipWith(bolusMealLinksWithInvalid(now)) { first, second -> first + second }
.zipWith(calcResultMealLinksWithInvalid(now)) { first, second -> first + second }
.map { ml ->
ml.sortedByDescending {
it.carbs?.timestamp ?: it.bolus?.timestamp
?: it.bolusCalculatorResult?.timestamp
}
}
}
.observeOn(aapsSchedulers.main)
.subscribe { list ->
binding.recyclerview.swapAdapter(RecyclerViewAdapter(list), true)
binding.deleteFutureTreatments.visibility = list.isNotEmpty().toVisibility()
}
else
disposable += carbsMealLinks(now)
.zipWith(bolusMealLinks(now)) { first, second -> first + second }
.zipWith(calcResultMealLinks(now)) { first, second -> first + second }
.map { ml ->
ml.sortedByDescending {
it.carbs?.timestamp ?: it.bolus?.timestamp
?: it.bolusCalculatorResult?.timestamp
.observeOn(aapsSchedulers.main)
.subscribe { list ->
binding.recyclerview.swapAdapter(RecyclerViewAdapter(list), true)
}
else
carbsMealLinks(now)
.zipWith(bolusMealLinks(now)) { first, second -> first + second }
.zipWith(calcResultMealLinks(now)) { first, second -> first + second }
.map { ml ->
ml.sortedByDescending {
it.carbs?.timestamp ?: it.bolus?.timestamp
?: it.bolusCalculatorResult?.timestamp
}
}
.observeOn(aapsSchedulers.main)
.subscribe { list ->
binding.recyclerview.swapAdapter(RecyclerViewAdapter(list), true)
}
}
.observeOn(aapsSchedulers.main)
.subscribe { list ->
binding.recyclerview.swapAdapter(RecyclerViewAdapter(list), true)
binding.deleteFutureTreatments.visibility = list.isNotEmpty().toVisibility()
}
}
@ -246,21 +168,12 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() {
.observeOn(aapsSchedulers.main)
.debounce(1L, TimeUnit.SECONDS)
.subscribe({ swapAdapter() }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventTreatmentUpdateGui::class.java) // TODO join with above
.observeOn(aapsSchedulers.io)
.debounce(1L, TimeUnit.SECONDS)
.subscribe({ swapAdapter() }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventAutosensCalculationFinished::class.java)
.observeOn(aapsSchedulers.main)
.debounce(1L, TimeUnit.SECONDS)
.subscribe({ swapAdapter() }, fabricPrivacy::logException)
}
@Synchronized
override fun onPause() {
super.onPause()
actionHelper.finish()
disposable.clear()
}
@ -271,9 +184,9 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() {
_binding = null
}
private fun timestamp(ml: MealLink): Long = ml.bolusCalculatorResult?.let { it.timestamp } ?: ml.bolus?.let { it.timestamp } ?: ml.carbs?.let { it.timestamp } ?: 0L
private fun timestamp(ml: MealLink): Long = ml.bolusCalculatorResult?.timestamp ?: ml.bolus?.timestamp ?: ml.carbs?.timestamp ?: 0L
inner class RecyclerViewAdapter internal constructor(var mealLinks: List<MealLink>) : RecyclerView.Adapter<RecyclerViewAdapter.MealLinkLoadedViewHolder>() {
inner class RecyclerViewAdapter internal constructor(private var mealLinks: List<MealLink>) : RecyclerView.Adapter<RecyclerViewAdapter.MealLinkLoadedViewHolder>() {
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): MealLinkLoadedViewHolder =
MealLinkLoadedViewHolder(LayoutInflater.from(viewGroup.context).inflate(R.layout.treatments_bolus_carbs_item, viewGroup, false))
@ -282,18 +195,18 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() {
val profile = profileFunction.getProfile() ?: return
val ml = mealLinks[position]
val sameDayPrevious = position > 0 && dateUtil.isSameDay(timestamp(ml), timestamp(mealLinks[position - 1]))
holder.binding.date.visibility = sameDayPrevious.not().toVisibility()
holder.binding.date.text = dateUtil.dateString(timestamp(ml))
val newDay = position == 0 || !dateUtil.isSameDayGroup(timestamp(ml), timestamp(mealLinks[position - 1]))
holder.binding.date.visibility = newDay.toVisibility()
holder.binding.date.text = if (newDay) dateUtil.dateStringRelative(timestamp(ml), rh) else ""
// Metadata
holder.binding.metadataLayout.visibility = (ml.bolusCalculatorResult != null && (ml.bolusCalculatorResult.isValid || binding.showInvalidated.isChecked)).toVisibility()
holder.binding.metadataLayout.visibility = (ml.bolusCalculatorResult != null && (ml.bolusCalculatorResult.isValid || showInvalidated)).toVisibility()
ml.bolusCalculatorResult?.let { bolusCalculatorResult ->
holder.binding.calcTime.text = dateUtil.timeString(bolusCalculatorResult.timestamp)
}
// Bolus
holder.binding.bolusLayout.visibility = (ml.bolus != null && (ml.bolus.isValid || binding.showInvalidated.isChecked)).toVisibility()
holder.binding.bolusLayout.visibility = (ml.bolus != null && (ml.bolus.isValid || showInvalidated)).toVisibility()
ml.bolus?.let { bolus ->
holder.binding.bolusTime.text = dateUtil.timeString(bolus.timestamp)
holder.binding.insulin.text = rh.gs(R.string.formatinsulinunits, bolus.amount)
@ -302,7 +215,7 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() {
holder.binding.bolusInvalid.visibility = bolus.isValid.not().toVisibility()
val iob = bolus.iobCalc(activePlugin, System.currentTimeMillis(), profile.dia)
if (iob.iobContrib > 0.01) {
holder.binding.iob.setTextColor(rh.gc(R.color.colorActive))
holder.binding.iob.setTextColor(rh.gac(context , R.attr.activeColor))
holder.binding.iob.text = rh.gs(R.string.formatinsulinunits, iob.iobContrib)
holder.binding.iobLabel.visibility = View.VISIBLE
holder.binding.iob.visibility = View.VISIBLE
@ -312,16 +225,28 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() {
holder.binding.iobLabel.visibility = View.GONE
holder.binding.iob.visibility = View.GONE
}
if (bolus.timestamp > dateUtil.now()) holder.binding.date.setTextColor(rh.gc(R.color.colorScheduled)) else holder.binding.date.setTextColor(holder.binding.carbs.currentTextColor)
if (bolus.timestamp > dateUtil.now()) holder.binding.date.setTextColor(rh.gac(context, R.attr.scheduledColor)) else holder.binding.date.setTextColor(holder.binding.carbs
.currentTextColor)
holder.binding.mealOrCorrection.text =
when (ml.bolus.type) {
Bolus.Type.SMB -> "SMB"
Bolus.Type.NORMAL -> rh.gs(R.string.mealbolus)
Bolus.Type.PRIMING -> rh.gs(R.string.prime)
}
holder.binding.cbBolusRemove.visibility = (ml.bolus.isValid && actionHelper.isRemoving).toVisibility()
if (actionHelper.isRemoving) {
holder.binding.cbBolusRemove.setOnCheckedChangeListener { _, value ->
actionHelper.updateSelection(position, ml, value)
}
holder.binding.root.setOnClickListener {
holder.binding.cbBolusRemove.toggle()
actionHelper.updateSelection(position, ml, holder.binding.cbBolusRemove.isChecked)
}
holder.binding.cbBolusRemove.isChecked = actionHelper.isSelected(position)
}
}
// Carbs
holder.binding.carbsLayout.visibility = (ml.carbs != null && (ml.carbs.isValid || binding.showInvalidated.isChecked)).toVisibility()
holder.binding.carbsLayout.visibility = (ml.carbs != null && (ml.carbs.isValid || showInvalidated)).toVisibility()
ml.carbs?.let { carbs ->
holder.binding.carbsTime.text = dateUtil.timeString(carbs.timestamp)
holder.binding.carbs.text = rh.gs(R.string.format_carbs, carbs.amount.toInt())
@ -329,20 +254,23 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() {
holder.binding.carbsNs.visibility = (carbs.interfaceIDs.nightscoutId != null).toVisibility()
holder.binding.carbsPump.visibility = (carbs.interfaceIDs.pumpId != null).toVisibility()
holder.binding.carbsInvalid.visibility = carbs.isValid.not().toVisibility()
holder.binding.cbCarbsRemove.visibility = (ml.carbs.isValid && actionHelper.isRemoving).toVisibility()
if (actionHelper.isRemoving) {
holder.binding.cbCarbsRemove.setOnCheckedChangeListener { _, value ->
actionHelper.updateSelection(position, ml, value)
}
holder.binding.root.setOnClickListener {
holder.binding.cbBolusRemove.toggle()
actionHelper.updateSelection(position, ml, holder.binding.cbBolusRemove.isChecked)
}
holder.binding.cbCarbsRemove.isChecked = actionHelper.isSelected(position)
}
}
holder.binding.bolusRemove.visibility = (ml.bolus?.isValid == true).toVisibility()
holder.binding.carbsRemove.visibility = (ml.carbs?.isValid == true).toVisibility()
holder.binding.bolusRemove.tag = ml
holder.binding.carbsRemove.tag = ml
holder.binding.calculation.tag = ml
val nextTimestamp = if (mealLinks.size != position + 1) timestamp(mealLinks[position + 1]) else 0L
holder.binding.delimiter.visibility = dateUtil.isSameDay(timestamp(ml), nextTimestamp).toVisibility()
}
override fun getItemCount(): Int {
return mealLinks.size
}
override fun getItemCount() = mealLinks.size
inner class MealLinkLoadedViewHolder internal constructor(view: View) : RecyclerView.ViewHolder(view) {
@ -359,50 +287,190 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() {
}
}
binding.calculation.paintFlags = binding.calculation.paintFlags or Paint.UNDERLINE_TEXT_FLAG
binding.bolusRemove.setOnClickListener { ml ->
val bolus = (ml.tag as MealLink?)?.bolus ?: return@setOnClickListener
activity?.let { activity ->
val text = rh.gs(R.string.configbuilder_insulin) + ": " +
rh.gs(R.string.formatinsulinunits, bolus.amount) + "\n" +
rh.gs(R.string.date) + ": " + dateUtil.dateAndTimeString(bolus.timestamp)
OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), text, Runnable {
uel.log(
Action.BOLUS_REMOVED, Sources.Treatments,
ValueWithUnit.Timestamp(bolus.timestamp),
ValueWithUnit.Insulin(bolus.amount)
//ValueWithUnit.Gram(mealLinkLoaded.carbs.toInt())
)
}
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
this.menu = menu
inflater.inflate(R.menu.menu_treatments_carbs_bolus, menu)
super.onCreateOptionsMenu(menu, inflater)
}
private fun updateMenuVisibility() {
menu?.findItem(R.id.nav_hide_invalidated)?.isVisible = showInvalidated
menu?.findItem(R.id.nav_show_invalidated)?.isVisible = !showInvalidated
}
override fun onPrepareOptionsMenu(menu: Menu) {
updateMenuVisibility()
val nsUploadOnly = !sp.getBoolean(R.string.key_ns_receive_insulin, false) || !sp.getBoolean(R.string.key_ns_receive_carbs, false) || !buildHelper.isEngineeringMode()
menu.findItem(R.id.nav_refresh_ns)?.isVisible = !nsUploadOnly
val hasItems = (binding.recyclerview.adapter?.itemCount ?: 0) > 0
menu.findItem(R.id.nav_delete_future)?.isVisible = hasItems
return super.onPrepareOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean =
when (item.itemId) {
R.id.nav_remove_items -> actionHelper.startRemove()
R.id.nav_show_invalidated -> {
showInvalidated = true
updateMenuVisibility()
ToastUtils.showToastInUiThread(context, rh.gs(R.string.show_invalidated_records))
swapAdapter()
true
}
R.id.nav_hide_invalidated -> {
showInvalidated = false
updateMenuVisibility()
ToastUtils.showToastInUiThread(context, rh.gs(R.string.hide_invalidated_records))
swapAdapter()
true
}
R.id.nav_delete_future -> {
deleteFutureTreatments()
true
}
R.id.nav_refresh_ns -> {
refreshFromNightscout()
true
}
else -> false
}
private fun refreshFromNightscout() {
activity?.let { activity ->
OKDialog.showConfirmation(activity, rh.gs(R.string.refresheventsfromnightscout) + "?") {
uel.log(Action.TREATMENTS_NS_REFRESH, Sources.Treatments)
disposable +=
Completable.fromAction {
repository.deleteAllBolusCalculatorResults()
repository.deleteAllBoluses()
repository.deleteAllCarbs()
}
.subscribeOn(aapsSchedulers.io)
.observeOn(aapsSchedulers.main)
.subscribeBy(
onError = { aapsLogger.error("Error removing entries", it) },
onComplete = {
rxBus.send(EventTreatmentChange())
rxBus.send(EventNewHistoryData(0, false))
}
)
rxBus.send(EventNSClientRestart())
}
}
}
fun deleteFutureTreatments() {
activity?.let { activity ->
OKDialog.showConfirmation(activity, rh.gs(R.string.overview_treatment_label), rh.gs(R.string.deletefuturetreatments) + "?", Runnable {
uel.log(Action.DELETE_FUTURE_TREATMENTS, Sources.Treatments)
disposable += repository
.getBolusesDataFromTime(dateUtil.now(), false)
.observeOn(aapsSchedulers.main)
.subscribe { list ->
list.forEach { bolus ->
disposable += repository.runTransactionForResult(InvalidateBolusTransaction(bolus.id))
.subscribe(
{ result -> result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated bolus $it") } },
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating bolus", it) }
)
})
}
}
}
binding.bolusRemove.paintFlags = binding.bolusRemove.paintFlags or Paint.UNDERLINE_TEXT_FLAG
binding.carbsRemove.setOnClickListener { ml ->
val carb = (ml.tag as MealLink?)?.carbs ?: return@setOnClickListener
activity?.let { activity ->
val text = rh.gs(R.string.carbs) + ": " +
rh.gs(R.string.carbs) + ": " + rh.gs(R.string.format_carbs, carb.amount.toInt()) + "\n" +
rh.gs(R.string.date) + ": " + dateUtil.dateAndTimeString(carb.timestamp)
OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), text, Runnable {
uel.log(
Action.CARBS_REMOVED, Sources.Treatments,
ValueWithUnit.Timestamp(carb.timestamp),
ValueWithUnit.Gram(carb.amount.toInt())
)
disposable += repository.runTransactionForResult(InvalidateCarbsTransaction(carb.id))
disposable += repository
.getCarbsDataFromTimeNotExpanded(dateUtil.now(), false)
.observeOn(aapsSchedulers.main)
.subscribe { list ->
list.forEach { carb ->
if (carb.duration == 0L)
disposable += repository.runTransactionForResult(InvalidateCarbsTransaction(carb.id))
.subscribe(
{ result -> result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated carbs $it") } },
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating carbs", it) }
)
else {
disposable += repository.runTransactionForResult(CutCarbsTransaction(carb.id, dateUtil.now()))
.subscribe(
{ result ->
result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated carbs $it") }
result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated (cut end) carbs $it") }
},
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating carbs", it) }
)
}
}
}
disposable += repository
.getBolusCalculatorResultsDataFromTime(dateUtil.now(), false)
.observeOn(aapsSchedulers.main)
.subscribe { list ->
list.forEach { bolusCalc ->
disposable += repository.runTransactionForResult(InvalidateBolusCalculatorResultTransaction(bolusCalc.id))
.subscribe(
{ result -> result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated carbs $it") } },
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating carbs", it) }
{ result -> result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated bolusCalculatorResult $it") } },
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating bolusCalculatorResult", it) }
)
})
}
}
}
binding.carbsRemove.paintFlags = binding.carbsRemove.paintFlags or Paint.UNDERLINE_TEXT_FLAG
}
})
}
}
}
private fun getConfirmationText(selectedItems: SparseArray<MealLink>): String {
if (selectedItems.size() == 1) {
val mealLink = selectedItems.valueAt(0)
val bolus = mealLink.bolus
if (bolus != null)
return rh.gs(R.string.configbuilder_insulin) + ": " + rh.gs(R.string.formatinsulinunits, bolus.amount) + "\n" +
rh.gs(R.string.date) + ": " + dateUtil.dateAndTimeString(bolus.timestamp)
val carbs = mealLink.carbs
if (carbs != null)
return rh.gs(R.string.carbs) + ": " + rh.gs(R.string.format_carbs, carbs.amount.toInt()) + "\n" +
rh.gs(R.string.date) + ": " + dateUtil.dateAndTimeString(carbs.timestamp)
}
return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size())
}
private fun removeSelected(selectedItems: SparseArray<MealLink>) {
activity?.let { activity ->
OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(selectedItems), Runnable {
selectedItems.forEach { _, ml ->
ml.bolus?.let { bolus ->
uel.log(
Action.BOLUS_REMOVED, Sources.Treatments,
ValueWithUnit.Timestamp(bolus.timestamp),
ValueWithUnit.Insulin(bolus.amount)
)
disposable += repository.runTransactionForResult(InvalidateBolusTransaction(bolus.id))
.subscribe(
{ result -> result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated bolus $it") } },
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating bolus", it) }
)
}
ml.carbs?.let { carb ->
uel.log(
Action.CARBS_REMOVED, Sources.Treatments,
ValueWithUnit.Timestamp(carb.timestamp),
ValueWithUnit.Gram(carb.amount.toInt())
)
disposable += repository.runTransactionForResult(InvalidateCarbsTransaction(carb.id))
.subscribe(
{ result -> result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated carbs $it") } },
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating carbs", it) }
)
}
}
actionHelper.finish()
})
}
}
}

View file

@ -1,45 +1,40 @@
package info.nightscout.androidaps.activities.fragments
import android.graphics.Paint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.util.SparseArray
import android.view.*
import androidx.core.util.forEach
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.R
import info.nightscout.androidaps.activities.fragments.TreatmentsCareportalFragment.RecyclerViewAdapter.TherapyEventsViewHolder
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.entities.ValueWithUnit
import info.nightscout.androidaps.database.entities.TherapyEvent
import info.nightscout.androidaps.database.entities.UserEntry.Action
import info.nightscout.androidaps.database.entities.UserEntry.Sources
import info.nightscout.androidaps.database.entities.ValueWithUnit
import info.nightscout.androidaps.database.transactions.InvalidateAAPSStartedTherapyEventTransaction
import info.nightscout.androidaps.database.transactions.InvalidateTherapyEventTransaction
import info.nightscout.androidaps.databinding.TreatmentsCareportalFragmentBinding
import info.nightscout.androidaps.databinding.TreatmentsCareportalItemBinding
import info.nightscout.androidaps.events.EventTherapyEventChange
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart
import info.nightscout.androidaps.events.EventTreatmentUpdateGui
import info.nightscout.androidaps.activities.fragments.TreatmentsCareportalFragment.RecyclerViewAdapter.TherapyEventsViewHolder
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.Translator
import info.nightscout.androidaps.utils.*
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.buildHelper.BuildHelper
import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.interfaces.BuildHelper
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import io.reactivex.Completable
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import io.reactivex.rxkotlin.subscribeBy
import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import io.reactivex.rxjava3.kotlin.subscribeBy
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@ -57,61 +52,64 @@ class TreatmentsCareportalFragment : DaggerFragment() {
@Inject lateinit var repository: AppRepository
@Inject lateinit var uel: UserEntryLogger
private val disposable = CompositeDisposable()
private val millsToThePast = T.days(30).msecs()
private var _binding: TreatmentsCareportalFragmentBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
// This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!!
private var menu: Menu? = null
private val disposable = CompositeDisposable()
private val millsToThePast = T.days(30).msecs()
private lateinit var actionHelper: ActionModeHelper<TherapyEvent>
private var showInvalidated = false
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
TreatmentsCareportalFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
actionHelper = ActionModeHelper(rh, activity, this)
actionHelper.setUpdateListHandler { binding.recyclerview.adapter?.notifyDataSetChanged() }
actionHelper.setOnRemoveHandler { removeSelected(it) }
setHasOptionsMenu(true)
binding.recyclerview.setHasFixedSize(true)
binding.recyclerview.layoutManager = LinearLayoutManager(view.context)
binding.refreshFromNightscout.setOnClickListener {
activity?.let { activity ->
OKDialog.showConfirmation(activity, rh.gs(R.string.careportal), rh.gs(R.string.refresheventsfromnightscout) + " ?", Runnable {
uel.log(Action.CAREPORTAL_NS_REFRESH, Sources.Treatments)
disposable += Completable.fromAction { repository.deleteAllTherapyEventsEntries() }
.subscribeOn(aapsSchedulers.io)
.subscribeBy(
onError = { aapsLogger.error("Error removing entries", it) },
onComplete = { rxBus.send(EventTherapyEventChange()) }
)
rxBus.send(EventNSClientRestart())
})
}
}
binding.removeAndroidapsStartedEvents.setOnClickListener {
activity?.let { activity ->
OKDialog.showConfirmation(activity, rh.gs(R.string.careportal), rh.gs(R.string.careportal_removestartedevents), Runnable {
uel.log(Action.RESTART_EVENTS_REMOVED, Sources.Treatments)
repository.runTransactionForResult(InvalidateAAPSStartedTherapyEventTransaction(rh.gs(R.string.androidaps_start)))
.subscribe(
{ result -> result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated therapy event $it") } },
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating therapy event", it) }
)
}, null)
}
}
binding.recyclerview.emptyView = binding.noRecordsText
binding.recyclerview.loadingView = binding.progressBar
}
val nsUploadOnly = !sp.getBoolean(R.string.key_ns_receive_therapy_events, false) || !buildHelper.isEngineeringMode()
if (nsUploadOnly) binding.refreshFromNightscout.visibility = View.GONE
binding.showInvalidated.setOnCheckedChangeListener { _, _ ->
rxBus.send(EventTreatmentUpdateGui())
private fun refreshFromNightscout() {
activity?.let { activity ->
OKDialog.showConfirmation(activity, rh.gs(R.string.careportal), rh.gs(R.string.refresheventsfromnightscout) + " ?", Runnable {
uel.log(Action.CAREPORTAL_NS_REFRESH, Sources.Treatments)
disposable += Completable.fromAction { repository.deleteAllTherapyEventsEntries() }
.subscribeOn(aapsSchedulers.io)
.subscribeBy(
onError = { aapsLogger.error("Error removing entries", it) },
onComplete = { rxBus.send(EventTherapyEventChange()) }
)
rxBus.send(EventNSClientRestart())
})
}
}
private fun removeStartedEvents() {
activity?.let { activity ->
OKDialog.showConfirmation(activity, rh.gs(R.string.careportal), rh.gs(R.string.careportal_removestartedevents), Runnable {
uel.log(Action.RESTART_EVENTS_REMOVED, Sources.Treatments)
disposable += repository.runTransactionForResult(InvalidateAAPSStartedTherapyEventTransaction(rh.gs(R.string.androidaps_start)))
.subscribe(
{ result -> result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated therapy event $it") } },
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating therapy event", it) }
)
})
}
}
fun swapAdapter() {
val now = System.currentTimeMillis()
binding.recyclerview.isLoading = true
disposable +=
if (binding.showInvalidated.isChecked)
if (showInvalidated)
repository
.getTherapyEventDataIncludingInvalidFromTime(now - millsToThePast, false)
.observeOn(aapsSchedulers.main)
@ -132,16 +130,12 @@ class TreatmentsCareportalFragment : DaggerFragment() {
.observeOn(aapsSchedulers.main)
.debounce(1L, TimeUnit.SECONDS)
.subscribe({ swapAdapter() }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventTreatmentUpdateGui::class.java) // TODO join with above
.observeOn(aapsSchedulers.io)
.debounce(1L, TimeUnit.SECONDS)
.subscribe({ swapAdapter() }, fabricPrivacy::logException)
}
@Synchronized
override fun onPause() {
super.onPause()
actionHelper.finish()
disposable.clear()
}
@ -163,49 +157,113 @@ class TreatmentsCareportalFragment : DaggerFragment() {
val therapyEvent = therapyList[position]
holder.binding.ns.visibility = (therapyEvent.interfaceIDs.nightscoutId != null).toVisibility()
holder.binding.invalid.visibility = therapyEvent.isValid.not().toVisibility()
val sameDayPrevious = position > 0 && dateUtil.isSameDay(therapyEvent.timestamp, therapyList[position - 1].timestamp)
holder.binding.date.visibility = sameDayPrevious.not().toVisibility()
holder.binding.date.text = dateUtil.dateString(therapyEvent.timestamp)
val newDay = position == 0 || !dateUtil.isSameDayGroup(therapyEvent.timestamp, therapyList[position - 1].timestamp)
holder.binding.date.visibility = newDay.toVisibility()
holder.binding.date.text = if (newDay) dateUtil.dateStringRelative(therapyEvent.timestamp, rh) else ""
holder.binding.time.text = dateUtil.timeString(therapyEvent.timestamp)
holder.binding.duration.text = if (therapyEvent.duration == 0L) "" else dateUtil.niceTimeScalar(therapyEvent.duration, rh)
holder.binding.note.text = therapyEvent.note
holder.binding.type.text = translator.translate(therapyEvent.type)
holder.binding.remove.tag = therapyEvent
val nextTimestamp = if (therapyList.size != position + 1) therapyList[position + 1].timestamp else 0L
holder.binding.delimiter.visibility = dateUtil.isSameDay(therapyEvent.timestamp, nextTimestamp).toVisibility()
holder.binding.cbRemove.visibility = (therapyEvent.isValid && actionHelper.isRemoving).toVisibility()
holder.binding.cbRemove.setOnCheckedChangeListener { _, value ->
actionHelper.updateSelection(position, therapyEvent, value)
}
holder.binding.root.setOnClickListener {
holder.binding.cbRemove.toggle()
actionHelper.updateSelection(position, therapyEvent, holder.binding.cbRemove.isChecked)
}
holder.binding.cbRemove.isChecked = actionHelper.isSelected(position)
}
override fun getItemCount(): Int {
return therapyList.size
}
override fun getItemCount() = therapyList.size
inner class TherapyEventsViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val binding = TreatmentsCareportalItemBinding.bind(view)
init {
binding.remove.setOnClickListener { v: View ->
val therapyEvent = v.tag as TherapyEvent
activity?.let { activity ->
val text = rh.gs(R.string.eventtype) + ": " + translator.translate(therapyEvent.type) + "\n" +
rh.gs(R.string.notes_label) + ": " + (therapyEvent.note
?: "") + "\n" +
rh.gs(R.string.date) + ": " + dateUtil.dateAndTimeString(therapyEvent.timestamp)
OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), text, Runnable {
uel.log(Action.CAREPORTAL_REMOVED, Sources.Treatments, therapyEvent.note ,
ValueWithUnit.Timestamp(therapyEvent.timestamp),
ValueWithUnit.TherapyEventType(therapyEvent.type))
disposable += repository.runTransactionForResult(InvalidateTherapyEventTransaction(therapyEvent.id))
.subscribe(
{ result -> result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated therapy event $it") } },
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating therapy event", it) }
)
}, null)
}
}
binding.remove.paintFlags = binding.remove.paintFlags or Paint.UNDERLINE_TEXT_FLAG
}
}
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
this.menu = menu
inflater.inflate(R.menu.menu_treatments_careportal, menu)
super.onCreateOptionsMenu(menu, inflater)
}
override fun onPrepareOptionsMenu(menu: Menu) {
updateMenuVisibility()
val nsUploadOnly = !sp.getBoolean(R.string.key_ns_receive_therapy_events, false) || !buildHelper.isEngineeringMode()
menu.findItem(R.id.nav_refresh_ns)?.isVisible = !nsUploadOnly
return super.onPrepareOptionsMenu(menu)
}
private fun updateMenuVisibility() {
menu?.findItem(R.id.nav_hide_invalidated)?.isVisible = showInvalidated
menu?.findItem(R.id.nav_show_invalidated)?.isVisible = !showInvalidated
}
override fun onOptionsItemSelected(item: MenuItem): Boolean =
when (item.itemId) {
R.id.nav_remove_items -> actionHelper.startRemove()
R.id.nav_show_invalidated -> {
showInvalidated = true
updateMenuVisibility()
ToastUtils.showToastInUiThread(context, rh.gs(R.string.show_invalidated_records))
swapAdapter()
true
}
R.id.nav_hide_invalidated -> {
showInvalidated = false
updateMenuVisibility()
ToastUtils.showToastInUiThread(context, rh.gs(R.string.hide_invalidated_records))
swapAdapter()
true
}
R.id.nav_remove_started_events -> {
removeStartedEvents()
true
}
R.id.nav_refresh_ns -> {
refreshFromNightscout()
true
}
else -> false
}
private fun getConfirmationText(selectedItems: SparseArray<TherapyEvent>): String {
if (selectedItems.size() == 1) {
val therapyEvent = selectedItems.valueAt(0)
return rh.gs(R.string.eventtype) + ": " + translator.translate(therapyEvent.type) + "\n" +
rh.gs(R.string.notes_label) + ": " + (therapyEvent.note ?: "") + "\n" +
rh.gs(R.string.date) + ": " + dateUtil.dateAndTimeString(therapyEvent.timestamp)
}
return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size())
}
private fun removeSelected(selectedItems: SparseArray<TherapyEvent>) {
activity?.let { activity ->
OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(selectedItems), Runnable {
selectedItems.forEach { _, therapyEvent ->
uel.log(
Action.CAREPORTAL_REMOVED, Sources.Treatments, therapyEvent.note,
ValueWithUnit.Timestamp(therapyEvent.timestamp),
ValueWithUnit.TherapyEventType(therapyEvent.type)
)
disposable += repository.runTransactionForResult(InvalidateTherapyEventTransaction(therapyEvent.id))
.subscribe(
{ result -> result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated therapy event $it") } },
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating therapy event", it) }
)
}
actionHelper.finish()
})
}
}
}

View file

@ -1,16 +1,15 @@
package info.nightscout.androidaps.activities.fragments
import android.annotation.SuppressLint
import android.content.DialogInterface
import android.graphics.Paint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.util.SparseArray
import android.view.*
import androidx.core.util.forEach
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.R
import info.nightscout.androidaps.activities.fragments.TreatmentsExtendedBolusesFragment.RecyclerViewAdapter.ExtendedBolusesViewHolder
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.entities.ExtendedBolus
import info.nightscout.androidaps.database.entities.UserEntry.Action
@ -26,19 +25,20 @@ import info.nightscout.androidaps.extensions.isInProgress
import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.activities.fragments.TreatmentsExtendedBolusesFragment.RecyclerViewAdapter.ExtendedBolusesViewHolder
import info.nightscout.androidaps.utils.ActionModeHelper
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.ToastUtils
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@ -61,22 +61,32 @@ class TreatmentsExtendedBolusesFragment : DaggerFragment() {
private var _binding: TreatmentsExtendedbolusFragmentBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
// This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!!
private var menu: Menu? = null
private lateinit var actionHelper: ActionModeHelper<ExtendedBolus>
private var showInvalidated = false
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View =
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
TreatmentsExtendedbolusFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root
@SuppressLint("NotifyDataSetChanged")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
actionHelper = ActionModeHelper(rh, activity, this)
actionHelper.setUpdateListHandler { binding.recyclerview.adapter?.notifyDataSetChanged() }
actionHelper.setOnRemoveHandler { removeSelected(it) }
setHasOptionsMenu(true)
binding.recyclerview.setHasFixedSize(true)
binding.recyclerview.layoutManager = LinearLayoutManager(view.context)
binding.recyclerview.emptyView = binding.noRecordsText
binding.recyclerview.loadingView = binding.progressBar
}
fun swapAdapter() {
val now = System.currentTimeMillis()
if (binding.showInvalidated.isChecked)
binding.recyclerview.isLoading = true
disposable += if (showInvalidated)
repository
.getExtendedBolusDataIncludingInvalidFromTime(now - millsToThePast, false)
.observeOn(aapsSchedulers.main)
@ -92,7 +102,6 @@ class TreatmentsExtendedBolusesFragment : DaggerFragment() {
override fun onResume() {
super.onResume()
swapAdapter()
disposable += rxBus
.toObservable(EventExtendedBolusChange::class.java)
.observeOn(aapsSchedulers.io)
@ -103,6 +112,7 @@ class TreatmentsExtendedBolusesFragment : DaggerFragment() {
@Synchronized
override fun onPause() {
super.onPause()
actionHelper.finish()
disposable.clear()
}
@ -125,13 +135,12 @@ class TreatmentsExtendedBolusesFragment : DaggerFragment() {
holder.binding.ns.visibility = (extendedBolus.interfaceIDs.nightscoutId != null).toVisibility()
holder.binding.ph.visibility = (extendedBolus.interfaceIDs.pumpId != null).toVisibility()
holder.binding.invalid.visibility = extendedBolus.isValid.not().toVisibility()
val sameDayPrevious = position > 0 && dateUtil.isSameDay(extendedBolus.timestamp, extendedBolusList[position-1].timestamp)
holder.binding.date.visibility = sameDayPrevious.not().toVisibility()
holder.binding.date.text = dateUtil.dateString(extendedBolus.timestamp)
@SuppressLint("SetTextI18n")
val newDay = position == 0 || !dateUtil.isSameDayGroup(extendedBolus.timestamp, extendedBolusList[position - 1].timestamp)
holder.binding.date.visibility = newDay.toVisibility()
holder.binding.date.text = if (newDay) dateUtil.dateStringRelative(extendedBolus.timestamp, rh) else ""
if (extendedBolus.isInProgress(dateUtil)) {
holder.binding.time.text = dateUtil.timeString(extendedBolus.timestamp)
holder.binding.time.setTextColor(rh.gc(R.color.colorActive))
holder.binding.time.setTextColor(rh.gac(context, R.attr.activeColor))
} else {
holder.binding.time.text = dateUtil.timeRangeString(extendedBolus.timestamp, extendedBolus.end)
holder.binding.time.setTextColor(holder.binding.insulin.currentTextColor)
@ -142,42 +151,97 @@ class TreatmentsExtendedBolusesFragment : DaggerFragment() {
val iob = extendedBolus.iobCalc(System.currentTimeMillis(), profile, activePlugin.activeInsulin)
holder.binding.iob.text = rh.gs(R.string.formatinsulinunits, iob.iob)
holder.binding.ratio.text = rh.gs(R.string.pump_basebasalrate, extendedBolus.rate)
if (iob.iob != 0.0) holder.binding.iob.setTextColor(rh.gc(R.color.colorActive)) else holder.binding.iob.setTextColor(holder.binding.insulin.currentTextColor)
holder.binding.remove.tag = extendedBolus
val nextTimestamp = if (extendedBolusList.size != position + 1) extendedBolusList[position + 1].timestamp else 0L
holder.binding.delimiter.visibility = dateUtil.isSameDay(extendedBolus.timestamp, nextTimestamp).toVisibility()
if (iob.iob != 0.0) holder.binding.iob.setTextColor(rh.gac(context, R.attr.activeColor)) else holder.binding.iob.setTextColor(holder.binding.insulin.currentTextColor)
holder.binding.cbRemove.visibility = (extendedBolus.isValid && actionHelper.isRemoving).toVisibility()
if (actionHelper.isRemoving) {
holder.binding.cbRemove.setOnCheckedChangeListener { _, value ->
actionHelper.updateSelection(position, extendedBolus, value)
}
holder.binding.root.setOnClickListener {
holder.binding.cbRemove.toggle()
actionHelper.updateSelection(position, extendedBolus, holder.binding.cbRemove.isChecked)
}
holder.binding.cbRemove.isChecked = actionHelper.isSelected(position)
}
}
override fun getItemCount(): Int = extendedBolusList.size
override fun getItemCount() = extendedBolusList.size
inner class ExtendedBolusesViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val binding = TreatmentsExtendedbolusItemBinding.bind(itemView)
init {
binding.remove.setOnClickListener { v: View ->
val extendedBolus = v.tag as ExtendedBolus
context?.let { context ->
OKDialog.showConfirmation(context, rh.gs(R.string.removerecord),
"""
${rh.gs(R.string.extended_bolus)}
${rh.gs(R.string.date)}: ${dateUtil.dateAndTimeString(extendedBolus.timestamp)}
""".trimIndent(), { _: DialogInterface, _: Int ->
uel.log(Action.EXTENDED_BOLUS_REMOVED, Sources.Treatments,
ValueWithUnit.Timestamp(extendedBolus.timestamp),
ValueWithUnit.Insulin(extendedBolus.amount),
ValueWithUnit.UnitPerHour(extendedBolus.rate),
ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(extendedBolus.duration).toInt()))
disposable += repository.runTransactionForResult(InvalidateExtendedBolusTransaction(extendedBolus.id))
.subscribe(
{ aapsLogger.debug(LTag.DATABASE, "Removed extended bolus $extendedBolus") },
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating extended bolus", it) })
}, null)
}
}
binding.remove.paintFlags = binding.remove.paintFlags or Paint.UNDERLINE_TEXT_FLAG
}
}
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
this.menu = menu
inflater.inflate(R.menu.menu_treatments_extended_bolus, menu)
super.onCreateOptionsMenu(menu, inflater)
}
private fun updateMenuVisibility() {
menu?.findItem(R.id.nav_hide_invalidated)?.isVisible = showInvalidated
menu?.findItem(R.id.nav_show_invalidated)?.isVisible = !showInvalidated
}
override fun onPrepareOptionsMenu(menu: Menu) {
updateMenuVisibility()
return super.onPrepareOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.nav_remove_items -> actionHelper.startRemove()
R.id.nav_show_invalidated -> {
showInvalidated = true
updateMenuVisibility()
ToastUtils.showToastInUiThread(context, rh.gs(R.string.show_invalidated_records))
swapAdapter()
true
}
R.id.nav_hide_invalidated -> {
showInvalidated = false
updateMenuVisibility()
ToastUtils.showToastInUiThread(context, rh.gs(R.string.hide_invalidated_records))
swapAdapter()
true
}
else -> false
}
}
private fun getConfirmationText(selectedItems: SparseArray<ExtendedBolus>): String {
if (selectedItems.size() == 1) {
val bolus = selectedItems.valueAt(0)
return rh.gs(R.string.extended_bolus) + "\n" +
"${rh.gs(R.string.date)}: ${dateUtil.dateAndTimeString(bolus.timestamp)}"
}
return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size())
}
private fun removeSelected(selectedItems: SparseArray<ExtendedBolus>) {
activity?.let { activity ->
OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(selectedItems), Runnable {
selectedItems.forEach { _, extendedBolus ->
uel.log(
Action.EXTENDED_BOLUS_REMOVED, Sources.Treatments,
ValueWithUnit.Timestamp(extendedBolus.timestamp),
ValueWithUnit.Insulin(extendedBolus.amount),
ValueWithUnit.UnitPerHour(extendedBolus.rate),
ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(extendedBolus.duration).toInt())
)
disposable += repository.runTransactionForResult(InvalidateExtendedBolusTransaction(extendedBolus.id))
.subscribe(
{ aapsLogger.debug(LTag.DATABASE, "Removed extended bolus $extendedBolus") },
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating extended bolus", it) })
}
actionHelper.finish()
})
}
}
}

View file

@ -1,14 +1,16 @@
package info.nightscout.androidaps.activities.fragments
import android.annotation.SuppressLint
import android.graphics.Paint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.util.SparseArray
import android.view.*
import androidx.core.util.forEach
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.R
import info.nightscout.androidaps.activities.fragments.TreatmentsProfileSwitchFragment.RecyclerProfileViewAdapter.ProfileSwitchViewHolder
import info.nightscout.androidaps.data.ProfileSealed
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.entities.UserEntry.Action
@ -18,32 +20,32 @@ import info.nightscout.androidaps.database.transactions.InvalidateProfileSwitchT
import info.nightscout.androidaps.databinding.TreatmentsProfileswitchFragmentBinding
import info.nightscout.androidaps.databinding.TreatmentsProfileswitchItemBinding
import info.nightscout.androidaps.dialogs.ProfileViewerDialog
import info.nightscout.androidaps.events.EventEffectiveProfileSwitchChanged
import info.nightscout.androidaps.events.EventProfileSwitchChanged
import info.nightscout.androidaps.extensions.getCustomizedName
import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventNewHistoryData
import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin
import info.nightscout.androidaps.plugins.profile.local.events.EventLocalProfileChanged
import info.nightscout.androidaps.events.EventTreatmentUpdateGui
import info.nightscout.androidaps.activities.fragments.TreatmentsProfileSwitchFragment.RecyclerProfileViewAdapter.ProfileSwitchViewHolder
import info.nightscout.androidaps.events.EventEffectiveProfileSwitchChanged
import info.nightscout.androidaps.utils.ActionModeHelper
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.ToastUtils
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.buildHelper.BuildHelper
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.interfaces.BuildHelper
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import io.reactivex.Completable
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import io.reactivex.rxkotlin.subscribeBy
import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import io.reactivex.rxjava3.kotlin.subscribeBy
import javax.inject.Inject
class TreatmentsProfileSwitchFragment : DaggerFragment() {
@ -62,49 +64,52 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() {
private var _binding: TreatmentsProfileswitchFragmentBinding? = null
private val disposable = CompositeDisposable()
private val millsToThePast = T.days(30).msecs()
// This property is only valid between onCreateView and
// onDestroyView.
// This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!!
private var menu: Menu? = null
private lateinit var actionHelper: ActionModeHelper<ProfileSealed>
private val disposable = CompositeDisposable()
private val millsToThePast = T.days(30).msecs()
private var showInvalidated = false
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
TreatmentsProfileswitchFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root
@SuppressLint("NotifyDataSetChanged")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
actionHelper = ActionModeHelper(rh, activity, this)
actionHelper.setUpdateListHandler { binding.recyclerview.adapter?.notifyDataSetChanged() }
actionHelper.setOnRemoveHandler { removeSelected(it) }
setHasOptionsMenu(true)
binding.recyclerview.setHasFixedSize(true)
binding.recyclerview.layoutManager = LinearLayoutManager(view.context)
binding.recyclerview.emptyView = binding.noRecordsText
binding.recyclerview.loadingView = binding.progressBar
}
binding.refreshFromNightscout.setOnClickListener {
activity?.let { activity ->
OKDialog.showConfirmation(activity, rh.gs(R.string.refresheventsfromnightscout) + "?") {
uel.log(Action.TREATMENTS_NS_REFRESH, Sources.Treatments)
disposable +=
Completable.fromAction {
repository.deleteAllEffectiveProfileSwitches()
repository.deleteAllProfileSwitches()
}
.subscribeOn(aapsSchedulers.io)
.observeOn(aapsSchedulers.main)
.subscribeBy(
onError = { aapsLogger.error("Error removing entries", it) },
onComplete = {
rxBus.send(EventProfileSwitchChanged())
rxBus.send(EventEffectiveProfileSwitchChanged(0L))
rxBus.send(EventNewHistoryData(0, false))
}
)
rxBus.send(EventNSClientRestart())
}
private fun refreshFromNightscout() {
activity?.let { activity ->
OKDialog.showConfirmation(activity, rh.gs(R.string.refresheventsfromnightscout) + "?") {
uel.log(Action.TREATMENTS_NS_REFRESH, Sources.Treatments)
disposable +=
Completable.fromAction {
repository.deleteAllEffectiveProfileSwitches()
repository.deleteAllProfileSwitches()
}
.subscribeOn(aapsSchedulers.io)
.observeOn(aapsSchedulers.main)
.subscribeBy(
onError = { aapsLogger.error("Error removing entries", it) },
onComplete = {
rxBus.send(EventProfileSwitchChanged())
rxBus.send(EventEffectiveProfileSwitchChanged(0L))
rxBus.send(EventNewHistoryData(0, false))
}
)
rxBus.send(EventNSClientRestart())
}
}
if (!sp.getBoolean(R.string.key_ns_receive_profile_switch, false) || !buildHelper.isEngineeringMode()) binding.refreshFromNightscout.visibility = View.GONE
binding.showInvalidated.setOnCheckedChangeListener { _, _ ->
rxBus.send(EventTreatmentUpdateGui())
}
}
private fun profileSwitchWithInvalid(now: Long) = repository
@ -125,24 +130,24 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() {
fun swapAdapter() {
val now = System.currentTimeMillis()
if (binding.showInvalidated.isChecked)
disposable += profileSwitchWithInvalid(now)
.zipWith(effectiveProfileSwitchWithInvalid(now)) { first, second -> first + second }
.map { ml -> ml.sortedByDescending { it.timestamp } }
.observeOn(aapsSchedulers.main)
.subscribe { list ->
binding.recyclerview.swapAdapter(RecyclerProfileViewAdapter(list), true)
}
else
disposable += profileSwitches(now)
.zipWith(effectiveProfileSwitches(now)) { first, second -> first + second }
.map { ml -> ml.sortedByDescending { it.timestamp } }
.observeOn(aapsSchedulers.main)
.subscribe { list ->
binding.recyclerview.swapAdapter(RecyclerProfileViewAdapter(list), true)
}
binding.recyclerview.isLoading = true
disposable +=
if (showInvalidated)
profileSwitchWithInvalid(now)
.zipWith(effectiveProfileSwitchWithInvalid(now)) { first, second -> first + second }
.map { ml -> ml.sortedByDescending { it.timestamp } }
.observeOn(aapsSchedulers.main)
.subscribe { list ->
binding.recyclerview.swapAdapter(RecyclerProfileViewAdapter(list), true)
}
else
profileSwitches(now)
.zipWith(effectiveProfileSwitches(now)) { first, second -> first + second }
.map { ml -> ml.sortedByDescending { it.timestamp } }
.observeOn(aapsSchedulers.main)
.subscribe { list ->
binding.recyclerview.swapAdapter(RecyclerProfileViewAdapter(list), true)
}
}
@Synchronized
@ -162,6 +167,7 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() {
@Synchronized
override fun onPause() {
super.onPause()
actionHelper.finish()
disposable.clear()
}
@ -181,69 +187,68 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() {
val profileSwitch = profileSwitchList[position]
holder.binding.ph.visibility = (profileSwitch is ProfileSealed.EPS).toVisibility()
holder.binding.ns.visibility = (profileSwitch.interfaceIDs_backing?.nightscoutId != null).toVisibility()
val sameDayPrevious = position > 0 && dateUtil.isSameDay(profileSwitch.timestamp, profileSwitchList[position - 1].timestamp)
holder.binding.date.visibility = sameDayPrevious.not().toVisibility()
holder.binding.date.text = dateUtil.dateString(profileSwitch.timestamp)
val newDay = position == 0 || !dateUtil.isSameDayGroup(profileSwitch.timestamp, profileSwitchList[position - 1].timestamp)
holder.binding.date.visibility = newDay.toVisibility()
holder.binding.date.text = if (newDay) dateUtil.dateStringRelative(profileSwitch.timestamp, rh) else ""
holder.binding.time.text = dateUtil.timeString(profileSwitch.timestamp)
holder.binding.duration.text = rh.gs(R.string.format_mins, T.msecs(profileSwitch.duration ?: 0L).mins())
holder.binding.name.text = if (profileSwitch is ProfileSealed.PS) profileSwitch.value.getCustomizedName() else if (profileSwitch is ProfileSealed.EPS) profileSwitch.value.originalCustomizedName else ""
if (profileSwitch.isInProgress(dateUtil)) holder.binding.date.setTextColor(rh.gc(R.color.colorActive))
holder.binding.name.text =
if (profileSwitch is ProfileSealed.PS) profileSwitch.value.getCustomizedName() else if (profileSwitch is ProfileSealed.EPS) profileSwitch.value.originalCustomizedName else ""
if (profileSwitch.isInProgress(dateUtil)) holder.binding.date.setTextColor(rh.gac(context , R.attr.activeColor))
else holder.binding.date.setTextColor(holder.binding.duration.currentTextColor)
holder.binding.remove.tag = profileSwitch
holder.binding.clone.tag = profileSwitch
holder.binding.name.tag = profileSwitch
holder.binding.date.tag = profileSwitch
holder.binding.invalid.visibility = profileSwitch.isValid.not().toVisibility()
holder.binding.duration.visibility = (profileSwitch.duration != 0L && profileSwitch.duration != null).toVisibility()
holder.binding.remove.visibility = (profileSwitch is ProfileSealed.PS).toVisibility()
holder.binding.cbRemove.visibility = (actionHelper.isRemoving && profileSwitch is ProfileSealed.PS).toVisibility()
if (actionHelper.isRemoving) {
holder.binding.cbRemove.setOnCheckedChangeListener { _, value ->
actionHelper.updateSelection(position, profileSwitch, value)
}
holder.binding.root.setOnClickListener {
holder.binding.cbRemove.toggle()
actionHelper.updateSelection(position, profileSwitch, holder.binding.cbRemove.isChecked)
}
holder.binding.cbRemove.isChecked = actionHelper.isSelected(position)
}
holder.binding.clone.visibility = (profileSwitch is ProfileSealed.PS).toVisibility()
holder.binding.spacer.visibility = (profileSwitch is ProfileSealed.PS).toVisibility()
holder.binding.root.setBackgroundColor(rh.gc(if (profileSwitch is ProfileSealed.PS) R.color.defaultbackground else R.color.list_delimiter))
val nextTimestamp = if (profileSwitchList.size != position + 1) profileSwitchList[position + 1].timestamp else 0L
holder.binding.delimiter.visibility = dateUtil.isSameDay(profileSwitch.timestamp, nextTimestamp).toVisibility()
}
override fun getItemCount(): Int {
return profileSwitchList.size
}
override fun getItemCount() = profileSwitchList.size
inner class ProfileSwitchViewHolder internal constructor(itemView: View) : RecyclerView.ViewHolder(itemView) {
val binding = TreatmentsProfileswitchItemBinding.bind(itemView)
init {
binding.remove.setOnClickListener { view ->
val profileSwitch = view.tag as ProfileSealed.PS
activity?.let { activity ->
OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord),
rh.gs(R.string.careportal_profileswitch) + ": " + profileSwitch.profileName +
"\n" + rh.gs(R.string.date) + ": " + dateUtil.dateAndTimeString(profileSwitch.timestamp), Runnable {
uel.log(Action.PROFILE_SWITCH_REMOVED, Sources.Treatments, profileSwitch.profileName,
ValueWithUnit.Timestamp(profileSwitch.timestamp))
disposable += repository.runTransactionForResult(InvalidateProfileSwitchTransaction(profileSwitch.id))
.subscribe(
{ result -> result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated ProfileSwitch $it") } },
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating ProfileSwitch", it) }
)
})
}
}
binding.clone.setOnClickListener {
activity?.let { activity ->
val profileSwitch = (it.tag as ProfileSealed.PS).value
val profileSealed = it.tag as ProfileSealed
OKDialog.showConfirmation(activity, rh.gs(R.string.careportal_profileswitch), rh.gs(R.string.copytolocalprofile) + "\n" + profileSwitch.getCustomizedName() + "\n" + dateUtil.dateAndTimeString(profileSwitch.timestamp), Runnable {
uel.log(Action.PROFILE_SWITCH_CLONED, Sources.Treatments,
profileSwitch.getCustomizedName() + " " + dateUtil.dateAndTimeString(profileSwitch.timestamp).replace(".", "_"),
ValueWithUnit.Timestamp(profileSwitch.timestamp),
ValueWithUnit.SimpleString(profileSwitch.profileName))
val nonCustomized = profileSealed.convertToNonCustomizedProfile(dateUtil)
localProfilePlugin.addProfile(localProfilePlugin.copyFrom(nonCustomized, profileSwitch.getCustomizedName() + " " + dateUtil.dateAndTimeString(profileSwitch.timestamp).replace(".", "_")))
rxBus.send(EventLocalProfileChanged())
})
OKDialog.showConfirmation(
activity,
rh.gs(R.string.careportal_profileswitch),
rh.gs(R.string.copytolocalprofile) + "\n" + profileSwitch.getCustomizedName() + "\n" + dateUtil.dateAndTimeString(profileSwitch.timestamp),
Runnable {
uel.log(
Action.PROFILE_SWITCH_CLONED, Sources.Treatments,
profileSwitch.getCustomizedName() + " " + dateUtil.dateAndTimeString(profileSwitch.timestamp).replace(".", "_"),
ValueWithUnit.Timestamp(profileSwitch.timestamp),
ValueWithUnit.SimpleString(profileSwitch.profileName)
)
val nonCustomized = profileSealed.convertToNonCustomizedProfile(dateUtil)
localProfilePlugin.addProfile(
localProfilePlugin.copyFrom(
nonCustomized,
profileSwitch.getCustomizedName() + " " + dateUtil.dateAndTimeString(profileSwitch.timestamp).replace(".", "_")
)
)
rxBus.send(EventLocalProfileChanged())
})
}
}
binding.remove.paintFlags = binding.remove.paintFlags or Paint.UNDERLINE_TEXT_FLAG
binding.clone.paintFlags = binding.clone.paintFlags or Paint.UNDERLINE_TEXT_FLAG
binding.name.setOnClickListener {
ProfileViewerDialog().also { pvd ->
@ -266,4 +271,78 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() {
}
}
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
this.menu = menu
inflater.inflate(R.menu.menu_treatments_profile_switch, menu)
super.onCreateOptionsMenu(menu, inflater)
}
private fun updateMenuVisibility() {
menu?.findItem(R.id.nav_hide_invalidated)?.isVisible = showInvalidated
menu?.findItem(R.id.nav_show_invalidated)?.isVisible = !showInvalidated
}
override fun onPrepareOptionsMenu(menu: Menu) {
updateMenuVisibility()
val nsUploadOnly = !sp.getBoolean(R.string.key_ns_receive_profile_switch, false) || !buildHelper.isEngineeringMode()
menu.findItem(R.id.nav_refresh_ns)?.isVisible = !nsUploadOnly
return super.onPrepareOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean =
when (item.itemId) {
R.id.nav_remove_items -> actionHelper.startRemove()
R.id.nav_show_invalidated -> {
showInvalidated = true
updateMenuVisibility()
ToastUtils.showToastInUiThread(context, rh.gs(R.string.show_invalidated_records))
swapAdapter()
true
}
R.id.nav_hide_invalidated -> {
showInvalidated = false
updateMenuVisibility()
ToastUtils.showToastInUiThread(context, rh.gs(R.string.hide_invalidated_records))
swapAdapter()
true
}
R.id.nav_refresh_ns -> {
refreshFromNightscout()
true
}
else -> false
}
private fun getConfirmationText(selectedItems: SparseArray<ProfileSealed>): String {
if (selectedItems.size() == 1) {
val profileSwitch = selectedItems.valueAt(0)
return rh.gs(R.string.careportal_profileswitch) + ": " + profileSwitch.profileName + "\n" + rh.gs(R.string.date) + ": " + dateUtil.dateAndTimeString(profileSwitch.timestamp)
}
return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size())
}
private fun removeSelected(selectedItems: SparseArray<ProfileSealed>) {
activity?.let { activity ->
OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(selectedItems), Runnable {
selectedItems.forEach { _, profileSwitch ->
uel.log(
Action.PROFILE_SWITCH_REMOVED, Sources.Treatments, profileSwitch.profileName,
ValueWithUnit.Timestamp(profileSwitch.timestamp)
)
disposable += repository.runTransactionForResult(InvalidateProfileSwitchTransaction(profileSwitch.id))
.subscribe(
{ result -> result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated ProfileSwitch $it") } },
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating ProfileSwitch", it) }
)
}
actionHelper.finish()
})
}
}
}

View file

@ -1,52 +1,49 @@
package info.nightscout.androidaps.activities.fragments
import android.annotation.SuppressLint
import android.content.DialogInterface
import android.graphics.Paint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.util.SparseArray
import android.view.*
import androidx.core.util.forEach
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.R
import info.nightscout.androidaps.activities.fragments.TreatmentsTempTargetFragment.RecyclerViewAdapter.TempTargetsViewHolder
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.ValueWrapper
import info.nightscout.androidaps.database.entities.ValueWithUnit
import info.nightscout.androidaps.database.entities.TemporaryTarget
import info.nightscout.androidaps.database.entities.UserEntry.Action
import info.nightscout.androidaps.database.entities.UserEntry.Sources
import info.nightscout.androidaps.database.entities.ValueWithUnit
import info.nightscout.androidaps.database.interfaces.end
import info.nightscout.androidaps.database.transactions.InvalidateTemporaryTargetTransaction
import info.nightscout.androidaps.databinding.TreatmentsTemptargetFragmentBinding
import info.nightscout.androidaps.databinding.TreatmentsTemptargetItemBinding
import info.nightscout.androidaps.events.EventEffectiveProfileSwitchChanged
import info.nightscout.androidaps.events.EventProfileSwitchChanged
import info.nightscout.androidaps.events.EventTempTargetChange
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart
import info.nightscout.androidaps.events.EventTreatmentUpdateGui
import info.nightscout.androidaps.activities.fragments.TreatmentsTempTargetFragment.RecyclerViewAdapter.TempTargetsViewHolder
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.Translator
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.buildHelper.BuildHelper
import info.nightscout.androidaps.extensions.friendlyDescription
import info.nightscout.androidaps.extensions.highValueToUnitsToString
import info.nightscout.androidaps.extensions.lowValueToUnitsToString
import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventNewHistoryData
import info.nightscout.androidaps.utils.*
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.interfaces.BuildHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import io.reactivex.Completable
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import io.reactivex.rxkotlin.subscribeBy
import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import io.reactivex.rxjava3.kotlin.subscribeBy
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@ -65,80 +62,86 @@ class TreatmentsTempTargetFragment : DaggerFragment() {
@Inject lateinit var uel: UserEntryLogger
@Inject lateinit var repository: AppRepository
private val disposable = CompositeDisposable()
private val millsToThePast = T.days(30).msecs()
private var _binding: TreatmentsTemptargetFragmentBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
// This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!!
private var menu: Menu? = null
private lateinit var actionHelper: ActionModeHelper<TemporaryTarget>
private val disposable = CompositeDisposable()
private val millsToThePast = T.days(30).msecs()
private var showInvalidated = false
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
TreatmentsTemptargetFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root
@SuppressLint("NotifyDataSetChanged")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.recyclerview.setHasFixedSize(true)
actionHelper = ActionModeHelper(rh, activity, this)
actionHelper.setUpdateListHandler { binding.recyclerview.adapter?.notifyDataSetChanged() }
actionHelper.setOnRemoveHandler { removeSelected(it) }
setHasOptionsMenu(true)
binding.recyclerview.layoutManager = LinearLayoutManager(view.context)
binding.refreshFromNightscout.setOnClickListener {
context?.let { context ->
OKDialog.showConfirmation(context, rh.gs(R.string.refresheventsfromnightscout) + " ?", {
uel.log(Action.TT_NS_REFRESH, Sources.Treatments)
disposable += Completable.fromAction { repository.deleteAllTempTargetEntries() }
binding.recyclerview.emptyView = binding.noRecordsText
binding.recyclerview.loadingView = binding.progressBar
}
private fun refreshFromNightscout() {
activity?.let { activity ->
OKDialog.showConfirmation(activity, rh.gs(R.string.refresheventsfromnightscout) + "?") {
uel.log(Action.TREATMENTS_NS_REFRESH, Sources.Treatments)
disposable +=
Completable.fromAction {
repository.deleteAllEffectiveProfileSwitches()
repository.deleteAllProfileSwitches()
}
.subscribeOn(aapsSchedulers.io)
.observeOn(aapsSchedulers.main)
.subscribeBy(
onError = { aapsLogger.error("Error removing entries", it) },
onComplete = { rxBus.send(EventTempTargetChange()) }
onComplete = {
rxBus.send(EventProfileSwitchChanged())
rxBus.send(EventEffectiveProfileSwitchChanged(0L))
rxBus.send(EventNewHistoryData(0, false))
}
)
rxBus.send(EventNSClientRestart())
})
rxBus.send(EventNSClientRestart())
}
}
val nsUploadOnly = !sp.getBoolean(R.string.key_ns_receive_temp_target, false) || !buildHelper.isEngineeringMode()
if (nsUploadOnly) binding.refreshFromNightscout.visibility = View.INVISIBLE
binding.showInvalidated.setOnCheckedChangeListener { _, _ ->
rxBus.send(EventTreatmentUpdateGui())
}
}
fun swapAdapter() {
val now = System.currentTimeMillis()
if (binding.showInvalidated.isChecked)
repository
.getTemporaryTargetDataIncludingInvalidFromTime(now - millsToThePast, false)
.observeOn(aapsSchedulers.main)
.subscribe { list -> binding.recyclerview.swapAdapter(RecyclerViewAdapter(list), true) }
else
repository
.getTemporaryTargetDataFromTime(now - millsToThePast, false)
.observeOn(aapsSchedulers.main)
.subscribe { list -> binding.recyclerview.swapAdapter(RecyclerViewAdapter(list), true) }
binding.recyclerview.isLoading = true
disposable +=
if (showInvalidated)
repository
.getTemporaryTargetDataIncludingInvalidFromTime(now - millsToThePast, false)
.observeOn(aapsSchedulers.main)
.subscribe { list -> binding.recyclerview.swapAdapter(RecyclerViewAdapter(list), true) }
else
repository
.getTemporaryTargetDataFromTime(now - millsToThePast, false)
.observeOn(aapsSchedulers.main)
.subscribe { list -> binding.recyclerview.swapAdapter(RecyclerViewAdapter(list), true) }
}
@Synchronized
override fun onResume() {
super.onResume()
swapAdapter()
disposable += rxBus
.toObservable(EventTempTargetChange::class.java)
.observeOn(aapsSchedulers.io)
.debounce(1L, TimeUnit.SECONDS)
.subscribe({ swapAdapter() }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventTreatmentUpdateGui::class.java) // TODO join with above
.observeOn(aapsSchedulers.io)
.debounce(1L, TimeUnit.SECONDS)
.subscribe({ swapAdapter() }, fabricPrivacy::logException)
}
@Synchronized
override fun onPause() {
super.onPause()
actionHelper.finish()
disposable.clear()
}
@ -163,10 +166,20 @@ class TreatmentsTempTargetFragment : DaggerFragment() {
val tempTarget = tempTargetList[position]
holder.binding.ns.visibility = (tempTarget.interfaceIDs.nightscoutId != null).toVisibility()
holder.binding.invalid.visibility = tempTarget.isValid.not().toVisibility()
holder.binding.remove.visibility = tempTarget.isValid.toVisibility()
val sameDayPrevious = position > 0 && dateUtil.isSameDay(tempTarget.timestamp, tempTargetList[position-1].timestamp)
holder.binding.date.visibility = sameDayPrevious.not().toVisibility()
holder.binding.date.text = dateUtil.dateString(tempTarget.timestamp)
holder.binding.cbRemove.visibility = (tempTarget.isValid && actionHelper.isRemoving).toVisibility()
if (actionHelper.isRemoving) {
holder.binding.cbRemove.setOnCheckedChangeListener { _, value ->
actionHelper.updateSelection(position, tempTarget, value)
}
holder.binding.root.setOnClickListener {
holder.binding.cbRemove.toggle()
actionHelper.updateSelection(position, tempTarget, holder.binding.cbRemove.isChecked)
}
holder.binding.cbRemove.isChecked = actionHelper.isSelected(position)
}
val newDay = position == 0 || !dateUtil.isSameDayGroup(tempTarget.timestamp, tempTargetList[position - 1].timestamp)
holder.binding.date.visibility = newDay.toVisibility()
holder.binding.date.text = if (newDay) dateUtil.dateStringRelative(tempTarget.timestamp, rh) else ""
holder.binding.time.text = dateUtil.timeRangeString(tempTarget.timestamp, tempTarget.end)
holder.binding.duration.text = rh.gs(R.string.format_mins, T.msecs(tempTarget.duration).mins())
holder.binding.low.text = tempTarget.lowValueToUnitsToString(units)
@ -174,46 +187,98 @@ class TreatmentsTempTargetFragment : DaggerFragment() {
holder.binding.reason.text = translator.translate(tempTarget.reason)
holder.binding.time.setTextColor(
when {
tempTarget.id == currentlyActiveTarget?.id -> rh.gc(R.color.colorActive)
tempTarget.timestamp > dateUtil.now() -> rh.gc(R.color.colorScheduled)
tempTarget.id == currentlyActiveTarget?.id -> rh.gac(context, R.attr.activeColor)
tempTarget.timestamp > dateUtil.now() -> rh.gac(context, R.attr.scheduledColor)
else -> holder.binding.reasonColon.currentTextColor
})
holder.binding.remove.tag = tempTarget
val nextTimestamp = if (tempTargetList.size != position + 1) tempTargetList[position + 1].timestamp else 0L
holder.binding.delimiter.visibility = dateUtil.isSameDay(tempTarget.timestamp, nextTimestamp).toVisibility()
}
)
}
override fun getItemCount(): Int = tempTargetList.size
override fun getItemCount() = tempTargetList.size
inner class TempTargetsViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val binding = TreatmentsTemptargetItemBinding.bind(view)
init {
binding.remove.setOnClickListener { v: View ->
val tempTarget = v.tag as TemporaryTarget
context?.let { context ->
OKDialog.showConfirmation(context, rh.gs(R.string.removerecord),
"""
${rh.gs(R.string.careportal_temporarytarget)}: ${tempTarget.friendlyDescription(profileFunction.getUnits(), rh)}
${dateUtil.dateAndTimeString(tempTarget.timestamp)}
""".trimIndent(),
{ _: DialogInterface?, _: Int ->
uel.log(Action.TT_REMOVED, Sources.Treatments,
ValueWithUnit.Timestamp(tempTarget.timestamp),
ValueWithUnit.TherapyEventTTReason(tempTarget.reason),
ValueWithUnit.Mgdl(tempTarget.lowTarget),
ValueWithUnit.Mgdl(tempTarget.highTarget).takeIf { tempTarget.lowTarget != tempTarget.highTarget },
ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(tempTarget.duration).toInt()))
disposable += repository.runTransactionForResult(InvalidateTemporaryTargetTransaction(tempTarget.id))
.subscribe(
{ aapsLogger.debug(LTag.DATABASE, "Removed temp target $tempTarget") },
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating temporary target", it) })
}, null)
}
}
binding.remove.paintFlags = binding.remove.paintFlags or Paint.UNDERLINE_TEXT_FLAG
}
}
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
this.menu = menu
inflater.inflate(R.menu.menu_treatments_temp_target, menu)
super.onCreateOptionsMenu(menu, inflater)
}
private fun updateMenuVisibility() {
menu?.findItem(R.id.nav_hide_invalidated)?.isVisible = showInvalidated
menu?.findItem(R.id.nav_show_invalidated)?.isVisible = !showInvalidated
}
override fun onPrepareOptionsMenu(menu: Menu) {
updateMenuVisibility()
val nsUploadOnly = !sp.getBoolean(R.string.key_ns_receive_temp_target, false) || !buildHelper.isEngineeringMode()
menu.findItem(R.id.nav_refresh_ns)?.isVisible = !nsUploadOnly
return super.onPrepareOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean =
when (item.itemId) {
R.id.nav_remove_items -> actionHelper.startRemove()
R.id.nav_show_invalidated -> {
showInvalidated = true
updateMenuVisibility()
ToastUtils.showToastInUiThread(context, rh.gs(R.string.show_invalidated_records))
swapAdapter()
true
}
R.id.nav_hide_invalidated -> {
showInvalidated = false
updateMenuVisibility()
ToastUtils.showToastInUiThread(context, rh.gs(R.string.show_invalidated_records))
swapAdapter()
true
}
R.id.nav_refresh_ns -> {
refreshFromNightscout()
true
}
else -> false
}
private fun getConfirmationText(selectedItems: SparseArray<TemporaryTarget>): String {
if (selectedItems.size() == 1) {
val tempTarget = selectedItems.valueAt(0)
return "${rh.gs(R.string.careportal_temporarytarget)}: ${tempTarget.friendlyDescription(profileFunction.getUnits(), rh)}\n" +
dateUtil.dateAndTimeString(tempTarget.timestamp)
}
return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size())
}
private fun removeSelected(selectedItems: SparseArray<TemporaryTarget>) {
activity?.let { activity ->
OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(selectedItems), Runnable {
selectedItems.forEach { _, tempTarget ->
uel.log(
Action.TT_REMOVED, Sources.Treatments,
ValueWithUnit.Timestamp(tempTarget.timestamp),
ValueWithUnit.TherapyEventTTReason(tempTarget.reason),
ValueWithUnit.Mgdl(tempTarget.lowTarget),
ValueWithUnit.Mgdl(tempTarget.highTarget).takeIf { tempTarget.lowTarget != tempTarget.highTarget },
ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(tempTarget.duration).toInt())
)
disposable += repository.runTransactionForResult(InvalidateTemporaryTargetTransaction(tempTarget.id))
.subscribe(
{ aapsLogger.debug(LTag.DATABASE, "Removed temp target $tempTarget") },
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating temporary target", it) })
}
actionHelper.finish()
})
}
}
}

View file

@ -1,15 +1,15 @@
package info.nightscout.androidaps.activities.fragments
import android.content.DialogInterface
import android.graphics.Paint
import android.annotation.SuppressLint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.util.SparseArray
import android.view.*
import androidx.core.util.forEach
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.R
import info.nightscout.androidaps.activities.fragments.TreatmentsTemporaryBasalsFragment.RecyclerViewAdapter.TempBasalsViewHolder
import info.nightscout.androidaps.data.IobTotal
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.ValueWrapper
@ -23,7 +23,6 @@ import info.nightscout.androidaps.database.transactions.InvalidateExtendedBolusT
import info.nightscout.androidaps.database.transactions.InvalidateTemporaryBasalTransaction
import info.nightscout.androidaps.databinding.TreatmentsTempbasalsFragmentBinding
import info.nightscout.androidaps.databinding.TreatmentsTempbasalsItemBinding
import info.nightscout.androidaps.events.EventAutosensCalculationFinished
import info.nightscout.androidaps.events.EventTempBasalChange
import info.nightscout.androidaps.extensions.iobCalc
import info.nightscout.androidaps.extensions.toStringFull
@ -31,19 +30,20 @@ import info.nightscout.androidaps.extensions.toTemporaryBasal
import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.activities.fragments.TreatmentsTemporaryBasalsFragment.RecyclerViewAdapter.TempBasalsViewHolder
import info.nightscout.androidaps.utils.ActionModeHelper
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.ToastUtils
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlin.math.abs
@ -65,19 +65,27 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() {
private var _binding: TreatmentsTempbasalsFragmentBinding? = null
private val millsToThePast = T.days(30).msecs()
// This property is only valid between onCreateView and
// onDestroyView.
// This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!!
private var menu: Menu? = null
private lateinit var actionHelper: ActionModeHelper<TemporaryBasal>
private val millsToThePast = T.days(30).msecs()
private var showInvalidated = false
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
TreatmentsTempbasalsFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root
@SuppressLint("NotifyDataSetChanged")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
actionHelper = ActionModeHelper(rh, activity, this)
actionHelper.setUpdateListHandler { binding.recyclerview.adapter?.notifyDataSetChanged() }
actionHelper.setOnRemoveHandler { removeSelected(it) }
setHasOptionsMenu(true)
binding.recyclerview.setHasFixedSize(true)
binding.recyclerview.layoutManager = LinearLayoutManager(view.context)
binding.recyclerview.emptyView = binding.noRecordsText
binding.recyclerview.loadingView = binding.progressBar
}
private fun tempBasalsWithInvalid(now: Long) = repository
@ -96,9 +104,10 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() {
fun swapAdapter() {
val now = System.currentTimeMillis()
binding.recyclerview.isLoading = true
disposable +=
if (activePlugin.activePump.isFakingTempsByExtendedBoluses) {
if (binding.showInvalidated.isChecked)
if (showInvalidated)
tempBasalsWithInvalid(now)
.zipWith(extendedBolusesWithInvalid(now)) { first, second -> first + second }
.map { list -> list.filterNotNull() }
@ -113,7 +122,7 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() {
.observeOn(aapsSchedulers.main)
.subscribe { list -> binding.recyclerview.swapAdapter(RecyclerViewAdapter(list), true) }
} else {
if (binding.showInvalidated.isChecked)
if (showInvalidated)
tempBasalsWithInvalid(now)
.observeOn(aapsSchedulers.main)
.subscribe { list -> binding.recyclerview.swapAdapter(RecyclerViewAdapter(list), true) }
@ -129,21 +138,16 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() {
override fun onResume() {
super.onResume()
swapAdapter()
disposable += rxBus
.toObservable(EventTempBasalChange::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({ swapAdapter() }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventAutosensCalculationFinished::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({ swapAdapter() }, fabricPrivacy::logException)
}
@Synchronized
override fun onPause() {
super.onPause()
actionHelper.finish()
disposable.clear()
}
@ -164,12 +168,14 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() {
holder.binding.ns.visibility = (tempBasal.interfaceIDs.nightscoutId != null).toVisibility()
holder.binding.invalid.visibility = tempBasal.isValid.not().toVisibility()
holder.binding.ph.visibility = (tempBasal.interfaceIDs.pumpId != null).toVisibility()
val sameDayPrevious = position > 0 && dateUtil.isSameDay(tempBasal.timestamp, tempBasalList[position-1].timestamp)
val sameDayPrevious = position > 0 && dateUtil.isSameDay(tempBasal.timestamp, tempBasalList[position - 1].timestamp)
holder.binding.date.visibility = sameDayPrevious.not().toVisibility()
holder.binding.date.text = dateUtil.dateString(tempBasal.timestamp)
val newDay = position == 0 || !dateUtil.isSameDayGroup(tempBasal.timestamp, tempBasalList[position - 1].timestamp)
holder.binding.date.visibility = newDay.toVisibility()
holder.binding.date.text = if (newDay) dateUtil.dateStringRelative(tempBasal.timestamp, rh) else ""
if (tempBasal.isInProgress) {
holder.binding.time.text = dateUtil.timeString(tempBasal.timestamp)
holder.binding.time.setTextColor(rh.gc(R.color.colorActive))
holder.binding.time.setTextColor(rh.gac(context, R.attr.activeColor))
} else {
holder.binding.time.text = dateUtil.timeRangeString(tempBasal.timestamp, tempBasal.end)
holder.binding.time.setTextColor(holder.binding.duration.currentTextColor)
@ -186,63 +192,120 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() {
holder.binding.suspendFlag.visibility = (tempBasal.type == TemporaryBasal.Type.PUMP_SUSPEND).toVisibility()
holder.binding.emulatedSuspendFlag.visibility = (tempBasal.type == TemporaryBasal.Type.EMULATED_PUMP_SUSPEND).toVisibility()
holder.binding.superBolusFlag.visibility = (tempBasal.type == TemporaryBasal.Type.SUPERBOLUS).toVisibility()
if (abs(iob.basaliob) > 0.01) holder.binding.iob.setTextColor(rh.gc(R.color.colorActive)) else holder.binding.iob.setTextColor(holder.binding.duration.currentTextColor)
holder.binding.remove.tag = tempBasal
val nextTimestamp = if (tempBasalList.size != position + 1) tempBasalList[position + 1].timestamp else 0L
holder.binding.delimiter.visibility = dateUtil.isSameDay(tempBasal.timestamp, nextTimestamp).toVisibility()
if (abs(iob.basaliob) > 0.01) holder.binding.iob.setTextColor(rh.gac(context, R.attr.activeColor)) else holder.binding.iob.setTextColor(holder.binding.duration.currentTextColor)
holder.binding.cbRemove.visibility = (tempBasal.isValid && actionHelper.isRemoving).toVisibility()
if (actionHelper.isRemoving) {
holder.binding.cbRemove.setOnCheckedChangeListener { _, value ->
actionHelper.updateSelection(position, tempBasal, value)
}
holder.binding.root.setOnClickListener {
holder.binding.cbRemove.toggle()
actionHelper.updateSelection(position, tempBasal, holder.binding.cbRemove.isChecked)
}
holder.binding.cbRemove.isChecked = actionHelper.isSelected(position)
}
}
override fun getItemCount(): Int = tempBasalList.size
override fun getItemCount() = tempBasalList.size
inner class TempBasalsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val binding = TreatmentsTempbasalsItemBinding.bind(itemView)
init {
binding.remove.setOnClickListener { v: View ->
val tempBasal = v.tag as TemporaryBasal
var extendedBolus: ExtendedBolus? = null
val isFakeExtended = tempBasal.type == TemporaryBasal.Type.FAKE_EXTENDED
if (isFakeExtended) {
val eb = repository.getExtendedBolusActiveAt(tempBasal.timestamp).blockingGet()
extendedBolus = if (eb is ValueWrapper.Existing) eb.value else null
}
val profile = profileFunction.getProfile(dateUtil.now())
?: return@setOnClickListener
context?.let {
OKDialog.showConfirmation(it, rh.gs(R.string.removerecord),
"""
${if (isFakeExtended) rh.gs(R.string.extended_bolus) else rh.gs(R.string.tempbasal_label)}: ${tempBasal.toStringFull(profile, dateUtil)}
${rh.gs(R.string.date)}: ${dateUtil.dateAndTimeString(tempBasal.timestamp)}
""".trimIndent(),
{ _: DialogInterface?, _: Int ->
if (isFakeExtended && extendedBolus != null) {
uel.log(Action.EXTENDED_BOLUS_REMOVED, Sources.Treatments,
ValueWithUnit.Timestamp(extendedBolus.timestamp),
ValueWithUnit.Insulin(extendedBolus.amount),
ValueWithUnit.UnitPerHour(extendedBolus.rate),
ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(extendedBolus.duration).toInt()))
disposable += repository.runTransactionForResult(InvalidateExtendedBolusTransaction(extendedBolus.id))
.subscribe(
{ aapsLogger.debug(LTag.DATABASE, "Removed extended bolus $extendedBolus") },
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating extended bolus", it) })
} else if (!isFakeExtended) {
uel.log(Action.TEMP_BASAL_REMOVED, Sources.Treatments,
ValueWithUnit.Timestamp(tempBasal.timestamp),
if (tempBasal.isAbsolute) ValueWithUnit.UnitPerHour(tempBasal.rate) else ValueWithUnit.Percent(tempBasal.rate.toInt()),
ValueWithUnit.Minute(T.msecs(tempBasal.duration).mins().toInt()))
disposable += repository.runTransactionForResult(InvalidateTemporaryBasalTransaction(tempBasal.id))
.subscribe(
{ aapsLogger.debug(LTag.DATABASE, "Removed temporary basal $tempBasal") },
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating temporary basal", it) })
}
}, null)
}
}
binding.remove.paintFlags = binding.remove.paintFlags or Paint.UNDERLINE_TEXT_FLAG
}
}
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
this.menu = menu
inflater.inflate(R.menu.menu_treatments_temp_basal, menu)
super.onCreateOptionsMenu(menu, inflater)
}
private fun updateMenuVisibility() {
menu?.findItem(R.id.nav_hide_invalidated)?.isVisible = showInvalidated
menu?.findItem(R.id.nav_show_invalidated)?.isVisible = !showInvalidated
}
override fun onPrepareOptionsMenu(menu: Menu) {
updateMenuVisibility()
return super.onPrepareOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean =
when (item.itemId) {
R.id.nav_remove_items -> actionHelper.startRemove()
R.id.nav_show_invalidated -> {
showInvalidated = true
updateMenuVisibility()
ToastUtils.showToastInUiThread(context, rh.gs(R.string.show_invalidated_records))
swapAdapter()
true
}
R.id.nav_hide_invalidated -> {
showInvalidated = false
updateMenuVisibility()
ToastUtils.showToastInUiThread(context, rh.gs(R.string.hide_invalidated_records))
swapAdapter()
true
}
else -> false
}
private fun getConfirmationText(selectedItems: SparseArray<TemporaryBasal>): String {
if (selectedItems.size() == 1) {
val tempBasal = selectedItems.valueAt(0)
val isFakeExtended = tempBasal.type == TemporaryBasal.Type.FAKE_EXTENDED
val profile = profileFunction.getProfile(dateUtil.now())
if (profile != null)
return "${if (isFakeExtended) rh.gs(R.string.extended_bolus) else rh.gs(R.string.tempbasal_label)}: ${tempBasal.toStringFull(profile, dateUtil)}\n" +
"${rh.gs(R.string.date)}: ${dateUtil.dateAndTimeString(tempBasal.timestamp)}"
}
return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size())
}
private fun removeSelected(selectedItems: SparseArray<TemporaryBasal>) {
if (selectedItems.size() > 0)
activity?.let { activity ->
OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(selectedItems), Runnable {
selectedItems.forEach { _, tempBasal ->
var extendedBolus: ExtendedBolus? = null
val isFakeExtended = tempBasal.type == TemporaryBasal.Type.FAKE_EXTENDED
if (isFakeExtended) {
val eb = repository.getExtendedBolusActiveAt(tempBasal.timestamp).blockingGet()
extendedBolus = if (eb is ValueWrapper.Existing) eb.value else null
}
if (isFakeExtended && extendedBolus != null) {
uel.log(
Action.EXTENDED_BOLUS_REMOVED, Sources.Treatments,
ValueWithUnit.Timestamp(extendedBolus.timestamp),
ValueWithUnit.Insulin(extendedBolus.amount),
ValueWithUnit.UnitPerHour(extendedBolus.rate),
ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(extendedBolus.duration).toInt())
)
disposable += repository.runTransactionForResult(InvalidateExtendedBolusTransaction(extendedBolus.id))
.subscribe(
{ aapsLogger.debug(LTag.DATABASE, "Removed extended bolus $extendedBolus") },
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating extended bolus", it) })
} else if (!isFakeExtended) {
uel.log(
Action.TEMP_BASAL_REMOVED, Sources.Treatments,
ValueWithUnit.Timestamp(tempBasal.timestamp),
if (tempBasal.isAbsolute) ValueWithUnit.UnitPerHour(tempBasal.rate) else ValueWithUnit.Percent(tempBasal.rate.toInt()),
ValueWithUnit.Minute(T.msecs(tempBasal.duration).mins().toInt())
)
disposable += repository.runTransactionForResult(InvalidateTemporaryBasalTransaction(tempBasal.id))
.subscribe(
{ aapsLogger.debug(LTag.DATABASE, "Removed temporary basal $tempBasal") },
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating temporary basal", it) })
}
}
actionHelper.finish()
})
}
}
}

View file

@ -1,9 +1,7 @@
package info.nightscout.androidaps.activities.fragments
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.*
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import dagger.android.support.DaggerFragment
@ -15,22 +13,22 @@ import info.nightscout.androidaps.database.entities.UserEntry.Sources
import info.nightscout.androidaps.databinding.TreatmentsUserEntryFragmentBinding
import info.nightscout.androidaps.databinding.TreatmentsUserEntryItemBinding
import info.nightscout.androidaps.events.EventPreferenceChange
import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.interfaces.ImportExportPrefs
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.events.EventTreatmentUpdateGui
import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.ToastUtils
import info.nightscout.androidaps.utils.Translator
import info.nightscout.androidaps.utils.userEntry.UserEntryPresentationHelper
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import io.reactivex.disposables.CompositeDisposable
import java.util.concurrent.TimeUnit
import info.nightscout.androidaps.utils.userEntry.UserEntryPresentationHelper
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import javax.inject.Inject
class TreatmentsUserEntryFragment : DaggerFragment() {
@ -48,14 +46,13 @@ class TreatmentsUserEntryFragment : DaggerFragment() {
@Inject lateinit var userEntryPresentationHelper: UserEntryPresentationHelper
private val disposable = CompositeDisposable()
private val millsToThePastFiltered = T.days(30).msecs()
private val millsToThePastUnFiltered = T.days(3).msecs()
private var menu: Menu? = null
private var showLoop = false
private var _binding: TreatmentsUserEntryFragmentBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
// This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
@ -63,51 +60,46 @@ class TreatmentsUserEntryFragment : DaggerFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setHasOptionsMenu(true)
binding.recyclerview.setHasFixedSize(true)
binding.recyclerview.layoutManager = LinearLayoutManager(view.context)
binding.ueExportToXml.setOnClickListener {
activity?.let { activity ->
OKDialog.showConfirmation(activity, rh.gs(R.string.ue_export_to_csv) + "?") {
uel.log(Action.EXPORT_CSV, Sources.Treatments)
importExportPrefs.exportUserEntriesCsv(activity)
}
binding.recyclerview.emptyView = binding.noRecordsText
binding.recyclerview.loadingView = binding.progressBar
}
private fun exportUserEntries() {
activity?.let { activity ->
OKDialog.showConfirmation(activity, rh.gs(R.string.ue_export_to_csv) + "?") {
uel.log(Action.EXPORT_CSV, Sources.Treatments)
importExportPrefs.exportUserEntriesCsv(activity)
}
}
binding.showLoop.setOnCheckedChangeListener { _, _ ->
rxBus.send(EventTreatmentUpdateGui())
}
}
fun swapAdapter() {
val now = System.currentTimeMillis()
if (binding.showLoop.isChecked)
disposable.add( repository
.getUserEntryDataFromTime(now - millsToThePastUnFiltered)
.observeOn(aapsSchedulers.main)
.subscribe { list -> binding.recyclerview.swapAdapter(UserEntryAdapter(list), true) }
)
else
disposable.add( repository
.getUserEntryFilteredDataFromTime(now - millsToThePastFiltered)
.observeOn(aapsSchedulers.main)
.subscribe { list -> binding.recyclerview.swapAdapter(UserEntryAdapter(list), true) }
)
binding.recyclerview.isLoading = true
disposable +=
if (showLoop)
repository
.getUserEntryDataFromTime(now - millsToThePastUnFiltered)
.observeOn(aapsSchedulers.main)
.subscribe { list -> binding.recyclerview.swapAdapter(UserEntryAdapter(list), true) }
else
repository
.getUserEntryFilteredDataFromTime(now - millsToThePastFiltered)
.observeOn(aapsSchedulers.main)
.subscribe { list -> binding.recyclerview.swapAdapter(UserEntryAdapter(list), true) }
}
@Synchronized
override fun onResume() {
super.onResume()
swapAdapter()
disposable.add(rxBus
disposable += rxBus
.toObservable(EventPreferenceChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ swapAdapter() }, fabricPrivacy::logException))
disposable.add(rxBus
.toObservable(EventTreatmentUpdateGui::class.java)
.observeOn(aapsSchedulers.io)
.debounce(1L, TimeUnit.SECONDS)
.subscribe({ swapAdapter() }, fabricPrivacy::logException))
.subscribe({ swapAdapter() }, fabricPrivacy::logException)
}
@Synchronized
@ -132,19 +124,16 @@ class TreatmentsUserEntryFragment : DaggerFragment() {
override fun onBindViewHolder(holder: UserEntryViewHolder, position: Int) {
val current = entries[position]
val sameDayPrevious = position > 0 && dateUtil.isSameDay(current.timestamp, entries[position-1].timestamp)
holder.binding.date.visibility = sameDayPrevious.not().toVisibility()
holder.binding.date.text = dateUtil.dateString(current.timestamp)
val newDay = position == 0 || !dateUtil.isSameDayGroup(current.timestamp, entries[position - 1].timestamp)
holder.binding.date.visibility = newDay.toVisibility()
holder.binding.date.text = if (newDay) dateUtil.dateStringRelative(current.timestamp, rh) else ""
holder.binding.time.text = dateUtil.timeStringWithSeconds(current.timestamp)
holder.binding.action.text = userEntryPresentationHelper.actionToColoredString(current.action)
holder.binding.notes.text = current.note
holder.binding.notes.visibility = if (current.note != "") View.VISIBLE else View.GONE
holder.binding.notes.visibility = (current.note != "").toVisibility()
holder.binding.iconSource.setImageResource(userEntryPresentationHelper.iconId(current.source))
holder.binding.iconSource.visibility = View.VISIBLE
holder.binding.values.text = userEntryPresentationHelper.listToPresentationString(current.values)
holder.binding.values.visibility = if (holder.binding.values.text != "") View.VISIBLE else View.GONE
val nextTimestamp = if (entries.size != position + 1) entries[position + 1].timestamp else 0L
holder.binding.delimiter.visibility = dateUtil.isSameDay(current.timestamp, nextTimestamp).toVisibility()
holder.binding.values.visibility = (holder.binding.values.text != "").toVisibility()
}
inner class UserEntryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
@ -152,7 +141,49 @@ class TreatmentsUserEntryFragment : DaggerFragment() {
val binding = TreatmentsUserEntryItemBinding.bind(itemView)
}
override fun getItemCount(): Int = entries.size
override fun getItemCount() = entries.size
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
this.menu = menu
inflater.inflate(R.menu.menu_treatments_user_entry, menu)
super.onCreateOptionsMenu(menu, inflater)
}
private fun updateMenuVisibility() {
menu?.findItem(R.id.nav_hide_loop)?.isVisible = showLoop
menu?.findItem(R.id.nav_show_loop)?.isVisible = !showLoop
}
override fun onPrepareOptionsMenu(menu: Menu) {
updateMenuVisibility()
return super.onPrepareOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean =
when (item.itemId) {
R.id.nav_show_loop -> {
showLoop = true
updateMenuVisibility()
ToastUtils.showToastInUiThread(context, rh.gs(R.string.show_loop_records))
swapAdapter()
true
}
R.id.nav_hide_loop -> {
showLoop = false
updateMenuVisibility()
ToastUtils.showToastInUiThread(context, rh.gs(R.string.show_hide_records))
swapAdapter()
true
}
R.id.nav_export -> {
exportUserEntries()
true
}
else -> false
}
}

View file

@ -7,7 +7,7 @@ import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventNewHistoryData
import io.reactivex.disposables.Disposable
import io.reactivex.rxjava3.disposables.Disposable
import javax.inject.Inject
import javax.inject.Singleton
@ -60,6 +60,11 @@ class CompatDBHelper @Inject constructor(
rxBus.send(EventExtendedBolusChange())
rxBus.send(EventNewHistoryData(timestamp, false))
}
it.filterIsInstance<EffectiveProfileSwitch>().firstOrNull()?.let { eps ->
aapsLogger.debug(LTag.DATABASE, "Firing EventEffectiveProfileSwitchChanged $eps")
rxBus.send(EventEffectiveProfileSwitchChanged(eps))
rxBus.send(EventNewHistoryData(eps.timestamp, false))
}
it.filterIsInstance<TemporaryTarget>().firstOrNull()?.let { tt ->
aapsLogger.debug(LTag.DATABASE, "Firing EventTempTargetChange $tt")
rxBus.send(EventTempTargetChange())
@ -76,10 +81,6 @@ class CompatDBHelper @Inject constructor(
aapsLogger.debug(LTag.DATABASE, "Firing EventProfileSwitchChanged $ps")
rxBus.send(EventProfileSwitchChanged())
}
it.filterIsInstance<EffectiveProfileSwitch>().firstOrNull()?.let { eps ->
aapsLogger.debug(LTag.DATABASE, "Firing EventEffectiveProfileSwitchChanged $eps")
rxBus.send(EventEffectiveProfileSwitchChanged(eps))
}
it.filterIsInstance<OfflineEvent>().firstOrNull()?.let { oe ->
aapsLogger.debug(LTag.DATABASE, "Firing EventOfflineChange $oe")
rxBus.send(EventOfflineChange())

View file

@ -7,8 +7,7 @@ import info.nightscout.androidaps.plugins.aps.openAPSAMA.DetermineBasalAdapterAM
import info.nightscout.androidaps.plugins.aps.openAPSAMA.DetermineBasalResultAMA
import info.nightscout.androidaps.plugins.aps.openAPSSMB.DetermineBasalAdapterSMBJS
import info.nightscout.androidaps.plugins.aps.openAPSSMB.DetermineBasalResultSMB
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobOref1Thread
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobThread
import info.nightscout.androidaps.plugins.aps.openAPSSMBDynamicISF.DetermineBasalAdapterSMBDynamicISFJS
@Module
@Suppress("unused")
@ -19,6 +18,5 @@ abstract class APSModule {
@ContributesAndroidInjector abstract fun determineBasalResultAMAInjector(): DetermineBasalResultAMA
@ContributesAndroidInjector abstract fun determineBasalAdapterAMAJSInjector(): DetermineBasalAdapterAMAJS
@ContributesAndroidInjector abstract fun determineBasalAdapterSMBJSInjector(): DetermineBasalAdapterSMBJS
@ContributesAndroidInjector abstract fun iobCobThreadInjector(): IobCobThread
@ContributesAndroidInjector abstract fun iobCobOref1ThreadInjector(): IobCobOref1Thread
@ContributesAndroidInjector abstract fun determineBasalAdapterSMBAutoISFJSInjector(): DetermineBasalAdapterSMBDynamicISFJS
}

View file

@ -12,16 +12,17 @@ import info.nightscout.androidaps.dana.di.DanaModule
import info.nightscout.androidaps.danar.di.DanaRModule
import info.nightscout.androidaps.danars.di.DanaRSModule
import info.nightscout.androidaps.database.DatabaseModule
import info.nightscout.androidaps.dependencyInjection.AutotuneModule
import info.nightscout.androidaps.diaconn.di.DiaconnG8Module
import info.nightscout.androidaps.insight.di.InsightDatabaseModule
import info.nightscout.androidaps.insight.di.InsightModule
import info.nightscout.androidaps.plugin.general.openhumans.dagger.OpenHumansModule
import info.nightscout.androidaps.plugin.general.openhumans.di.OpenHumansModule
import info.nightscout.androidaps.plugins.pump.common.di.PumpCommonModule
import info.nightscout.androidaps.plugins.pump.common.di.RileyLinkModule
import info.nightscout.androidaps.plugins.pump.eopatch.dagger.EopatchModule
import info.nightscout.androidaps.plugins.pump.medtronic.di.MedtronicModule
import info.nightscout.androidaps.plugins.pump.omnipod.dash.dagger.OmnipodDashModule
import info.nightscout.androidaps.plugins.pump.omnipod.eros.dagger.OmnipodErosModule
import info.nightscout.androidaps.plugins.pump.omnipod.dash.di.OmnipodDashModule
import info.nightscout.androidaps.plugins.pump.omnipod.eros.di.OmnipodErosModule
import info.nightscout.shared.di.SharedModule
import javax.inject.Singleton
@ -38,6 +39,7 @@ import javax.inject.Singleton
ReceiversModule::class,
ServicesModule::class,
AutomationModule::class,
AutotuneModule::class,
CommandQueueModule::class,
ObjectivesModule::class,
WizardModule::class,
@ -47,6 +49,7 @@ import javax.inject.Singleton
OmnipodDashModule::class,
OmnipodErosModule::class,
APSModule::class,
WorkflowModule::class,
PreferencesModule::class,
OverviewModule::class,
DataClassesModule::class,

View file

@ -15,6 +15,7 @@ import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin
import info.nightscout.androidaps.plugins.configBuilder.PluginStore
import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctionImplementation
import info.nightscout.androidaps.plugins.general.autotune.AutotunePlugin
import info.nightscout.androidaps.plugins.general.maintenance.ImportExportPrefsImpl
import info.nightscout.androidaps.plugins.general.maintenance.PrefFileListProvider
import info.nightscout.androidaps.plugins.general.nsclient.DataSyncSelectorImplementation
@ -27,11 +28,11 @@ import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.HardLimits
import info.nightscout.androidaps.utils.androidNotification.NotificationHolderImpl
import info.nightscout.androidaps.utils.buildHelper.BuildHelper
import info.nightscout.androidaps.interfaces.BuildHelper
import info.nightscout.androidaps.utils.buildHelper.BuildHelperImpl
import info.nightscout.androidaps.utils.buildHelper.ConfigImpl
import info.nightscout.androidaps.utils.resources.IconsProviderImplementation
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.androidaps.utils.rx.DefaultAapsSchedulers
import info.nightscout.androidaps.utils.storage.FileStorage
@ -98,6 +99,7 @@ open class AppModule {
@Binds fun bindImportExportPrefsInterface(importExportPrefs: ImportExportPrefsImpl): ImportExportPrefs
@Binds fun bindIconsProviderInterface(iconsProvider: IconsProviderImplementation): IconsProvider
@Binds fun bindLoopInterface(loopPlugin: LoopPlugin): Loop
@Binds fun bindAutotuneInterface(autotunePlugin: AutotunePlugin): Autotune
@Binds fun bindIobCobCalculatorInterface(iobCobCalculatorPlugin: IobCobCalculatorPlugin): IobCobCalculator
@Binds fun bindSmsCommunicatorInterface(smsCommunicatorPlugin: SmsCommunicatorPlugin): SmsCommunicator
@Binds fun bindDataSyncSelector(dataSyncSelectorImplementation: DataSyncSelectorImplementation): DataSyncSelector

View file

@ -0,0 +1,23 @@
package info.nightscout.androidaps.dependencyInjection
import dagger.Module
import dagger.android.ContributesAndroidInjector
import info.nightscout.androidaps.plugins.general.autotune.AutotuneCore
import info.nightscout.androidaps.plugins.general.autotune.AutotuneIob
import info.nightscout.androidaps.plugins.general.autotune.AutotunePrep
import info.nightscout.androidaps.plugins.general.autotune.AutotuneFS
import info.nightscout.androidaps.plugins.general.autotune.data.*
@Module
@Suppress("unused")
abstract class AutotuneModule {
@ContributesAndroidInjector abstract fun autoTunePrepInjector(): AutotunePrep
@ContributesAndroidInjector abstract fun autoTuneIobInjector(): AutotuneIob
@ContributesAndroidInjector abstract fun autoTuneCoreInjector(): AutotuneCore
@ContributesAndroidInjector abstract fun autoTuneFSInjector(): AutotuneFS
@ContributesAndroidInjector abstract fun autoTuneATProfileInjector(): ATProfile
@ContributesAndroidInjector abstract fun autoTuneBGDatumInjector(): BGDatum
@ContributesAndroidInjector abstract fun autoTuneCRDatumInjector(): CRDatum
@ContributesAndroidInjector abstract fun autoTunePreppedGlucoseInjector(): PreppedGlucose
}

View file

@ -12,6 +12,7 @@ import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesFragm
import info.nightscout.androidaps.plugins.constraints.objectives.activities.ObjectivesExamDialog
import info.nightscout.androidaps.plugins.general.actions.ActionsFragment
import info.nightscout.androidaps.plugins.general.automation.AutomationFragment
import info.nightscout.androidaps.plugins.general.autotune.AutotuneFragment
import info.nightscout.androidaps.plugins.general.food.FoodFragment
import info.nightscout.androidaps.plugins.general.maintenance.MaintenanceFragment
import info.nightscout.androidaps.plugins.general.nsclient.NSClientFragment
@ -36,6 +37,7 @@ abstract class FragmentsModule {
@ContributesAndroidInjector abstract fun contributesActionsFragment(): ActionsFragment
@ContributesAndroidInjector abstract fun contributesAutomationFragment(): AutomationFragment
@ContributesAndroidInjector abstract fun contributesAutotuneFragment(): AutotuneFragment
@ContributesAndroidInjector abstract fun contributesBGSourceFragment(): BGSourceFragment
@ContributesAndroidInjector abstract fun contributesConfigBuilderFragment(): ConfigBuilderFragment

View file

@ -14,6 +14,7 @@ import info.nightscout.androidaps.plugin.general.openhumans.OpenHumansUploader
import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin
import info.nightscout.androidaps.plugins.aps.openAPSAMA.OpenAPSAMAPlugin
import info.nightscout.androidaps.plugins.aps.openAPSSMB.OpenAPSSMBPlugin
import info.nightscout.androidaps.plugins.aps.openAPSSMBDynamicISF.OpenAPSSMBDynamicISFPlugin
import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin
import info.nightscout.androidaps.plugins.constraints.bgQualityCheck.BgQualityCheckPlugin
import info.nightscout.androidaps.plugins.constraints.dstHelper.DstHelperPlugin
@ -24,6 +25,7 @@ import info.nightscout.androidaps.plugins.constraints.storage.StorageConstraintP
import info.nightscout.androidaps.plugins.constraints.versionChecker.VersionCheckerPlugin
import info.nightscout.androidaps.plugins.general.actions.ActionsPlugin
import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin
import info.nightscout.androidaps.plugins.general.autotune.AutotunePlugin
import info.nightscout.androidaps.plugins.general.dataBroadcaster.DataBroadcastPlugin
import info.nightscout.androidaps.plugins.general.food.FoodPlugin
import info.nightscout.androidaps.plugins.general.maintenance.MaintenancePlugin
@ -31,6 +33,7 @@ import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin
import info.nightscout.androidaps.plugins.general.overview.OverviewPlugin
import info.nightscout.androidaps.plugins.general.persistentNotification.PersistentNotificationPlugin
import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin
import info.nightscout.androidaps.plugins.general.themes.ThemeSwitcherPlugin
import info.nightscout.androidaps.plugins.general.wear.WearPlugin
import info.nightscout.androidaps.plugins.general.xdripStatusline.StatusLinePlugin
import info.nightscout.androidaps.plugins.insulin.InsulinLyumjevPlugin
@ -42,7 +45,6 @@ import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin
import info.nightscout.androidaps.plugins.pump.combo.ComboPlugin
import info.nightscout.androidaps.plugins.pump.eopatch.EopatchPumpPlugin
import info.nightscout.androidaps.plugins.pump.insight.LocalInsightPlugin
import info.nightscout.androidaps.plugins.pump.mdi.MDIPlugin
import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin
import info.nightscout.androidaps.plugins.pump.omnipod.dash.OmnipodDashPumpPlugin
import info.nightscout.androidaps.plugins.pump.omnipod.eros.OmnipodErosPumpPlugin
@ -189,12 +191,6 @@ abstract class PluginsModule {
@IntKey(156)
abstract fun bindEopatchPumpPlugin(plugin: EopatchPumpPlugin): PluginBase
@Binds
@NotNSClient
@IntoMap
@IntKey(160)
abstract fun bindMDIPlugin(plugin: MDIPlugin): PluginBase
@Binds
@AllConfigs
@IntoMap
@ -219,6 +215,12 @@ abstract class PluginsModule {
@IntKey(220)
abstract fun bindOpenAPSSMBPlugin(plugin: OpenAPSSMBPlugin): PluginBase
@Binds
@APS
@IntoMap
@IntKey(222)
abstract fun bindOpenAPSSMBAutoISFPlugin(plugin: OpenAPSSMBDynamicISFPlugin): PluginBase
@Binds
@AllConfigs
@IntoMap
@ -231,6 +233,12 @@ abstract class PluginsModule {
@IntKey(250)
abstract fun bindAutomationPlugin(plugin: AutomationPlugin): PluginBase
@Binds
@AllConfigs
@IntoMap
@IntKey(255)
abstract fun bindAutotunePlugin(plugin: AutotunePlugin): PluginBase
@Binds
@AllConfigs
@IntoMap
@ -357,6 +365,12 @@ abstract class PluginsModule {
@IntKey(460)
abstract fun bindTomatoPlugin(plugin: TomatoPlugin): PluginBase
@Binds
@AllConfigs
@IntoMap
@IntKey(465)
abstract fun bindAidexPlugin(plugin: AidexPlugin): PluginBase
@Binds
@AllConfigs
@IntoMap
@ -387,6 +401,12 @@ abstract class PluginsModule {
@IntKey(490)
abstract fun bindConfigBuilderPlugin(plugin: ConfigBuilderPlugin): PluginBase
@Binds
@AllConfigs
@IntoMap
@IntKey(500)
abstract fun bindThemeSwitcherPlugin(plugin: ThemeSwitcherPlugin): PluginBase
@Qualifier
annotation class AllConfigs

View file

@ -2,12 +2,11 @@ package info.nightscout.androidaps.di
import dagger.Module
import dagger.android.ContributesAndroidInjector
import info.nightscout.androidaps.plugins.aps.loop.CarbSuggestionReceiver
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkBluetoothStateReceiver
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkBroadcastReceiver
import info.nightscout.androidaps.plugins.aps.loop.CarbSuggestionReceiver
import info.nightscout.androidaps.receivers.*
@Module
@Suppress("unused")
abstract class ReceiversModule {
@ -16,8 +15,7 @@ abstract class ReceiversModule {
@ContributesAndroidInjector abstract fun contributesBTReceiver(): BTReceiver
@ContributesAndroidInjector abstract fun contributesChargingStateReceiver(): ChargingStateReceiver
@ContributesAndroidInjector abstract fun contributesDataReceiver(): DataReceiver
@ContributesAndroidInjector abstract fun contributesKeepAliveReceiver(): KeepAliveReceiver
@ContributesAndroidInjector abstract fun contributesKeepAliveWorker(): KeepAliveReceiver.KeepAliveWorker
@ContributesAndroidInjector abstract fun contributesKeepAliveWorker(): KeepAliveWorker
@ContributesAndroidInjector abstract fun contributesRileyLinkBluetoothStateReceiver(): RileyLinkBluetoothStateReceiver
@ContributesAndroidInjector abstract fun contributesSmsReceiver(): SmsReceiver
@ContributesAndroidInjector abstract fun contributesTimeDateOrTZChangeReceiver(): TimeDateOrTZChangeReceiver

View file

@ -5,7 +5,7 @@ import dagger.android.ContributesAndroidInjector
import info.nightscout.androidaps.plugins.general.nsclient.services.NSClientService
import info.nightscout.androidaps.plugins.general.overview.notifications.DismissNotificationService
import info.nightscout.androidaps.plugins.general.persistentNotification.DummyService
import info.nightscout.androidaps.plugins.general.wear.wearintegration.WatchUpdaterService
import info.nightscout.androidaps.plugins.general.wear.wearintegration.DataLayerListenerServiceMobile
import info.nightscout.androidaps.services.AlarmSoundService
import info.nightscout.androidaps.services.LocationService
@ -18,5 +18,5 @@ abstract class ServicesModule {
@ContributesAndroidInjector abstract fun contributesDummyService(): DummyService
@ContributesAndroidInjector abstract fun contributesLocationService(): LocationService
@ContributesAndroidInjector abstract fun contributesNSClientService(): NSClientService
@ContributesAndroidInjector abstract fun contributesWatchUpdaterService(): WatchUpdaterService
@ContributesAndroidInjector abstract fun contributesWatchUpdaterService(): DataLayerListenerServiceMobile
}

View file

@ -2,10 +2,16 @@ package info.nightscout.androidaps.di
import dagger.Module
import dagger.android.ContributesAndroidInjector
import info.nightscout.androidaps.widget.WidgetConfigureActivity
import info.nightscout.androidaps.skins.SkinListPreference
import info.nightscout.androidaps.widget.Widget
@Module
@Suppress("unused")
abstract class UIModule {
@ContributesAndroidInjector abstract fun skinListPreferenceInjector(): SkinListPreference
@ContributesAndroidInjector abstract fun aapsWidgetInjector(): Widget
@ContributesAndroidInjector abstract fun contributesWidgetConfigureActivity(): WidgetConfigureActivity
}

View file

@ -32,4 +32,5 @@ abstract class WorkersModule {
@ContributesAndroidInjector abstract fun contributesNSClientMbgWorker(): NSClientMbgWorker
@ContributesAndroidInjector abstract fun contributesFoodWorker(): FoodPlugin.FoodWorker
@ContributesAndroidInjector abstract fun contributesCsvExportWorker(): ImportExportPrefsImpl.CsvExportWorker
@ContributesAndroidInjector abstract fun contributesAidexWorker(): AidexPlugin.AidexWorker
}

View file

@ -0,0 +1,25 @@
package info.nightscout.androidaps.di
import dagger.Module
import dagger.android.ContributesAndroidInjector
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.*
import info.nightscout.androidaps.workflow.*
@Module
@Suppress("unused")
abstract class WorkflowModule {
@ContributesAndroidInjector abstract fun iobCobWorkerInjector(): IobCobOrefWorker
@ContributesAndroidInjector abstract fun iobCobOref1WorkerInjector(): IobCobOref1Worker
@ContributesAndroidInjector abstract fun prepareIobAutosensDataWorkerInjector(): PrepareIobAutosensGraphDataWorker
@ContributesAndroidInjector abstract fun prepareBasalDataWorkerInjector(): PrepareBasalDataWorker
@ContributesAndroidInjector abstract fun prepareTemporaryTargetDataWorkerInjector(): PrepareTemporaryTargetDataWorker
@ContributesAndroidInjector abstract fun prepareTreatmentsDataWorkerInjector(): PrepareTreatmentsDataWorker
@ContributesAndroidInjector abstract fun loadIobCobResultsWorkerInjector(): UpdateIobCobSensWorker
@ContributesAndroidInjector abstract fun preparePredictionsWorkerInjector(): PreparePredictionsWorker
@ContributesAndroidInjector abstract fun updateGraphAndIobWorkerInjector(): UpdateGraphWorker
@ContributesAndroidInjector abstract fun prepareBgDataWorkerInjector(): PrepareBgDataWorker
@ContributesAndroidInjector abstract fun prepareBucketedDataWorkerInjector(): PrepareBucketedDataWorker
@ContributesAndroidInjector abstract fun loadBgDataWorkerInjector(): LoadBgDataWorker
@ContributesAndroidInjector abstract fun invokeLoopWorkerInjector(): InvokeLoopWorker
}

View file

@ -19,7 +19,7 @@ import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProv
import info.nightscout.androidaps.utils.HtmlHelper
import info.nightscout.androidaps.utils.XDripBroadcast
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.interfaces.ResourceHelper
import java.text.DecimalFormat
import java.util.*
import javax.inject.Inject
@ -64,6 +64,7 @@ class CalibrationDialog : DialogFragmentWithDate() {
binding.bg.setParams(savedInstanceState?.getDouble("bg")
?: bg, 36.0, 500.0, 1.0, DecimalFormat("0"), false, binding.okcancel.ok)
binding.units.text = if (units == GlucoseUnit.MMOL) rh.gs(R.string.mmol) else rh.gs(R.string.mgdl)
binding.bgLabel.labelFor = binding.bg.editTextId
}
override fun onDestroyView() {

View file

@ -27,9 +27,11 @@ import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProv
import info.nightscout.androidaps.queue.Callback
import info.nightscout.androidaps.utils.*
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.resources.ResourceHelper
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import info.nightscout.androidaps.utils.protection.ProtectionCheck
import info.nightscout.androidaps.utils.protection.ProtectionCheck.Protection.BOLUS
import info.nightscout.androidaps.interfaces.ResourceHelper
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import java.text.DecimalFormat
import java.util.*
import java.util.concurrent.TimeUnit
@ -50,14 +52,16 @@ class CarbsDialog : DialogFragmentWithDate() {
@Inject lateinit var bolusTimer: BolusTimer
@Inject lateinit var commandQueue: CommandQueue
@Inject lateinit var repository: AppRepository
@Inject lateinit var protectionCheck: ProtectionCheck
companion object {
private const val FAV1_DEFAULT = 5
private const val FAV2_DEFAULT = 10
private const val FAV3_DEFAULT = 20
const val FAV1_DEFAULT = 5
const val FAV2_DEFAULT = 10
const val FAV3_DEFAULT = 20
}
private var queryingProtection = false
private val disposable = CompositeDisposable()
private val textWatcher: TextWatcher = object : TextWatcher {
@ -72,7 +76,7 @@ class CarbsDialog : DialogFragmentWithDate() {
private fun validateInputs() {
val maxCarbs = constraintChecker.getMaxCarbsAllowed().value().toDouble()
val time = binding.time.value.toInt()
if (time > 12 * 60 || time < -12 * 60) {
if (time > 12 * 60 || time < -7 * 24 * 60) {
binding.time.value = 0.0
ToastUtils.showToastInUiThread(ctx, rh.gs(R.string.constraintapllied))
}
@ -125,7 +129,7 @@ class CarbsDialog : DialogFragmentWithDate() {
val maxCarbs = constraintChecker.getMaxCarbsAllowed().value().toDouble()
binding.time.setParams(
savedInstanceState?.getDouble("time")
?: 0.0, -12 * 60.0, 12 * 60.0, 5.0, DecimalFormat("0"), false, binding.okcancel.ok, textWatcher
?: 0.0, -7 * 24 * 60.0, 12 * 60.0, 5.0, DecimalFormat("0"), false, binding.okcancel.ok, textWatcher
)
binding.duration.setParams(
@ -137,38 +141,45 @@ class CarbsDialog : DialogFragmentWithDate() {
savedInstanceState?.getDouble("carbs")
?: 0.0, 0.0, maxCarbs, 1.0, DecimalFormat("0"), false, binding.okcancel.ok, textWatcher
)
binding.plus1.text = toSignedString(sp.getInt(R.string.key_carbs_button_increment_1, FAV1_DEFAULT))
val plus1text = toSignedString(sp.getInt(R.string.key_carbs_button_increment_1, FAV1_DEFAULT))
binding.plus1.text = plus1text
binding.plus1.contentDescription = rh.gs(R.string.treatments_wizard_carbs_label) + " " + plus1text
binding.plus1.setOnClickListener {
binding.carbs.value = max(
0.0, binding.carbs.value
+ sp.getInt(R.string.key_carbs_button_increment_1, FAV1_DEFAULT)
)
validateInputs()
binding.carbs.announceValue()
}
binding.plus2.text = toSignedString(sp.getInt(R.string.key_carbs_button_increment_2, FAV2_DEFAULT))
val plus2text = toSignedString(sp.getInt(R.string.key_carbs_button_increment_2, FAV2_DEFAULT))
binding.plus2.text = plus2text
binding.plus2.contentDescription = rh.gs(R.string.treatments_wizard_carbs_label) + " " + plus2text
binding.plus2.setOnClickListener {
binding.carbs.value = max(
0.0, binding.carbs.value
+ sp.getInt(R.string.key_carbs_button_increment_2, FAV2_DEFAULT)
)
validateInputs()
binding.carbs.announceValue()
}
binding.plus3.text = toSignedString(sp.getInt(R.string.key_carbs_button_increment_3, FAV3_DEFAULT))
val plus3text = toSignedString(sp.getInt(R.string.key_carbs_button_increment_3, FAV3_DEFAULT))
binding.plus3.text = plus3text
binding.plus2.contentDescription = rh.gs(R.string.treatments_wizard_carbs_label) + " " + plus3text
binding.plus3.setOnClickListener {
binding.carbs.value = max(
0.0, binding.carbs.value
+ sp.getInt(R.string.key_carbs_button_increment_3, FAV3_DEFAULT)
)
validateInputs()
binding.carbs.announceValue()
}
setOnValueChangedListener { eventTime: Long ->
run {
val timeOffset = ((eventTime - eventTimeOriginal) / (1000 * 60)).toDouble()
binding.time.value = timeOffset
if (_binding != null) binding.time.value = timeOffset
}
}
@ -188,6 +199,9 @@ class CarbsDialog : DialogFragmentWithDate() {
binding.hypoTt.isChecked = false
binding.activityTt.isChecked = false
}
binding.durationLabel.labelFor = binding.duration.editTextId
binding.timeLabel.labelFor = binding.time.editTextId
binding.carbsLabel.labelFor = binding.carbs.editTextId
}
override fun onDestroyView() {
@ -220,8 +234,9 @@ class CarbsDialog : DialogFragmentWithDate() {
if (activitySelected)
actions.add(
rh.gs(R.string.temptargetshort) + ": " + (DecimalFormatter.to1Decimal(activityTT) + " " + unitLabel + " (" + rh.gs(R.string.format_mins, activityTTDuration) + ")").formatColor(
context,
rh,
R.color.tempTargetConfirmation
R.attr.tempTargetConfirmation
)
)
val eatingSoonSelected = binding.eatingSoonTt.isChecked
@ -230,27 +245,28 @@ class CarbsDialog : DialogFragmentWithDate() {
rh.gs(R.string.temptargetshort) + ": " + (DecimalFormatter.to1Decimal(eatingSoonTT) + " " + unitLabel + " (" + rh.gs(
R.string.format_mins,
eatingSoonTTDuration
) + ")").formatColor(rh, R.color.tempTargetConfirmation)
) + ")").formatColor(context, rh, R.attr.tempTargetConfirmation)
)
val hypoSelected = binding.hypoTt.isChecked
if (hypoSelected)
actions.add(
rh.gs(R.string.temptargetshort) + ": " + (DecimalFormatter.to1Decimal(hypoTT) + " " + unitLabel + " (" + rh.gs(R.string.format_mins, hypoTTDuration) + ")").formatColor(
context,
rh,
R.color.tempTargetConfirmation
R.attr.tempTargetConfirmation
)
)
val timeOffset = binding.time.value.toInt()
if (useAlarm && carbs > 0 && timeOffset > 0)
actions.add(rh.gs(R.string.alarminxmin, timeOffset).formatColor(rh, R.color.info))
actions.add(rh.gs(R.string.alarminxmin, timeOffset).formatColor(context, rh, R.attr.infoColor))
val duration = binding.duration.value.toInt()
if (duration > 0)
actions.add(rh.gs(R.string.duration) + ": " + duration + rh.gs(R.string.shorthour))
if (carbsAfterConstraints > 0) {
actions.add(rh.gs(R.string.carbs) + ": " + "<font color='" + rh.gc(R.color.carbs) + "'>" + rh.gs(R.string.format_carbs, carbsAfterConstraints) + "</font>")
actions.add(rh.gs(R.string.carbs) + ": " + "<font color='" + rh.gac(context, R.attr.carbsColor) + "'>" + rh.gs(R.string.format_carbs, carbsAfterConstraints) + "</font>")
if (carbsAfterConstraints != carbs)
actions.add("<font color='" + rh.gc(R.color.warning) + "'>" + rh.gs(R.string.carbsconstraintapplied) + "</font>")
actions.add("<font color='" + rh.gac(context, R.attr.warningColor) + "'>" + rh.gs(R.string.carbsconstraintapplied) + "</font>")
}
val notes = binding.notesLayout.notes.text.toString()
if (notes.isNotEmpty())
@ -367,4 +383,20 @@ class CarbsDialog : DialogFragmentWithDate() {
}
return true
}
}
override fun onResume() {
super.onResume()
if (!queryingProtection) {
queryingProtection = true
activity?.let { activity ->
val cancelFail = {
queryingProtection = false
aapsLogger.debug(LTag.APS, "Dialog canceled on resume protection: ${this.javaClass.name}")
ToastUtils.showToastInUiThread(ctx, R.string.dialog_canceled)
dismiss()
}
protectionCheck.queryProtection(activity, BOLUS, { queryingProtection = false }, cancelFail, cancelFail)
}
}
}
}

View file

@ -30,9 +30,9 @@ import info.nightscout.androidaps.utils.Translator
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.extensions.fromConstant
import info.nightscout.androidaps.interfaces.GlucoseUnit
import info.nightscout.androidaps.utils.resources.ResourceHelper
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import info.nightscout.androidaps.interfaces.ResourceHelper
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import java.text.DecimalFormat
import java.util.*
import javax.inject.Inject
@ -165,6 +165,8 @@ class CareDialog : DialogFragmentWithDate() {
?: 0.0, 0.0, Constants.MAX_PROFILE_SWITCH_DURATION, 10.0, DecimalFormat("0"), false, binding.okcancel.ok)
if (options == EventType.NOTE || options == EventType.QUESTION || options == EventType.ANNOUNCEMENT || options == EventType.EXERCISE)
binding.notesLayout.root.visibility = View.VISIBLE // independent to preferences
binding.bgLabel.labelFor = binding.bg.editTextId
binding.durationLabel.labelFor = binding.duration.editTextId
}
override fun onDestroyView() {

View file

@ -22,7 +22,11 @@ import info.nightscout.androidaps.utils.HtmlHelper
import info.nightscout.shared.SafeParse
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.extensions.formatColor
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.ToastUtils
import info.nightscout.androidaps.utils.protection.ProtectionCheck
import info.nightscout.androidaps.utils.protection.ProtectionCheck.Protection.BOLUS
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.logging.LTag
import java.text.DecimalFormat
import java.util.*
import javax.inject.Inject
@ -36,11 +40,12 @@ class ExtendedBolusDialog : DialogFragmentWithDate() {
@Inject lateinit var commandQueue: CommandQueue
@Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var uel: UserEntryLogger
@Inject lateinit var protectionCheck: ProtectionCheck
private var queryingProtection = false
private var _binding: DialogExtendedbolusBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
// This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!!
override fun onSaveInstanceState(savedInstanceState: Bundle) {
@ -70,6 +75,8 @@ class ExtendedBolusDialog : DialogFragmentWithDate() {
val extendedMaxDuration = pumpDescription.extendedBolusMaxDuration
binding.duration.setParams(savedInstanceState?.getDouble("duration")
?: extendedDurationStep, extendedDurationStep, extendedMaxDuration, extendedDurationStep, DecimalFormat("0"), false, binding.okcancel.ok)
binding.insulinLabel.labelFor = binding.insulin.editTextId
binding.durationLabel.labelFor = binding.duration.editTextId
}
override fun onDestroyView() {
@ -86,7 +93,7 @@ class ExtendedBolusDialog : DialogFragmentWithDate() {
actions.add(rh.gs(R.string.formatinsulinunits, insulinAfterConstraint))
actions.add(rh.gs(R.string.duration) + ": " + rh.gs(R.string.format_mins, durationInMinutes))
if (abs(insulinAfterConstraint - insulin) > 0.01)
actions.add(rh.gs(R.string.constraintapllied).formatColor(rh, R.color.warning))
actions.add(rh.gs(R.string.constraintapllied).formatColor(context, rh, R.attr.warningColor))
activity?.let { activity ->
OKDialog.showConfirmation(activity, rh.gs(R.string.extended_bolus), HtmlHelper.fromHtml(Joiner.on("<br/>").join(actions)), {
@ -104,4 +111,20 @@ class ExtendedBolusDialog : DialogFragmentWithDate() {
}
return true
}
}
override fun onResume() {
super.onResume()
if(!queryingProtection) {
queryingProtection = true
activity?.let { activity ->
val cancelFail = {
queryingProtection = false
aapsLogger.debug(LTag.APS, "Dialog canceled on resume protection: ${this.javaClass.name}")
ToastUtils.showToastInUiThread(ctx, R.string.dialog_canceled)
dismiss()
}
protectionCheck.queryProtection(activity, BOLUS, { queryingProtection = false }, cancelFail, cancelFail)
}
}
}
}

View file

@ -28,9 +28,12 @@ import info.nightscout.androidaps.utils.HtmlHelper
import info.nightscout.shared.SafeParse
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.extensions.formatColor
import info.nightscout.androidaps.utils.resources.ResourceHelper
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import info.nightscout.androidaps.utils.ToastUtils
import info.nightscout.androidaps.utils.protection.ProtectionCheck
import info.nightscout.androidaps.utils.protection.ProtectionCheck.Protection.BOLUS
import info.nightscout.androidaps.interfaces.ResourceHelper
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import java.util.*
import javax.inject.Inject
import kotlin.math.abs
@ -44,13 +47,13 @@ class FillDialog : DialogFragmentWithDate() {
@Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var uel: UserEntryLogger
@Inject lateinit var repository: AppRepository
@Inject lateinit var protectionCheck: ProtectionCheck
private var queryingProtection = false
private val disposable = CompositeDisposable()
private var _binding: DialogFillBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
// This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!!
override fun onSaveInstanceState(savedInstanceState: Bundle) {
@ -96,7 +99,7 @@ class FillDialog : DialogFragmentWithDate() {
} else {
binding.fillPresetButton3.visibility = View.GONE
}
binding.fillLabel.labelFor = binding.fillInsulinamount.editTextId
}
override fun onDestroyView() {
@ -113,16 +116,16 @@ class FillDialog : DialogFragmentWithDate() {
if (insulinAfterConstraints > 0) {
actions.add(rh.gs(R.string.fillwarning))
actions.add("")
actions.add(rh.gs(R.string.bolus) + ": " + DecimalFormatter.toPumpSupportedBolus(insulinAfterConstraints, activePlugin.activePump, rh).formatColor(rh, R.color.colorInsulinButton))
actions.add(rh.gs(R.string.bolus) + ": " + DecimalFormatter.toPumpSupportedBolus(insulinAfterConstraints, activePlugin.activePump, rh).formatColor(context, rh, R.attr.insulinButtonColor))
if (abs(insulinAfterConstraints - insulin) > 0.01)
actions.add(rh.gs(R.string.bolusconstraintappliedwarn, insulin, insulinAfterConstraints).formatColor(rh, R.color.warning))
actions.add(rh.gs(R.string.bolusconstraintappliedwarn, insulin, insulinAfterConstraints).formatColor(context, rh, R.attr.warningColor))
}
val siteChange = binding.fillCatheterChange.isChecked
if (siteChange)
actions.add(rh.gs(R.string.record_pump_site_change).formatColor(rh, R.color.actionsConfirm))
actions.add(rh.gs(R.string.record_pump_site_change).formatColor(context, rh, R.attr.actionsConfirmColor))
val insulinChange = binding.fillCartridgeChange.isChecked
if (insulinChange)
actions.add(rh.gs(R.string.record_insulin_cartridge_change).formatColor(rh, R.color.actionsConfirm))
actions.add(rh.gs(R.string.record_insulin_cartridge_change).formatColor(context, rh, R.attr.actionsConfirmColor))
val notes: String = binding.notesLayout.notes.text.toString()
if (notes.isNotEmpty())
actions.add(rh.gs(R.string.notes_label) + ": " + notes)
@ -196,4 +199,20 @@ class FillDialog : DialogFragmentWithDate() {
}
})
}
override fun onResume() {
super.onResume()
if(!queryingProtection) {
queryingProtection = true
activity?.let { activity ->
val cancelFail = {
queryingProtection = false
aapsLogger.debug(LTag.APS, "Dialog canceled on resume protection: ${this.javaClass.name}")
ToastUtils.showToastInUiThread(ctx, R.string.dialog_canceled)
dismiss()
}
protectionCheck.queryProtection(activity, BOLUS, { queryingProtection = false }, cancelFail, cancelFail)
}
}
}
}

View file

@ -28,10 +28,12 @@ import info.nightscout.androidaps.queue.Callback
import info.nightscout.androidaps.utils.*
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.extensions.toSignedString
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.protection.ProtectionCheck
import info.nightscout.androidaps.utils.protection.ProtectionCheck.Protection.BOLUS
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.SafeParse
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import java.text.DecimalFormat
import java.util.*
import java.util.concurrent.TimeUnit
@ -52,15 +54,21 @@ class InsulinDialog : DialogFragmentWithDate() {
@Inject lateinit var config: Config
@Inject lateinit var bolusTimer: BolusTimer
@Inject lateinit var uel: UserEntryLogger
@Inject lateinit var protectionCheck: ProtectionCheck
companion object {
private const val PLUS1_DEFAULT = 0.5
private const val PLUS2_DEFAULT = 1.0
private const val PLUS3_DEFAULT = 2.0
const val PLUS1_DEFAULT = 0.5
const val PLUS2_DEFAULT = 1.0
const val PLUS3_DEFAULT = 2.0
}
private var queryingProtection = false
private val disposable = CompositeDisposable()
private var _binding: DialogInsulinBinding? = null
// This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!!
private val textWatcher: TextWatcher = object : TextWatcher {
override fun afterTextChanged(s: Editable) {
@ -71,12 +79,6 @@ class InsulinDialog : DialogFragmentWithDate() {
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
}
private var _binding: DialogInsulinBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
private fun validateInputs() {
val maxInsulin = constraintChecker.getMaxBolusAllowed().value()
if (abs(binding.time.value.toInt()) > 12 * 60) {
@ -116,29 +118,40 @@ class InsulinDialog : DialogFragmentWithDate() {
binding.amount.setParams(savedInstanceState?.getDouble("amount")
?: 0.0, 0.0, maxInsulin, activePlugin.activePump.pumpDescription.bolusStep, DecimalFormatter.pumpSupportedBolusFormat(activePlugin.activePump), false, binding.okcancel.ok, textWatcher)
binding.plus05.text = sp.getDouble(rh.gs(R.string.key_insulin_button_increment_1), PLUS1_DEFAULT).toSignedString(activePlugin.activePump)
val plus05Text = sp.getDouble(rh.gs(R.string.key_insulin_button_increment_1), PLUS1_DEFAULT).toSignedString(activePlugin.activePump)
binding.plus05.text = plus05Text
binding.plus05.contentDescription = rh.gs(R.string.overview_insulin_label) + " " + plus05Text
binding.plus05.setOnClickListener {
binding.amount.value = max(0.0, binding.amount.value
+ sp.getDouble(rh.gs(R.string.key_insulin_button_increment_1), PLUS1_DEFAULT))
validateInputs()
binding.amount.announceValue()
}
binding.plus10.text = sp.getDouble(rh.gs(R.string.key_insulin_button_increment_2), PLUS2_DEFAULT).toSignedString(activePlugin.activePump)
val plus10Text = sp.getDouble(rh.gs(R.string.key_insulin_button_increment_2), PLUS2_DEFAULT).toSignedString(activePlugin.activePump)
binding.plus10.text = plus10Text
binding.plus10.contentDescription = rh.gs(R.string.overview_insulin_label) + " " + plus10Text
binding.plus10.setOnClickListener {
binding.amount.value = max(0.0, binding.amount.value
+ sp.getDouble(rh.gs(R.string.key_insulin_button_increment_2), PLUS2_DEFAULT))
validateInputs()
binding.amount.announceValue()
}
binding.plus20.text = sp.getDouble(rh.gs(R.string.key_insulin_button_increment_3), PLUS3_DEFAULT).toSignedString(activePlugin.activePump)
val plus20Text = sp.getDouble(rh.gs(R.string.key_insulin_button_increment_3), PLUS3_DEFAULT).toSignedString(activePlugin.activePump)
binding.plus20.text = plus20Text
binding.plus20.contentDescription = rh.gs(R.string.overview_insulin_label) + " " + plus20Text
binding.plus20.setOnClickListener {
binding.amount.value = max(0.0, binding.amount.value
+ sp.getDouble(rh.gs(R.string.key_insulin_button_increment_3), PLUS3_DEFAULT))
validateInputs()
binding.amount.announceValue()
}
binding.timeLayout.visibility = View.GONE
binding.recordOnly.setOnCheckedChangeListener { _, isChecked: Boolean ->
binding.timeLayout.visibility = isChecked.toVisibility()
}
binding.insulinLabel.labelFor = binding.amount.editTextId
binding.timeLabel.labelFor = binding.time.editTextId
}
override fun onDestroyView() {
@ -159,16 +172,17 @@ class InsulinDialog : DialogFragmentWithDate() {
val eatingSoonChecked = binding.startEatingSoonTt.isChecked
if (insulinAfterConstraints > 0) {
actions.add(rh.gs(R.string.bolus) + ": " + DecimalFormatter.toPumpSupportedBolus(insulinAfterConstraints, activePlugin.activePump, rh).formatColor(rh, R.color.bolus))
actions.add(rh.gs(R.string.bolus) + ": " + DecimalFormatter.toPumpSupportedBolus(insulinAfterConstraints, activePlugin.activePump, rh).formatColor(context, rh, R.attr.bolusColor))
if (recordOnlyChecked)
actions.add(rh.gs(R.string.bolusrecordedonly).formatColor(rh, R.color.warning))
actions.add(rh.gs(R.string.bolusrecordedonly).formatColor(context, rh, R.attr.warningColor))
if (abs(insulinAfterConstraints - insulin) > pumpDescription.pumpType.determineCorrectBolusStepSize(insulinAfterConstraints))
actions.add(rh.gs(R.string.bolusconstraintappliedwarn, insulin, insulinAfterConstraints).formatColor(rh, R.color.warning))
actions.add(rh.gs(R.string.bolusconstraintappliedwarn, insulin, insulinAfterConstraints).formatColor(context, rh, R.attr.warningColor))
}
val eatingSoonTTDuration = defaultValueHelper.determineEatingSoonTTDuration()
val eatingSoonTT = defaultValueHelper.determineEatingSoonTT()
if (eatingSoonChecked)
actions.add(rh.gs(R.string.temptargetshort) + ": " + (DecimalFormatter.to1Decimal(eatingSoonTT) + " " + unitLabel + " (" + rh.gs(R.string.format_mins, eatingSoonTTDuration) + ")").formatColor(rh, R.color.tempTargetConfirmation))
actions.add(rh.gs(R.string.temptargetshort) + ": " + (DecimalFormatter.to1Decimal(eatingSoonTT) + " " + unitLabel + " (" + rh.gs(R.string.format_mins, eatingSoonTTDuration) + ")")
.formatColor(context, rh, R.attr.tempTargetConfirmation))
val timeOffset = binding.time.value.toInt()
val time = dateUtil.now() + T.mins(timeOffset.toLong()).msecs()
@ -244,4 +258,20 @@ class InsulinDialog : DialogFragmentWithDate() {
}
return true
}
}
override fun onResume() {
super.onResume()
if(!queryingProtection) {
queryingProtection = true
activity?.let { activity ->
val cancelFail = {
queryingProtection = false
aapsLogger.debug(LTag.APS, "Dialog canceled on resume protection: ${this.javaClass.name}")
ToastUtils.showToastInUiThread(ctx, R.string.dialog_canceled)
dismiss()
}
protectionCheck.queryProtection(activity, BOLUS, { queryingProtection = false }, cancelFail, cancelFail)
}
}
}
}

View file

@ -38,10 +38,12 @@ import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.ToastUtils
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.protection.ProtectionCheck
import info.nightscout.androidaps.utils.protection.ProtectionCheck.Protection.BOLUS
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import javax.inject.Inject
class LoopDialog : DaggerDialogFragment() {
@ -62,14 +64,15 @@ class LoopDialog : DaggerDialogFragment() {
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var repository: AppRepository
@Inject lateinit var objectivePlugin: ObjectivesPlugin
@Inject lateinit var protectionCheck: ProtectionCheck
private var queryingProtection = false
private var showOkCancel: Boolean = true
private var _binding: DialogLoopBinding? = null
private var handler = Handler(HandlerThread(this::class.simpleName + "Handler").also { it.start() }.looper)
private lateinit var refreshDialog: Runnable
// This property is only valid between onCreateView and
// onDestroyView.
// This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!!
val disposable = CompositeDisposable()
@ -437,4 +440,20 @@ class LoopDialog : DaggerDialogFragment() {
aapsLogger.debug(e.localizedMessage ?: e.toString())
}
}
override fun onResume() {
super.onResume()
if(!queryingProtection) {
queryingProtection = true
activity?.let { activity ->
val cancelFail = {
queryingProtection = false
aapsLogger.debug(LTag.APS, "Dialog canceled on resume protection: ${this.javaClass.name}")
ToastUtils.showToastInUiThread(ctx, R.string.dialog_canceled)
dismiss()
}
protectionCheck.queryProtection(activity, BOLUS, { queryingProtection = false }, cancelFail, cancelFail)
}
}
}
}

View file

@ -1,5 +1,6 @@
package info.nightscout.androidaps.dialogs
import android.content.Context
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
@ -30,10 +31,13 @@ import info.nightscout.androidaps.utils.DefaultValueHelper
import info.nightscout.androidaps.utils.HardLimits
import info.nightscout.androidaps.utils.HtmlHelper
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.ToastUtils
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.resources.ResourceHelper
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import info.nightscout.androidaps.utils.protection.ProtectionCheck
import info.nightscout.androidaps.utils.protection.ProtectionCheck.Protection.BOLUS
import info.nightscout.androidaps.interfaces.ResourceHelper
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import java.text.DecimalFormat
import java.util.*
import java.util.concurrent.TimeUnit
@ -51,15 +55,15 @@ class ProfileSwitchDialog : DialogFragmentWithDate() {
@Inject lateinit var hardLimits: HardLimits
@Inject lateinit var rxBus: RxBus
@Inject lateinit var defaultValueHelper: DefaultValueHelper
@Inject lateinit var ctx: Context
@Inject lateinit var protectionCheck: ProtectionCheck
private var profileIndex: Int? = null
private var queryingProtection = false
private var profileName: String? = null
private val disposable = CompositeDisposable()
private var _binding: DialogProfileswitchBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
// This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!!
private val textWatcher: TextWatcher = object : TextWatcher {
@ -86,7 +90,7 @@ class ProfileSwitchDialog : DialogFragmentWithDate() {
): View {
onCreateViewGeneral()
arguments?.let { bundle ->
profileIndex = bundle.getInt("profileIndex", 0)
profileName = bundle.getString("profileName", null)
}
_binding = DialogProfileswitchBinding.inflate(inflater, container, false)
return binding.root
@ -112,8 +116,7 @@ class ProfileSwitchDialog : DialogFragmentWithDate() {
// profile
context?.let { context ->
val profileStore = activePlugin.activeProfileSource.profile
?: return
val profileStore = activePlugin.activeProfileSource.profile ?: return
val profileListToCheck = profileStore.getProfileList()
val profileList = ArrayList<CharSequence>()
for (profileName in profileListToCheck) {
@ -125,15 +128,16 @@ class ProfileSwitchDialog : DialogFragmentWithDate() {
dismiss()
return
}
val adapter = ArrayAdapter(context, R.layout.spinner_centered, profileList)
binding.profile.adapter = adapter
binding.profileList.setAdapter(ArrayAdapter(context, R.layout.spinner_centered, profileList))
// set selected to actual profile
if (profileIndex != null)
binding.profile.setSelection(profileIndex as Int)
else
if (profileName != null)
binding.profileList.setText(profileName, false)
else {
binding.profileList.setText(profileList[0], false)
for (p in profileList.indices)
if (profileList[p] == profileFunction.getOriginalProfileName())
binding.profile.setSelection(p)
binding.profileList.setText(profileList[p], false)
}
}
profileFunction.getProfile()?.let { profile ->
@ -143,11 +147,14 @@ class ProfileSwitchDialog : DialogFragmentWithDate() {
binding.reusebutton.text = rh.gs(R.string.reuse_profile_pct_hours, profile.value.originalPercentage, T.msecs(profile.value.originalTimeshift).hours().toInt())
binding.reusebutton.setOnClickListener {
binding.percentage.value = profile.value.originalPercentage.toDouble()
binding.timeshift.value = profile.value.originalTimeshift.toDouble()
binding.timeshift.value = T.msecs(profile.value.originalTimeshift).hours().toDouble()
}
}
}
binding.ttLayout.visibility = View.GONE
binding.durationLabel.labelFor = binding.duration.editTextId
binding.percentageLabel.labelFor = binding.percentage.editTextId
binding.timeshiftLabel.labelFor = binding.timeshift.editTextId
}
override fun onDestroyView() {
@ -165,7 +172,7 @@ class ProfileSwitchDialog : DialogFragmentWithDate() {
val duration = binding.duration.value.toInt()
if (duration > 0L)
actions.add(rh.gs(R.string.duration) + ": " + rh.gs(R.string.format_mins, duration))
val profileName = binding.profile.selectedItem.toString()
val profileName = binding.profileList.text.toString()
actions.add(rh.gs(R.string.profile) + ": " + profileName)
val percent = binding.percentage.value.toInt()
if (percent != 100)
@ -242,4 +249,20 @@ class ProfileSwitchDialog : DialogFragmentWithDate() {
}
return true
}
override fun onResume() {
super.onResume()
if(!queryingProtection) {
queryingProtection = true
activity?.let { activity ->
val cancelFail = {
queryingProtection = false
aapsLogger.debug(LTag.APS, "Dialog canceled on resume protection: ${this.javaClass.name}")
ToastUtils.showToastInUiThread(ctx, R.string.dialog_canceled)
dismiss()
}
protectionCheck.queryProtection(activity, BOLUS, { queryingProtection = false }, cancelFail, cancelFail)
}
}
}
}

View file

@ -20,7 +20,11 @@ import info.nightscout.androidaps.utils.HtmlHelper
import info.nightscout.shared.SafeParse
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.extensions.formatColor
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.ToastUtils
import info.nightscout.androidaps.utils.protection.ProtectionCheck
import info.nightscout.androidaps.utils.protection.ProtectionCheck.Protection.BOLUS
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.logging.LTag
import java.text.DecimalFormat
import java.util.*
import javax.inject.Inject
@ -35,13 +39,13 @@ class TempBasalDialog : DialogFragmentWithDate() {
@Inject lateinit var commandQueue: CommandQueue
@Inject lateinit var ctx: Context
@Inject lateinit var uel: UserEntryLogger
@Inject lateinit var protectionCheck: ProtectionCheck
private var queryingProtection = false
private var isPercentPump = true
private var _binding: DialogTempbasalBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
// This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!!
override fun onSaveInstanceState(savedInstanceState: Bundle) {
@ -51,8 +55,7 @@ class TempBasalDialog : DialogFragmentWithDate() {
savedInstanceState.putDouble("basalAbsoluteInput", binding.basalAbsoluteInput.value)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
onCreateViewGeneral()
_binding = DialogTempbasalBinding.inflate(inflater, container, false)
return binding.root
@ -86,6 +89,9 @@ class TempBasalDialog : DialogFragmentWithDate() {
binding.percentLayout.visibility = View.GONE
binding.absoluteLayout.visibility = View.VISIBLE
}
binding.basalPercentLabel.labelFor = binding.basalPercentInput.editTextId
binding.basalAbsoluteLabel.labelFor = binding.basalAbsoluteInput.editTextId
binding.durationLabel.labelFor = binding.duration.editTextId
}
override fun onDestroyView() {
@ -112,7 +118,7 @@ class TempBasalDialog : DialogFragmentWithDate() {
actions.add(rh.gs(R.string.tempbasal_label) + ": " + rh.gs(R.string.pump_basebasalrate, absolute))
actions.add(rh.gs(R.string.duration) + ": " + rh.gs(R.string.format_mins, durationInMinutes))
if (abs(absolute - basalAbsoluteInput) > 0.01)
actions.add(rh.gs(R.string.constraintapllied).formatColor(rh, R.color.warning))
actions.add(rh.gs(R.string.constraintapllied).formatColor(context, rh, R.attr.warningColor))
}
activity?.let { activity ->
OKDialog.showConfirmation(activity, rh.gs(R.string.tempbasal_label), HtmlHelper.fromHtml(Joiner.on("<br/>").join(actions)), {
@ -138,4 +144,20 @@ class TempBasalDialog : DialogFragmentWithDate() {
}
return true
}
}
override fun onResume() {
super.onResume()
if(!queryingProtection) {
queryingProtection = true
activity?.let { activity ->
val cancelFail = {
queryingProtection = false
aapsLogger.debug(LTag.APS, "Dialog canceled on resume protection: ${this.javaClass.name}")
ToastUtils.showToastInUiThread(ctx, R.string.dialog_canceled)
dismiss()
}
protectionCheck.queryProtection(activity, BOLUS, { queryingProtection = false }, cancelFail, cancelFail)
}
}
}
}

View file

@ -1,5 +1,6 @@
package info.nightscout.androidaps.dialogs
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@ -26,10 +27,13 @@ import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.androidaps.utils.DefaultValueHelper
import info.nightscout.androidaps.utils.HtmlHelper
import info.nightscout.androidaps.utils.ToastUtils
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.resources.ResourceHelper
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import info.nightscout.androidaps.utils.protection.ProtectionCheck
import info.nightscout.androidaps.utils.protection.ProtectionCheck.Protection.BOLUS
import info.nightscout.androidaps.interfaces.ResourceHelper
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import java.text.DecimalFormat
import java.util.*
import java.util.concurrent.TimeUnit
@ -43,15 +47,16 @@ class TempTargetDialog : DialogFragmentWithDate() {
@Inject lateinit var defaultValueHelper: DefaultValueHelper
@Inject lateinit var uel: UserEntryLogger
@Inject lateinit var repository: AppRepository
@Inject lateinit var ctx: Context
@Inject lateinit var protectionCheck: ProtectionCheck
private lateinit var reasonList: List<String>
private var queryingProtection = false
private val disposable = CompositeDisposable()
private var _binding: DialogTemptargetBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
// This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!!
override fun onSaveInstanceState(savedInstanceState: Bundle) {
@ -60,8 +65,7 @@ class TempTargetDialog : DialogFragmentWithDate() {
savedInstanceState.putDouble("tempTarget", binding.temptarget.value)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
onCreateViewGeneral()
_binding = DialogTemptargetBinding.inflate(inflater, container, false)
return binding.root
@ -100,10 +104,9 @@ class TempTargetDialog : DialogFragmentWithDate() {
rh.gs(R.string.activity),
rh.gs(R.string.hypo)
)
val adapterReason = ArrayAdapter(context, R.layout.spinner_centered, reasonList)
binding.reason.adapter = adapterReason
binding.reasonList.setAdapter(ArrayAdapter(context, R.layout.spinner_centered, reasonList))
binding.targetCancel.setOnClickListener { shortClick(it) }
binding.targetCancel.setOnClickListener { binding.duration.value = 0.0; shortClick(it) }
binding.eatingSoon.setOnClickListener { shortClick(it) }
binding.activity.setOnClickListener { shortClick(it) }
binding.hypo.setOnClickListener { shortClick(it) }
@ -120,6 +123,8 @@ class TempTargetDialog : DialogFragmentWithDate() {
longClick(it)
return@setOnLongClickListener true
}
binding.durationLabel.labelFor = binding.duration.editTextId
binding.temptargetLabel.labelFor = binding.temptarget.editTextId
}
}
@ -133,23 +138,19 @@ class TempTargetDialog : DialogFragmentWithDate() {
R.id.eating_soon -> {
binding.temptarget.value = defaultValueHelper.determineEatingSoonTT()
binding.duration.value = defaultValueHelper.determineEatingSoonTTDuration().toDouble()
binding.reason.setSelection(reasonList.indexOf(rh.gs(R.string.eatingsoon)))
binding.reasonList.setText(rh.gs(R.string.eatingsoon), false)
}
R.id.activity -> {
binding.temptarget.value = defaultValueHelper.determineActivityTT()
binding.duration.value = defaultValueHelper.determineActivityTTDuration().toDouble()
binding.reason.setSelection(reasonList.indexOf(rh.gs(R.string.activity)))
binding.reasonList.setText(rh.gs(R.string.activity), false)
}
R.id.hypo -> {
binding.temptarget.value = defaultValueHelper.determineHypoTT()
binding.duration.value = defaultValueHelper.determineHypoTTDuration().toDouble()
binding.reason.setSelection(reasonList.indexOf(rh.gs(R.string.hypo)))
}
R.id.cancel -> {
binding.duration.value = 0.0
binding.reasonList.setText(rh.gs(R.string.hypo), false)
}
}
}
@ -163,7 +164,7 @@ class TempTargetDialog : DialogFragmentWithDate() {
override fun submit(): Boolean {
if (_binding == null) return false
val actions: LinkedList<String> = LinkedList()
var reason = binding.reason.selectedItem?.toString() ?: return false
var reason = binding.reasonList.text.toString()
val unitResId = if (profileFunction.getUnits() == GlucoseUnit.MGDL) R.string.mgdl else R.string.mmol
val target = binding.temptarget.value
val duration = binding.duration.value.toInt()
@ -220,4 +221,20 @@ class TempTargetDialog : DialogFragmentWithDate() {
}
return true
}
override fun onResume() {
super.onResume()
if(!queryingProtection) {
queryingProtection = true
activity?.let { activity ->
val cancelFail = {
queryingProtection = false
aapsLogger.debug(LTag.APS, "Dialog canceled on resume protection: ${this.javaClass.name}")
ToastUtils.showToastInUiThread(ctx, R.string.dialog_canceled)
dismiss()
}
protectionCheck.queryProtection(activity, BOLUS, { queryingProtection = false }, cancelFail, cancelFail)
}
}
}
}

View file

@ -30,9 +30,11 @@ import info.nightscout.shared.SafeParse
import info.nightscout.androidaps.utils.ToastUtils
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.extensions.formatColor
import info.nightscout.androidaps.utils.resources.ResourceHelper
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import info.nightscout.androidaps.utils.protection.ProtectionCheck
import info.nightscout.androidaps.utils.protection.ProtectionCheck.Protection.BOLUS
import info.nightscout.androidaps.interfaces.ResourceHelper
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import java.text.DecimalFormat
import java.util.*
import javax.inject.Inject
@ -48,8 +50,14 @@ class TreatmentDialog : DialogFragmentWithDate() {
@Inject lateinit var config: Config
@Inject lateinit var uel: UserEntryLogger
@Inject lateinit var repository: AppRepository
@Inject lateinit var protectionCheck: ProtectionCheck
private var queryingProtection = false
private val disposable = CompositeDisposable()
private var _binding: DialogTreatmentBinding? = null
// This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!!
private val textWatcher: TextWatcher = object : TextWatcher {
override fun afterTextChanged(s: Editable) {}
@ -72,12 +80,6 @@ class TreatmentDialog : DialogFragmentWithDate() {
}
}
private var _binding: DialogTreatmentBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onSaveInstanceState(savedInstanceState: Bundle) {
super.onSaveInstanceState(savedInstanceState)
savedInstanceState.putDouble("carbs", binding.carbs.value)
@ -106,6 +108,8 @@ class TreatmentDialog : DialogFragmentWithDate() {
binding.insulin.setParams(savedInstanceState?.getDouble("insulin")
?: 0.0, 0.0, maxInsulin, pumpDescription.bolusStep, DecimalFormatter.pumpSupportedBolusFormat(activePlugin.activePump), false, binding.okcancel.ok, textWatcher)
binding.recordOnlyLayout.visibility = View.GONE
binding.insulinLabel.labelFor = binding.insulin.editTextId
binding.carbsLabel.labelFor = binding.carbs.editTextId
}
override fun onDestroyView() {
@ -124,16 +128,16 @@ class TreatmentDialog : DialogFragmentWithDate() {
val carbsAfterConstraints = constraintChecker.applyCarbsConstraints(Constraint(carbs)).value()
if (insulinAfterConstraints > 0) {
actions.add(rh.gs(R.string.bolus) + ": " + DecimalFormatter.toPumpSupportedBolus(insulinAfterConstraints, activePlugin.activePump, rh).formatColor(rh, R.color.bolus))
actions.add(rh.gs(R.string.bolus) + ": " + DecimalFormatter.toPumpSupportedBolus(insulinAfterConstraints, activePlugin.activePump, rh).formatColor(context, rh, R.attr.bolusColor))
if (recordOnlyChecked)
actions.add(rh.gs(R.string.bolusrecordedonly).formatColor(rh, R.color.warning))
actions.add(rh.gs(R.string.bolusrecordedonly).formatColor(context, rh, R.attr.warningColor))
if (abs(insulinAfterConstraints - insulin) > pumpDescription.pumpType.determineCorrectBolusStepSize(insulinAfterConstraints))
actions.add(rh.gs(R.string.bolusconstraintappliedwarn, insulin, insulinAfterConstraints).formatColor(rh, R.color.warning))
actions.add(rh.gs(R.string.bolusconstraintappliedwarn, insulin, insulinAfterConstraints).formatColor(context, rh, R.attr.warningColor))
}
if (carbsAfterConstraints > 0) {
actions.add(rh.gs(R.string.carbs) + ": " + rh.gs(R.string.format_carbs, carbsAfterConstraints).formatColor(rh, R.color.carbs))
actions.add(rh.gs(R.string.carbs) + ": " + rh.gs(R.string.format_carbs, carbsAfterConstraints).formatColor(context, rh, R.attr.carbsColor))
if (carbsAfterConstraints != carbs)
actions.add(rh.gs(R.string.carbsconstraintapplied).formatColor(rh, R.color.warning))
actions.add(rh.gs(R.string.carbsconstraintapplied).formatColor(context, rh, R.attr.warningColor))
}
if (insulinAfterConstraints > 0 || carbsAfterConstraints > 0) {
activity?.let { activity ->
@ -199,4 +203,20 @@ class TreatmentDialog : DialogFragmentWithDate() {
}
return true
}
}
override fun onResume() {
super.onResume()
if(!queryingProtection) {
queryingProtection = true
activity?.let { activity ->
val cancelFail = {
queryingProtection = false
aapsLogger.debug(LTag.APS, "Dialog canceled on resume protection: ${this.javaClass.name}")
ToastUtils.showToastInUiThread(ctx, R.string.dialog_canceled)
dismiss()
}
protectionCheck.queryProtection(activity, BOLUS, { queryingProtection = false }, cancelFail, cancelFail)
}
}
}
}

View file

@ -1,12 +1,18 @@
package info.nightscout.androidaps.dialogs
import android.annotation.SuppressLint
import android.content.Context
import android.os.Bundle
import android.os.Handler
import android.os.HandlerThread
import android.text.Editable
import android.text.TextWatcher
import android.view.*
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.Window
import android.view.WindowManager
import android.widget.AdapterView
import android.widget.AdapterView.OnItemSelectedListener
import android.widget.ArrayAdapter
import android.widget.CompoundButton
import androidx.fragment.app.FragmentManager
@ -20,22 +26,25 @@ import info.nightscout.androidaps.database.ValueWrapper
import info.nightscout.androidaps.databinding.DialogWizardBinding
import info.nightscout.androidaps.events.EventAutosensCalculationFinished
import info.nightscout.androidaps.extensions.formatColor
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.shared.SafeParse
import info.nightscout.androidaps.extensions.runOnUiThread
import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.extensions.valueToUnits
import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.androidaps.utils.*
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.protection.ProtectionCheck
import info.nightscout.androidaps.utils.protection.ProtectionCheck.Protection.BOLUS
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.androidaps.utils.wizard.BolusWizard
import io.reactivex.disposables.CompositeDisposable
import info.nightscout.shared.SafeParse
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import java.text.DecimalFormat
import java.util.*
import javax.inject.Inject
import kotlin.math.abs
@ -55,16 +64,24 @@ class WizardDialog : DaggerDialogFragment() {
@Inject lateinit var iobCobCalculator: IobCobCalculator
@Inject lateinit var repository: AppRepository
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var protectionCheck: ProtectionCheck
private val handler = Handler(HandlerThread(this::class.simpleName + "Handler").also { it.start() }.looper)
private var queryingProtection = false
private var wizard: BolusWizard? = null
private var calculatedPercentage = 100.0
private var calculatedCorrection = 0.0
private var correctionPercent = false
private var usePercentage = false
private var carbsPassedIntoWizard = 0.0
private var notesPassedIntoWizard = ""
private var okClicked: Boolean = false // one shot guards
private var disposable: CompositeDisposable = CompositeDisposable()
private var bolusStep = 0.0
private var _binding: DialogWizardBinding? = null
//one shot guards
private var okClicked: Boolean = false
// This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!!
private val textWatcher = object : TextWatcher {
override fun afterTextChanged(s: Editable) {}
@ -83,15 +100,6 @@ class WizardDialog : DaggerDialogFragment() {
}
}
private var disposable: CompositeDisposable = CompositeDisposable()
private var bolusStep = 0.0
private var _binding: DialogWizardBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onStart() {
super.onStart()
dialog?.window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
@ -106,10 +114,9 @@ class WizardDialog : DaggerDialogFragment() {
savedInstanceState.putDouble("carb_time_input", binding.carbTimeInput.value)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
this.arguments?.let { bundle ->
carbsPassedIntoWizard = bundle.getInt("carbs_input").toDouble()
carbsPassedIntoWizard = bundle.getDouble("carbs_input")
notesPassedIntoWizard = bundle.getString("notes_input").toString()
}
@ -125,8 +132,10 @@ class WizardDialog : DaggerDialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
loadCheckedStates()
processCobCheckBox()
binding.sbCheckbox.visibility = sp.getBoolean(R.string.key_usesuperbolus, false).toVisibility()
binding.notesLayout.visibility = sp.getBoolean(R.string.key_show_notes_entry_dialogs, false).toVisibility()
val useSuperBolus = sp.getBoolean(R.string.key_usesuperbolus, false)
binding.sbCheckbox.visibility = useSuperBolus.toVisibility()
binding.superBolusRow.visibility = useSuperBolus.toVisibility()
binding.notesLayout.root.visibility = sp.getBoolean(R.string.key_show_notes_entry_dialogs, false).toVisibility()
val maxCarbs = constraintChecker.getMaxCarbsAllowed().value()
val maxCorrection = constraintChecker.getMaxBolusAllowed().value()
@ -135,31 +144,39 @@ class WizardDialog : DaggerDialogFragment() {
if (profileFunction.getUnits() == GlucoseUnit.MGDL) {
binding.bgInput.setParams(
savedInstanceState?.getDouble("bg_input")
?: 0.0, 0.0, 500.0, 1.0, DecimalFormat("0"), false, binding.okcancel.ok, timeTextWatcher)
?: 0.0, 0.0, 500.0, 1.0, DecimalFormat("0"), false, binding.okcancel.ok, timeTextWatcher
)
} else {
binding.bgInput.setParams(
savedInstanceState?.getDouble("bg_input")
?: 0.0, 0.0, 30.0, 0.1, DecimalFormat("0.0"), false, binding.okcancel.ok, textWatcher)
?: 0.0, 0.0, 30.0, 0.1, DecimalFormat("0.0"), false, binding.okcancel.ok, textWatcher
)
}
binding.carbsInput.setParams(savedInstanceState?.getDouble("carbs_input")
?: 0.0, 0.0, maxCarbs.toDouble(), 1.0, DecimalFormat("0"), false, binding.okcancel.ok, textWatcher)
binding.carbsInput.setParams(
savedInstanceState?.getDouble("carbs_input")
?: 0.0, 0.0, maxCarbs.toDouble(), 1.0, DecimalFormat("0"), false, binding.okcancel.ok, textWatcher
)
if (correctionPercent) {
if (usePercentage) {
calculatedPercentage = sp.getInt(R.string.key_boluswizard_percentage, 100).toDouble()
binding.correctionInput.setParams(calculatedPercentage, 10.0, 200.0, 1.0, DecimalFormat("0"), false, binding.okcancel.ok, textWatcher)
binding.correctionInput.setParams(calculatedPercentage, 10.0, 200.0, 5.0, DecimalFormat("0"), false, binding.okcancel.ok, textWatcher)
binding.correctionInput.value = calculatedPercentage
binding.correctionUnit.text = "%"
} else {
binding.correctionInput.setParams(
savedInstanceState?.getDouble("correction_input")
?: 0.0, -maxCorrection, maxCorrection, bolusStep, DecimalFormatter.pumpSupportedBolusFormat(activePlugin.activePump), false, binding.okcancel.ok, textWatcher)
?: 0.0, -maxCorrection, maxCorrection, bolusStep, DecimalFormatter.pumpSupportedBolusFormat(activePlugin.activePump), false, binding.okcancel.ok, textWatcher
)
binding.correctionUnit.text = rh.gs(R.string.insulin_unit_shortname)
}
binding.carbTimeInput.setParams(savedInstanceState?.getDouble("carb_time_input")
?: 0.0, -60.0, 60.0, 5.0, DecimalFormat("0"), false, binding.okcancel.ok, timeTextWatcher)
initDialog()
binding.carbTimeInput.setParams(
savedInstanceState?.getDouble("carb_time_input")
?: 0.0, -60.0, 60.0, 5.0, DecimalFormat("0"), false, binding.okcancel.ok, timeTextWatcher
)
handler.post { initDialog() }
calculatedPercentage = sp.getInt(R.string.key_boluswizard_percentage, 100).toDouble()
binding.percentUsed.text = rh.gs(R.string.format_percent, sp.getInt(R.string.key_boluswizard_percentage, 100))
binding.percentUsed.visibility = (sp.getInt(R.string.key_boluswizard_percentage, 100) != 100 || usePercentage).toVisibility()
// ok button
binding.okcancel.ok.setOnClickListener {
if (okClicked) {
@ -175,9 +192,10 @@ class WizardDialog : DaggerDialogFragment() {
dismiss()
}
binding.bgCheckboxIcon.setOnClickListener { binding.bgCheckbox.isChecked = !binding.bgCheckbox.isChecked }
binding.ttCheckboxIcon.setOnClickListener { binding.ttCheckbox.isChecked = !binding.ttCheckbox.isChecked }
binding.trendCheckboxIcon.setOnClickListener { binding.bgTrendCheckbox.isChecked = !binding.bgTrendCheckbox.isChecked }
binding.cobCheckboxIcon.setOnClickListener { binding.cobCheckbox.isChecked = !binding.cobCheckbox.isChecked; processCobCheckBox(); }
binding.iobCheckboxIcon.setOnClickListener { if (!binding.cobCheckbox.isChecked) binding.iobCheckbox.isChecked = !binding.iobCheckbox.isChecked }
binding.iobCheckboxIcon.setOnClickListener { binding.iobCheckbox.isChecked = !binding.iobCheckbox.isChecked; processIobCheckBox(); }
// cancel button
binding.okcancel.cancel.setOnClickListener {
aapsLogger.debug(LTag.APS, "Dialog canceled: ${this.javaClass.name}")
@ -206,13 +224,13 @@ class WizardDialog : DaggerDialogFragment() {
processEnabledIcons()
binding.correctionPercent.setOnCheckedChangeListener {_, isChecked ->
binding.correctionPercent.setOnCheckedChangeListener { _, isChecked ->
run {
sp.putBoolean(rh.gs(R.string.key_wizard_correction_percent), isChecked)
binding.correctionUnit.text = if (isChecked) "%" else rh.gs(R.string.insulin_unit_shortname)
correctionPercent = binding.correctionPercent.isChecked
if (correctionPercent) {
binding.correctionInput.setParams(calculatedPercentage, 10.0, 200.0, 1.0, DecimalFormat("0"), false, binding.okcancel.ok, textWatcher)
usePercentage = binding.correctionPercent.isChecked
if (usePercentage) {
binding.correctionInput.setParams(calculatedPercentage, 10.0, 200.0, 5.0, DecimalFormat("0"), false, binding.okcancel.ok, textWatcher)
binding.correctionInput.customContentDescription = rh.gs(R.string.a11_correction_percentage)
} else {
binding.correctionInput.setParams(
@ -222,73 +240,76 @@ class WizardDialog : DaggerDialogFragment() {
binding.correctionInput.customContentDescription = rh.gs(R.string.a11_correction_units)
}
binding.correctionInput.updateA11yDescription()
binding.correctionInput.value = if (correctionPercent) calculatedPercentage else Round.roundTo(calculatedCorrection, bolusStep)
}
}
// profile spinner
binding.profile.onItemSelectedListener = object : OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) {
ToastUtils.showToastInUiThread(ctx, rh.gs(R.string.noprofileset))
binding.okcancel.ok.visibility = View.GONE
}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
calculateInsulin()
binding.okcancel.ok.visibility = View.VISIBLE
binding.correctionInput.value = if (usePercentage) calculatedPercentage else Round.roundTo(calculatedCorrection, bolusStep)
}
}
// profile
binding.profileList.onItemClickListener = AdapterView.OnItemClickListener { _, _, _, _ -> calculateInsulin() }
// bus
disposable.add(rxBus
disposable += rxBus
.toObservable(EventAutosensCalculationFinished::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({
activity?.runOnUiThread { calculateInsulin() }
}, fabricPrivacy::logException)
)
.subscribe({ calculateInsulin() }, fabricPrivacy::logException)
setA11yLabels()
}
private fun setA11yLabels() {
binding.bgInput.editText?.id?.let { binding.bgInputLabel.labelFor = it }
binding.carbsInput.editText?.id?.let { binding.carbsInputLabel.labelFor = it }
binding.correctionInput.editText?.id?.let { binding.correctionInputLabel.labelFor = it }
binding.carbTimeInput.editText?.id?.let { binding.carbTimeInputLabel.labelFor = it }
binding.bgInputLabel.labelFor = binding.bgInput.editTextId
binding.carbsInputLabel.labelFor = binding.carbsInput.editTextId
binding.correctionInputLabel.labelFor = binding.correctionInput.editTextId
binding.carbTimeInputLabel.labelFor = binding.carbTimeInput.editTextId
}
override fun onDestroyView() {
super.onDestroyView()
disposable.clear()
handler.removeCallbacksAndMessages(null)
_binding = null
}
private fun onCheckedChanged(buttonView: CompoundButton, @Suppress("UNUSED_PARAMETER") state: Boolean) {
saveCheckedStates()
binding.ttCheckbox.isEnabled = binding.bgCheckbox.isChecked && repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet() is ValueWrapper.Existing
binding.ttCheckboxIcon.visibility = binding.ttCheckbox.isEnabled.toVisibility()
if (buttonView.id == binding.cobCheckbox.id)
processCobCheckBox()
if (buttonView.id == binding.iobCheckbox.id)
processIobCheckBox()
processEnabledIcons()
calculateInsulin()
}
private fun processCobCheckBox() {
if (binding.cobCheckbox.isChecked) {
binding.iobCheckbox.isEnabled = false
binding.iobCheckbox.isChecked = true
} else {
binding.iobCheckbox.isEnabled = true
}
}
private fun processIobCheckBox() {
if (!binding.iobCheckbox.isChecked) {
binding.cobCheckbox.isChecked = false
}
}
private fun processEnabledIcons() {
binding.bgCheckboxIcon.isChecked = binding.bgCheckbox.isChecked
binding.ttCheckboxIcon.isChecked = binding.ttCheckbox.isChecked
binding.trendCheckboxIcon.isChecked = binding.bgTrendCheckbox.isChecked
binding.iobCheckboxIcon.isChecked = binding.iobCheckbox.isChecked
binding.cobCheckboxIcon.isChecked = binding.cobCheckbox.isChecked
binding.bgCheckboxIcon.alpha = if (binding.bgCheckbox.isChecked) 1.0f else 0.2f
binding.ttCheckboxIcon.alpha = if (binding.ttCheckbox.isChecked) 1.0f else 0.2f
binding.trendCheckboxIcon.alpha = if (binding.bgTrendCheckbox.isChecked) 1.0f else 0.2f
binding.iobCheckboxIcon.alpha = if (binding.iobCheckbox.isChecked) 1.0f else 0.2f
binding.cobCheckboxIcon.alpha = if (binding.cobCheckbox.isChecked) 1.0f else 0.2f
binding.bgCheckboxIcon.visibility = binding.calculationCheckbox.isChecked.not().toVisibility()
binding.ttCheckboxIcon.visibility = (binding.calculationCheckbox.isChecked.not() && binding.ttCheckbox.isEnabled).toVisibility()
binding.trendCheckboxIcon.visibility = binding.calculationCheckbox.isChecked.not().toVisibility()
binding.iobCheckboxIcon.visibility = binding.calculationCheckbox.isChecked.not().toVisibility()
binding.cobCheckboxIcon.visibility = binding.calculationCheckbox.isChecked.not().toVisibility()
binding.checkboxRow.visibility = binding.calculationCheckbox.isChecked.not().toVisibility()
}
private fun saveCheckedStates() {
@ -300,8 +321,8 @@ class WizardDialog : DaggerDialogFragment() {
private fun loadCheckedStates() {
binding.bgTrendCheckbox.isChecked = sp.getBoolean(R.string.key_wizard_include_trend_bg, false)
binding.cobCheckbox.isChecked = sp.getBoolean(R.string.key_wizard_include_cob, false)
correctionPercent = sp.getBoolean(R.string.key_wizard_correction_percent,false)
binding.correctionPercent.isChecked = correctionPercent
usePercentage = sp.getBoolean(R.string.key_wizard_correction_percent, false)
binding.correctionPercent.isChecked = usePercentage
}
private fun valueToUnitsToString(value: Double, units: String): String =
@ -309,14 +330,9 @@ class WizardDialog : DaggerDialogFragment() {
else DecimalFormatter.to1Decimal(value * Constants.MGDL_TO_MMOLL)
private fun initDialog() {
if(carbsPassedIntoWizard != 0.0) {
binding.carbsInput.value = carbsPassedIntoWizard
}
if(notesPassedIntoWizard.isNotBlank()) {
binding.notes.setText(notesPassedIntoWizard)
}
val profile = profileFunction.getProfile()
val profileStore = activePlugin.activeProfileSource.profile
val tempTarget = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet()
if (profile == null || profileStore == null) {
ToastUtils.showToastInUiThread(ctx, rh.gs(R.string.noprofile))
@ -324,37 +340,45 @@ class WizardDialog : DaggerDialogFragment() {
return
}
val profileList: ArrayList<CharSequence> = profileStore.getProfileList()
profileList.add(0, rh.gs(R.string.active))
context?.let { context ->
val adapter = ArrayAdapter(context, R.layout.spinner_centered, profileList)
binding.profile.adapter = adapter
} ?: return
val units = profileFunction.getUnits()
binding.bgUnits.text = units.asText
binding.bgInput.step = if (units == GlucoseUnit.MGDL) 1.0 else 0.1
// Set BG if not old
binding.bgInput.value = iobCobCalculator.ads.actualBg()?.valueToUnits(units) ?: 0.0
binding.ttCheckbox.isEnabled = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet() is ValueWrapper.Existing
// IOB calculation
val bolusIob = iobCobCalculator.calculateIobFromBolus().round()
val basalIob = iobCobCalculator.calculateIobFromTempBasalsIncludingConvertedExtended().round()
binding.iobInsulin.text = rh.gs(R.string.formatinsulinunits, -bolusIob.iob - basalIob.basaliob)
runOnUiThread {
_binding ?: return@runOnUiThread
if (carbsPassedIntoWizard != 0.0) {
binding.carbsInput.value = carbsPassedIntoWizard
}
if (notesPassedIntoWizard.isNotBlank()) {
binding.notesLayout.notes.setText(notesPassedIntoWizard)
}
calculateInsulin()
val profileList: ArrayList<CharSequence> = profileStore.getProfileList()
profileList.add(0, rh.gs(R.string.active))
context?.let { context ->
binding.profileList.setAdapter(ArrayAdapter(context, R.layout.spinner_centered, profileList))
binding.profileList.setText(profileList[0], false)
}
binding.percentUsed.visibility = (sp.getInt(R.string.key_boluswizard_percentage, 100) != 100 || correctionPercent).toVisibility()
val units = profileFunction.getUnits()
binding.bgUnits.text = units.asText
binding.bgInput.step = if (units == GlucoseUnit.MGDL) 1.0 else 0.1
// Set BG if not old
binding.bgInput.value = iobCobCalculator.ads.actualBg()?.valueToUnits(units) ?: 0.0
binding.ttCheckbox.isEnabled = tempTarget is ValueWrapper.Existing
binding.ttCheckboxIcon.visibility = binding.ttCheckbox.isEnabled.toVisibility()
binding.iobInsulin.text = rh.gs(R.string.formatinsulinunits, -bolusIob.iob - basalIob.basaliob)
calculateInsulin()
}
}
@SuppressLint("SetTextI18n")
private fun calculateInsulin() {
val profileStore = activePlugin.activeProfileSource.profile
if (binding.profile.selectedItem == null || profileStore == null)
return // not initialized yet
var profileName = binding.profile.selectedItem.toString()
val profileStore = activePlugin.activeProfileSource.profile ?: return // not initialized yet
var profileName = binding.profileList.text.toString()
val specificProfile: Profile?
if (profileName == rh.gs(R.string.active)) {
specificProfile = profileFunction.getProfile()
@ -376,7 +400,7 @@ class WizardDialog : DaggerDialogFragment() {
} else
0.0
val percentageCorrection = if (usePercentage) {
if (Round.roundTo(calculatedPercentage,1.0) == SafeParse.stringToDouble(binding.correctionInput.text))
if (Round.roundTo(calculatedPercentage, 1.0) == SafeParse.stringToDouble(binding.correctionInput.text))
calculatedPercentage
else
SafeParse.stringToDouble(binding.correctionInput.text)
@ -402,7 +426,8 @@ class WizardDialog : DaggerDialogFragment() {
val carbTime = SafeParse.stringToInt(binding.carbTimeInput.text)
wizard = BolusWizard(injector).doCalc(specificProfile, profileName, tempTarget, carbsAfterConstraint, cob, bg, correction, sp.getInt(R.string.key_boluswizard_percentage, 100),
wizard = BolusWizard(injector).doCalc(
specificProfile, profileName, tempTarget, carbsAfterConstraint, cob, bg, correction, sp.getInt(R.string.key_boluswizard_percentage, 100),
binding.bgCheckbox.isChecked,
binding.cobCheckbox.isChecked,
binding.iobCheckbox.isChecked,
@ -411,17 +436,17 @@ class WizardDialog : DaggerDialogFragment() {
binding.ttCheckbox.isChecked,
binding.bgTrendCheckbox.isChecked,
binding.alarm.isChecked,
binding.notes.text.toString(),
binding.notesLayout.notes.text.toString(),
carbTime,
usePercentage = usePercentage,
totalPercentage = percentageCorrection
)
wizard?.let { wizard ->
binding.bg.text = String.format(rh.gs(R.string.format_bg_isf), valueToUnitsToString(Profile.toMgdl(bg, profileFunction.getUnits()), profileFunction.getUnits().asText), wizard.sens)
binding.bg.text = rh.gs(R.string.format_bg_isf, valueToUnitsToString(Profile.toMgdl(bg, profileFunction.getUnits()), profileFunction.getUnits().asText), wizard.sens)
binding.bgInsulin.text = rh.gs(R.string.formatinsulinunits, wizard.insulinFromBG)
binding.carbs.text = String.format(rh.gs(R.string.format_carbs_ic), carbs.toDouble(), wizard.ic)
binding.carbs.text = rh.gs(R.string.format_carbs_ic, carbs.toDouble(), wizard.ic)
binding.carbsInsulin.text = rh.gs(R.string.formatinsulinunits, wizard.insulinFromCarbs)
binding.iobInsulin.text = rh.gs(R.string.formatinsulinunits, wizard.insulinFromBolusIOB + wizard.insulinFromBasalIOB)
@ -444,7 +469,7 @@ class WizardDialog : DaggerDialogFragment() {
// COB
if (binding.cobCheckbox.isChecked) {
binding.cob.text = String.format(rh.gs(R.string.format_cob_ic), cob, wizard.ic)
binding.cob.text = rh.gs(R.string.format_cob_ic, cob, wizard.ic)
binding.cobInsulin.text = rh.gs(R.string.formatinsulinunits, wizard.insulinFromCOB)
} else {
binding.cob.text = ""
@ -452,12 +477,12 @@ class WizardDialog : DaggerDialogFragment() {
}
if (wizard.calculatedTotalInsulin > 0.0 || carbsAfterConstraint > 0.0) {
val insulinText = if (wizard.calculatedTotalInsulin > 0.0) rh.gs(R.string.formatinsulinunits, wizard.calculatedTotalInsulin).formatColor(rh, R.color.bolus) else ""
val carbsText = if (carbsAfterConstraint > 0.0) rh.gs(R.string.format_carbs, carbsAfterConstraint).formatColor(rh, R.color.carbs) else ""
val insulinText = if (wizard.calculatedTotalInsulin > 0.0) rh.gs(R.string.formatinsulinunits, wizard.calculatedTotalInsulin).formatColor(context, rh, R.attr.bolusColor) else ""
val carbsText = if (carbsAfterConstraint > 0.0) rh.gs(R.string.format_carbs, carbsAfterConstraint).formatColor(context, rh, R.attr.carbsColor) else ""
binding.total.text = HtmlHelper.fromHtml(rh.gs(R.string.result_insulin_carbs, insulinText, carbsText))
binding.okcancel.ok.visibility = View.VISIBLE
} else {
binding.total.text = HtmlHelper.fromHtml(rh.gs(R.string.missing_carbs, wizard.carbsEquivalent.toInt()).formatColor(rh, R.color.carbs))
binding.total.text = HtmlHelper.fromHtml(rh.gs(R.string.missing_carbs, wizard.carbsEquivalent.toInt()).formatColor(context, rh, R.attr.carbsColor))
binding.okcancel.ok.visibility = View.INVISIBLE
}
binding.percentUsed.text = rh.gs(R.string.format_percent, wizard.percentageCorrection)
@ -477,4 +502,20 @@ class WizardDialog : DaggerDialogFragment() {
aapsLogger.debug(e.localizedMessage ?: "")
}
}
override fun onResume() {
super.onResume()
if (!queryingProtection) {
queryingProtection = true
activity?.let { activity ->
val cancelFail = {
queryingProtection = false
aapsLogger.debug(LTag.APS, "Dialog canceled on resume protection: ${this.javaClass.name}")
ToastUtils.showToastInUiThread(ctx, R.string.dialog_canceled)
dismiss()
}
protectionCheck.queryProtection(activity, BOLUS, { queryingProtection = false }, cancelFail, cancelFail)
}
}
}
}

View file

@ -17,7 +17,7 @@ import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.DecimalFormatter
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.interfaces.ResourceHelper
import org.json.JSONObject
import javax.inject.Inject
@ -60,7 +60,7 @@ class WizardInfoDialog : DaggerDialogFragment() {
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString("data", data.toJson(true, dateUtil).toString())
outState.putString("data", data.toJson(true, dateUtil, profileFunction).toString())
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -69,13 +69,15 @@ class WizardInfoDialog : DaggerDialogFragment() {
binding.close.setOnClickListener { dismiss() }
val units = profileFunction.getUnits()
val bgString = Profile.toUnitsString(data.glucoseValue, data.glucoseValue * Constants.MGDL_TO_MMOLL, units)
val isf = Profile.toUnits(data.isf, data.isf * Constants.MGDL_TO_MMOLL, units)
val trend = Profile.toUnitsString(data.glucoseTrend * 3, data.glucoseTrend * 3 * Constants.MGDL_TO_MMOLL, units)
// BG
binding.bg.text = rh.gs(R.string.format_bg_isf, bgString, data.isf)
binding.bg.text = rh.gs(R.string.format_bg_isf, bgString, isf)
binding.bgInsulin.text = rh.gs(R.string.formatinsulinunits, data.glucoseInsulin)
binding.bgCheckbox.isChecked = data.wasGlucoseUsed
binding.ttCheckbox.isChecked = data.wasTempTargetUsed
// Trend
binding.bgTrend.text = DecimalFormatter.to1Decimal(data.glucoseTrend)
binding.bgTrend.text = trend
binding.bgTrendInsulin.text = rh.gs(R.string.formatinsulinunits, data.trendInsulin)
binding.bgTrendCheckbox.isChecked = data.wasTrendUsed
// COB

View file

@ -1,3 +0,0 @@
package info.nightscout.androidaps.events
class EventBolusRequested(var amount: Double) : Event()

View file

@ -1,3 +0,0 @@
package info.nightscout.androidaps.events
class EventReloadProfileSwitchData : Event()

View file

@ -0,0 +1,3 @@
package info.nightscout.androidaps.events
class EventScale(val hours: Int) : Event()

View file

@ -1,3 +0,0 @@
package info.nightscout.androidaps.events
class EventTreatmentUpdateGui : EventUpdateGui()

View file

@ -1,5 +0,0 @@
package info.nightscout.androidaps.plugins.aps.events
import info.nightscout.androidaps.events.Event
class EventLoopInvoked : Event()

View file

@ -1,26 +1,24 @@
package info.nightscout.androidaps.plugins.aps.loop
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.*
import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.R
import info.nightscout.androidaps.databinding.LoopFragmentBinding
import info.nightscout.androidaps.interfaces.Constraint
import info.nightscout.androidaps.interfaces.Loop
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.androidaps.plugins.aps.loop.events.EventLoopSetLastRunGui
import info.nightscout.androidaps.plugins.aps.loop.events.EventLoopUpdateGui
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.HtmlHelper
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.sharedPreferences.SP
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import javax.inject.Inject
class LoopFragment : DaggerFragment() {
@ -34,6 +32,8 @@ class LoopFragment : DaggerFragment() {
@Inject lateinit var loop: Loop
@Inject lateinit var dateUtil: DateUtil
private val ID_MENU_RUN = 1
private var disposable: CompositeDisposable = CompositeDisposable()
private var _binding: LoopFragmentBinding? = null
@ -42,21 +42,44 @@ class LoopFragment : DaggerFragment() {
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
_binding = LoopFragmentBinding.inflate(inflater, container, false)
return binding.root
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
LoopFragmentBinding.inflate(inflater, container, false).also {
_binding = it
setHasOptionsMenu(true)
}.root
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.run.setOnClickListener {
binding.lastrun.text = rh.gs(R.string.executing)
Thread { loop.invoke("Loop button", true) }.start()
with(binding.swipeRefresh) {
setColorSchemeColors(rh.gac(context, R.attr.colorPrimaryDark), rh.gac(context, R.attr.colorPrimary), rh.gac(context, R.attr.colorSecondary))
setOnRefreshListener {
binding.lastrun.text = rh.gs(info.nightscout.androidaps.R.string.executing)
Thread { loop.invoke("Loop swiperefresh", true) }.start()
}
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
if (isResumed) {
menu.removeItem(ID_MENU_RUN)
menu.add(Menu.FIRST, ID_MENU_RUN, 0, rh.gs(R.string.openapsma_run)).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
menu.setGroupDividerEnabled(true)
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean =
when (item.itemId) {
ID_MENU_RUN -> {
binding.lastrun.text = rh.gs(R.string.executing)
Thread { loop.invoke("Loop menu", true) }.start()
true
}
else -> false
}
@Synchronized
override fun onResume() {
super.onResume()
@ -64,16 +87,16 @@ class LoopFragment : DaggerFragment() {
.toObservable(EventLoopUpdateGui::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({
updateGUI()
}, fabricPrivacy::logException)
updateGUI()
}, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventLoopSetLastRunGui::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({
clearGUI()
binding.lastrun.text = it.text
}, fabricPrivacy::logException)
clearGUI()
binding.lastrun.text = it.text
}, fabricPrivacy::logException)
updateGUI()
sp.putBoolean(R.string.key_objectiveuseloop, true)
@ -117,6 +140,7 @@ class LoopFragment : DaggerFragment() {
allConstraints.getMostLimitedReasons(aapsLogger)
} ?: ""
binding.constraints.text = constraints
binding.swipeRefresh.isRefreshing = false
}
}
@ -133,5 +157,6 @@ class LoopFragment : DaggerFragment() {
binding.tbrexecutionTime.text = ""
binding.tbrsetbypump.text = ""
binding.smbsetbypump.text = ""
binding.swipeRefresh.isRefreshing = false
}
}

View file

@ -10,11 +10,13 @@ import android.content.Intent
import android.os.SystemClock
import androidx.core.app.NotificationCompat
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.*
import info.nightscout.androidaps.BuildConfig
import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.MainActivity
import info.nightscout.androidaps.R
import info.nightscout.androidaps.activities.ErrorHelperActivity
import info.nightscout.androidaps.annotations.OpenForTesting
import info.nightscout.androidaps.data.DetailedBolusInfo
import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.data.PumpEnactResult
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.ValueWrapper
@ -25,13 +27,14 @@ import info.nightscout.androidaps.database.entities.ValueWithUnit
import info.nightscout.androidaps.database.transactions.InsertAndCancelCurrentOfflineEventTransaction
import info.nightscout.androidaps.database.transactions.InsertTherapyEventAnnouncementTransaction
import info.nightscout.androidaps.events.EventAcceptOpenLoopChange
import info.nightscout.androidaps.events.EventAutosensCalculationFinished
import info.nightscout.androidaps.events.EventNewBG
import info.nightscout.androidaps.events.EventMobileToWear
import info.nightscout.androidaps.events.EventTempTargetChange
import info.nightscout.androidaps.extensions.buildDeviceStatus
import info.nightscout.androidaps.extensions.convertedToAbsolute
import info.nightscout.androidaps.extensions.convertedToPercent
import info.nightscout.androidaps.extensions.plannedRemainingMinutes
import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.interfaces.Loop.LastRun
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.aps.loop.events.EventLoopSetLastRunGui
import info.nightscout.androidaps.plugins.aps.loop.events.EventLoopUpdateGui
@ -42,8 +45,6 @@ import info.nightscout.androidaps.plugins.configBuilder.RunningConfiguration
import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification
import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification
import info.nightscout.androidaps.plugins.general.overview.notifications.Notification
import info.nightscout.androidaps.plugins.general.wear.events.EventWearConfirmAction
import info.nightscout.androidaps.plugins.general.wear.events.EventWearInitiateAction
import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin
import info.nightscout.androidaps.queue.Callback
import info.nightscout.androidaps.receivers.ReceiverStatusStore
@ -51,16 +52,14 @@ import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.HardLimits
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.extensions.buildDeviceStatus
import info.nightscout.androidaps.extensions.convertedToAbsolute
import info.nightscout.androidaps.extensions.convertedToPercent
import info.nightscout.androidaps.extensions.plannedRemainingMinutes
import info.nightscout.androidaps.plugins.aps.events.EventLoopInvoked
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import info.nightscout.shared.weardata.EventData
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.abs
@ -88,20 +87,21 @@ class LoopPlugin @Inject constructor(
private val uel: UserEntryLogger,
private val repository: AppRepository,
private val runningConfiguration: RunningConfiguration
) : PluginBase(PluginDescription()
.mainType(PluginType.LOOP)
.fragmentClass(LoopFragment::class.java.name)
.pluginIcon(R.drawable.ic_loop_closed_white)
.pluginName(R.string.loop)
.shortName(R.string.loop_shortname)
.preferencesId(R.xml.pref_loop)
.enableByDefault(config.APS)
.description(R.string.description_loop),
) : PluginBase(
PluginDescription()
.mainType(PluginType.LOOP)
.fragmentClass(LoopFragment::class.java.name)
.pluginIcon(R.drawable.ic_loop_closed_white)
.pluginName(R.string.loop)
.shortName(R.string.loop_shortname)
.preferencesId(R.xml.pref_loop)
.enableByDefault(config.APS)
.description(R.string.description_loop),
aapsLogger, rh, injector
), Loop {
private val disposable = CompositeDisposable()
private var lastBgTriggeredRun: Long = 0
override var lastBgTriggeredRun: Long = 0
private var carbsSuggestionsSuspendedUntil: Long = 0
private var prevCarbsreq = 0
override var lastRun: LastRun? = null
@ -109,39 +109,19 @@ class LoopPlugin @Inject constructor(
override fun onStart() {
createNotificationChannel()
super.onStart()
disposable.add(rxBus
disposable += rxBus
.toObservable(EventTempTargetChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ invoke("EventTempTargetChange", true) }, fabricPrivacy::logException)
)
/*
This method is triggered once autosens calculation has completed, so the LoopPlugin
has current data to work with. However, autosens calculation can be triggered by multiple
sources and currently only a new BG should trigger a loop run. Hence we return early if
the event causing the calculation is not EventNewBg.
<p>
*/
disposable.add(rxBus
.toObservable(EventAutosensCalculationFinished::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ event: EventAutosensCalculationFinished ->
// Autosens calculation not triggered by a new BG
if (event.cause !is EventNewBG) return@subscribe
val glucoseValue = iobCobCalculator.ads.actualBg() ?: return@subscribe
// BG outdated
// already looped with that value
if (glucoseValue.timestamp <= lastBgTriggeredRun) return@subscribe
lastBgTriggeredRun = glucoseValue.timestamp
invoke("AutosenseCalculation for $glucoseValue", true)
}, fabricPrivacy::logException)
)
}
private fun createNotificationChannel() {
val mNotificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
@SuppressLint("WrongConstant") val channel = NotificationChannel(CHANNEL_ID,
@SuppressLint("WrongConstant") val channel = NotificationChannel(
CHANNEL_ID,
NotificationManager.IMPORTANCE_HIGH)
CHANNEL_ID,
NotificationManager.IMPORTANCE_HIGH
)
mNotificationManager.createNotificationChannel(channel)
}
@ -255,7 +235,7 @@ class LoopPlugin @Inject constructor(
if (apsResult == null) {
rxBus.send(EventLoopSetLastRunGui(rh.gs(R.string.noapsselected)))
return
} else rxBus.send(EventLoopInvoked())
}
if (!isEmptyQueue()) {
aapsLogger.debug(LTag.APS, rh.gs(R.string.pumpbusy))
@ -296,9 +276,11 @@ class LoopPlugin @Inject constructor(
lastRun.lastTBRRequest = 0
lastRun.lastSMBEnact = 0
lastRun.lastSMBRequest = 0
buildDeviceStatus(dateUtil, this, iobCobCalculator, profileFunction,
buildDeviceStatus(
dateUtil, this, iobCobCalculator, profileFunction,
activePlugin.activePump, receiverStatusStore, runningConfiguration,
BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION)?.also {
BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION
)?.also {
repository.insert(it)
}
@ -316,7 +298,11 @@ class LoopPlugin @Inject constructor(
if (closedLoopEnabled.value()) {
if (allowNotification) {
if (resultAfterConstraints.isCarbsRequired
&& resultAfterConstraints.carbsReq >= sp.getInt(R.string.key_smb_enable_carbs_suggestions_threshold, 0) && carbsSuggestionsSuspendedUntil < System.currentTimeMillis() && !treatmentTimeThreshold(-15)) {
&& resultAfterConstraints.carbsReq >= sp.getInt(
R.string.key_smb_enable_carbs_suggestions_threshold,
0
) && carbsSuggestionsSuspendedUntil < System.currentTimeMillis() && !treatmentTimeThreshold(-15)
) {
if (sp.getBoolean(R.string.key_enable_carbs_required_alert_local, true) && !sp.getBoolean(R.string.key_raise_notifications_as_android_notifications, true)) {
val carbReqLocal = Notification(Notification.CARBS_REQUIRED, resultAfterConstraints.carbsRequiredText, Notification.NORMAL)
rxBus.send(EventNewNotification(carbReqLocal))
@ -353,15 +339,17 @@ class LoopPlugin @Inject constructor(
// mId allows you to update the notification later on.
mNotificationManager.notify(Constants.notificationID, builder.build())
uel.log(Action.CAREPORTAL, Sources.Loop, rh.gs(R.string.carbsreq, resultAfterConstraints.carbsReq, resultAfterConstraints.carbsReqWithin),
ValueWithUnit.Gram(resultAfterConstraints.carbsReq),
ValueWithUnit.Minute(resultAfterConstraints.carbsReqWithin))
uel.log(
Action.CAREPORTAL, Sources.Loop, rh.gs(R.string.carbsreq, resultAfterConstraints.carbsReq, resultAfterConstraints.carbsReqWithin),
ValueWithUnit.Gram(resultAfterConstraints.carbsReq),
ValueWithUnit.Minute(resultAfterConstraints.carbsReqWithin)
)
rxBus.send(EventNewOpenLoopNotification())
//only send to wear if Native notifications are turned off
if (!sp.getBoolean(R.string.key_raise_notifications_as_android_notifications, true)) {
// Send to Wear
rxBus.send(EventWearInitiateAction("changeRequest"))
sendToWear()
}
}
} else {
@ -373,7 +361,8 @@ class LoopPlugin @Inject constructor(
}
}
if (resultAfterConstraints.isChangeRequested
&& !commandQueue.bolusInQueue()) {
&& !commandQueue.bolusInQueue()
) {
val waiting = PumpEnactResult(injector)
waiting.queued = true
if (resultAfterConstraints.tempBasalRequested) lastRun.tbrSetByPump = waiting
@ -465,14 +454,28 @@ class LoopPlugin @Inject constructor(
rxBus.send(EventNewOpenLoopNotification())
// Send to Wear
rxBus.send(EventWearInitiateAction("changeRequest"))
sendToWear()
}
private fun dismissSuggestion() {
// dismiss notifications
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.cancel(Constants.notificationID)
rxBus.send(EventWearConfirmAction("cancelChangeRequest"))
rxBus.send(EventMobileToWear(EventData.CancelNotification(dateUtil.now())))
}
private fun sendToWear() {
lastRun?.let {
rxBus.send(
EventMobileToWear(
EventData.OpenLoopRequest(
rh.gs(R.string.openloop_newsuggestion),
it.constraintsProcessed.toString(),
EventData.OpenLoopRequestConfirmed(dateUtil.now())
)
)
)
}
}
override fun acceptChangeRequest() {
@ -486,9 +489,11 @@ class LoopPlugin @Inject constructor(
lastRun.lastTBRRequest = lastRun.lastAPSRun
lastRun.lastTBREnact = dateUtil.now()
lastRun.lastOpenModeAccept = dateUtil.now()
buildDeviceStatus(dateUtil, this@LoopPlugin, iobCobCalculator, profileFunction,
buildDeviceStatus(
dateUtil, this@LoopPlugin, iobCobCalculator, profileFunction,
activePlugin.activePump, receiverStatusStore, runningConfiguration,
BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION)?.also {
BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION
)?.also {
repository.insert(it)
}
sp.incInt(R.string.key_ObjectivesmanualEnacts)
@ -531,8 +536,10 @@ class LoopPlugin @Inject constructor(
commandQueue.cancelTempBasal(false, callback)
} else {
aapsLogger.debug(LTag.APS, "applyAPSRequest: Basal set correctly")
callback?.result(PumpEnactResult(injector).absolute(request.rate).duration(0)
.enacted(false).success(true).comment(R.string.basal_set_correctly))?.run()
callback?.result(
PumpEnactResult(injector).absolute(request.rate).duration(0)
.enacted(false).success(true).comment(R.string.basal_set_correctly)
)?.run()
}
} else if (request.usePercent && allowPercentage()) {
if (request.percent == 100 && request.duration == 0) {
@ -542,32 +549,52 @@ class LoopPlugin @Inject constructor(
commandQueue.cancelTempBasal(false, callback)
} else {
aapsLogger.debug(LTag.APS, "applyAPSRequest: Basal set correctly")
callback?.result(PumpEnactResult(injector).percent(request.percent).duration(0)
.enacted(false).success(true).comment(R.string.basal_set_correctly))?.run()
callback?.result(
PumpEnactResult(injector).percent(request.percent).duration(0)
.enacted(false).success(true).comment(R.string.basal_set_correctly)
)?.run()
}
} else if (activeTemp != null && activeTemp.plannedRemainingMinutes > 5 && request.duration - activeTemp.plannedRemainingMinutes < 30 && request.percent == activeTemp.convertedToPercent(now, profile)) {
} else if (activeTemp != null && activeTemp.plannedRemainingMinutes > 5 && request.duration - activeTemp.plannedRemainingMinutes < 30 && request.percent == activeTemp.convertedToPercent(
now,
profile
)
) {
aapsLogger.debug(LTag.APS, "applyAPSRequest: Temp basal set correctly")
callback?.result(PumpEnactResult(injector).percent(request.percent)
.enacted(false).success(true).duration(activeTemp.plannedRemainingMinutes)
.comment(R.string.let_temp_basal_run))?.run()
callback?.result(
PumpEnactResult(injector).percent(request.percent)
.enacted(false).success(true).duration(activeTemp.plannedRemainingMinutes)
.comment(R.string.let_temp_basal_run)
)?.run()
} else {
aapsLogger.debug(LTag.APS, "applyAPSRequest: tempBasalPercent()")
uel.log(Action.TEMP_BASAL, Sources.Loop,
uel.log(
Action.TEMP_BASAL, Sources.Loop,
ValueWithUnit.Percent(request.percent),
ValueWithUnit.Minute(request.duration))
ValueWithUnit.Minute(request.duration)
)
commandQueue.tempBasalPercent(request.percent, request.duration, false, profile, PumpSync.TemporaryBasalType.NORMAL, callback)
}
} else {
if (activeTemp != null && activeTemp.plannedRemainingMinutes > 5 && request.duration - activeTemp.plannedRemainingMinutes < 30 && abs(request.rate - activeTemp.convertedToAbsolute(now, profile)) < pump.pumpDescription.basalStep) {
if (activeTemp != null && activeTemp.plannedRemainingMinutes > 5 && request.duration - activeTemp.plannedRemainingMinutes < 30 && abs(
request.rate - activeTemp.convertedToAbsolute(
now,
profile
)
) < pump.pumpDescription.basalStep
) {
aapsLogger.debug(LTag.APS, "applyAPSRequest: Temp basal set correctly")
callback?.result(PumpEnactResult(injector).absolute(activeTemp.convertedToAbsolute(now, profile))
.enacted(false).success(true).duration(activeTemp.plannedRemainingMinutes)
.comment(R.string.let_temp_basal_run))?.run()
callback?.result(
PumpEnactResult(injector).absolute(activeTemp.convertedToAbsolute(now, profile))
.enacted(false).success(true).duration(activeTemp.plannedRemainingMinutes)
.comment(R.string.let_temp_basal_run)
)?.run()
} else {
aapsLogger.debug(LTag.APS, "applyAPSRequest: setTempBasalAbsolute()")
uel.log(Action.TEMP_BASAL, Sources.Loop,
uel.log(
Action.TEMP_BASAL, Sources.Loop,
ValueWithUnit.UnitPerHour(request.rate),
ValueWithUnit.Minute(request.duration))
ValueWithUnit.Minute(request.duration)
)
commandQueue.tempBasalAbsolute(request.rate, request.duration, false, profile, PumpSync.TemporaryBasalType.NORMAL, callback)
}
}
@ -575,15 +602,18 @@ class LoopPlugin @Inject constructor(
private fun applySMBRequest(request: APSResult, callback: Callback?) {
if (!request.bolusRequested()) {
aapsLogger.debug(LTag.APS, "No SMB requested")
return
}
val pump = activePlugin.activePump
val lastBolusTime = repository.getLastBolusRecord()?.timestamp ?: 0L
if (lastBolusTime != 0L && lastBolusTime + 3 * 60 * 1000 > System.currentTimeMillis()) {
aapsLogger.debug(LTag.APS, "SMB requested but still in 3 min interval")
callback?.result(PumpEnactResult(injector)
.comment(R.string.smb_frequency_exceeded)
.enacted(false).success(false))?.run()
callback?.result(
PumpEnactResult(injector)
.comment(R.string.smb_frequency_exceeded)
.enacted(false).success(false)
)?.run()
return
}
if (!pump.isInitialized()) {
@ -619,11 +649,11 @@ class LoopPlugin @Inject constructor(
val pump = activePlugin.activePump
disposable += repository.runTransactionForResult(InsertAndCancelCurrentOfflineEventTransaction(dateUtil.now(), T.mins(durationInMinutes.toLong()).msecs(), reason))
.subscribe({ result ->
result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated OfflineEvent $it") }
result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted OfflineEvent $it") }
}, {
aapsLogger.error(LTag.DATABASE, "Error while saving OfflineEvent", it)
})
result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated OfflineEvent $it") }
result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted OfflineEvent $it") }
}, {
aapsLogger.error(LTag.DATABASE, "Error while saving OfflineEvent", it)
})
if (pump.pumpDescription.tempBasalStyle == PumpDescription.ABSOLUTE) {
commandQueue.tempBasalAbsolute(0.0, durationInMinutes, true, profile, PumpSync.TemporaryBasalType.EMULATED_PUMP_SUSPEND, object : Callback() {
override fun run() {
@ -655,11 +685,11 @@ class LoopPlugin @Inject constructor(
override fun suspendLoop(durationInMinutes: Int) {
disposable += repository.runTransactionForResult(InsertAndCancelCurrentOfflineEventTransaction(dateUtil.now(), T.mins(durationInMinutes.toLong()).msecs(), OfflineEvent.Reason.SUSPEND))
.subscribe({ result ->
result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated OfflineEvent $it") }
result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted OfflineEvent $it") }
}, {
aapsLogger.error(LTag.DATABASE, "Error while saving OfflineEvent", it)
})
result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated OfflineEvent $it") }
result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted OfflineEvent $it") }
}, {
aapsLogger.error(LTag.DATABASE, "Error while saving OfflineEvent", it)
})
commandQueue.cancelTempBasal(true, object : Callback() {
override fun run() {
if (!result.success) {

View file

@ -7,6 +7,7 @@ import info.nightscout.androidaps.data.MealData
import info.nightscout.androidaps.extensions.convertedToAbsolute
import info.nightscout.androidaps.extensions.getPassedDurationToTimeInMinutes
import info.nightscout.androidaps.extensions.plannedRemainingMinutes
import info.nightscout.androidaps.interfaces.DetermineBasalAdapterInterface
import info.nightscout.androidaps.interfaces.GlucoseUnit
import info.nightscout.androidaps.interfaces.IobCobCalculator
import info.nightscout.androidaps.interfaces.Profile
@ -14,6 +15,7 @@ import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.plugins.aps.logger.LoggerCallback
import info.nightscout.androidaps.plugins.aps.loop.APSResult
import info.nightscout.androidaps.plugins.aps.loop.ScriptReader
import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
@ -30,7 +32,7 @@ import java.nio.charset.StandardCharsets
import javax.inject.Inject
import kotlin.math.min
class DetermineBasalAdapterAMAJS internal constructor(scriptReader: ScriptReader, injector: HasAndroidInjector) {
class DetermineBasalAdapterAMAJS internal constructor(scriptReader: ScriptReader, injector: HasAndroidInjector) : DetermineBasalAdapterInterface {
private val injector: HasAndroidInjector
@ -48,21 +50,15 @@ class DetermineBasalAdapterAMAJS internal constructor(scriptReader: ScriptReader
private var currentTemp = JSONObject()
private var autosensData = JSONObject()
var currentTempParam: String? = null
private set
var iobDataParam: String? = null
private set
var glucoseStatusParam: String? = null
private set
var profileParam: String? = null
private set
var mealDataParam: String? = null
private set
var scriptDebug = ""
private set
override var currentTempParam: String? = null
override var iobDataParam: String? = null
override var glucoseStatusParam: String? = null
override var profileParam: String? = null
override var mealDataParam: String? = null
override var scriptDebug = ""
@Suppress("SpellCheckingInspection")
operator fun invoke(): DetermineBasalResultAMA? {
override operator fun invoke(): APSResult? {
aapsLogger.debug(LTag.APS, ">>> Invoking determine_basal <<<")
aapsLogger.debug(LTag.APS, "Glucose status: " + glucoseStatus.toString().also { glucoseStatusParam = it })
aapsLogger.debug(LTag.APS, "IOB data: " + iobData.toString().also { iobDataParam = it })
@ -143,18 +139,25 @@ class DetermineBasalAdapterAMAJS internal constructor(scriptReader: ScriptReader
}
@Suppress("SpellCheckingInspection")
@Throws(JSONException::class) fun setData(profile: Profile,
maxIob: Double,
maxBasal: Double,
minBg: Double,
maxBg: Double,
targetBg: Double,
basalRate: Double,
iobArray: Array<IobTotal>,
glucoseStatus: GlucoseStatus,
mealData: MealData,
autosensDataRatio: Double,
tempTargetSet: Boolean) {
@Throws(JSONException::class)
override fun setData(
profile: Profile,
maxIob: Double,
maxBasal: Double,
minBg: Double,
maxBg: Double,
targetBg: Double,
basalRate: Double,
iobArray: Array<IobTotal>,
glucoseStatus: GlucoseStatus,
mealData: MealData,
autosensDataRatio: Double,
tempTargetSet: Boolean,
microBolusAllowed: Boolean,
uamAllowed: Boolean,
advancedFiltering: Boolean,
isSaveCgmSource: Boolean
) {
this.profile = JSONObject()
this.profile.put("max_iob", maxIob)
this.profile.put("dia", min(profile.dia, 3.0))

View file

@ -2,24 +2,22 @@ package info.nightscout.androidaps.plugins.aps.openAPSAMA
import android.os.Bundle
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.*
import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.R
import info.nightscout.androidaps.databinding.OpenapsamaFragmentBinding
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.plugins.aps.events.EventOpenAPSUpdateGui
import info.nightscout.androidaps.plugins.aps.events.EventOpenAPSUpdateResultGui
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.JSONFormatter
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import org.json.JSONArray
import org.json.JSONException
import javax.inject.Inject
@ -37,26 +35,53 @@ class OpenAPSAMAFragment : DaggerFragment() {
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var jsonFormatter: JSONFormatter
private val ID_MENU_RUN = 1
private var _binding: OpenapsamaFragmentBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
_binding = OpenapsamaFragmentBinding.inflate(inflater, container, false)
return binding.root
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
OpenapsamaFragmentBinding.inflate(inflater, container, false).also {
_binding = it
setHasOptionsMenu(true)
}.root
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.run.setOnClickListener {
openAPSAMAPlugin.invoke("OpenAPSAMA button", false)
with(binding.swipeRefresh) {
setColorSchemeColors(rh.gac(context, R.attr.colorPrimaryDark), rh.gac(context, R.attr.colorPrimary), rh.gac(context, R.attr.colorSecondary))
setOnRefreshListener {
binding.lastrun.text = rh.gs(info.nightscout.androidaps.R.string.executing)
Thread { openAPSAMAPlugin.invoke("OpenAPSAMA swiperefresh", false) }.start()
}
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
if (isResumed) {
menu.removeItem(ID_MENU_RUN)
menu.add(Menu.FIRST, ID_MENU_RUN, 0, rh.gs(R.string.openapsma_run)).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
menu.setGroupDividerEnabled(true)
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean =
when (item.itemId) {
ID_MENU_RUN -> {
binding.lastrun.text = rh.gs(R.string.executing)
Thread { openAPSAMAPlugin.invoke("OpenAPSAMA menu", false) }.start()
true
}
else -> false
}
@Synchronized
override fun onResume() {
super.onResume()
@ -65,14 +90,14 @@ class OpenAPSAMAFragment : DaggerFragment() {
.toObservable(EventOpenAPSUpdateGui::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({
updateGUI()
}, fabricPrivacy::logException)
updateGUI()
}, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventOpenAPSUpdateResultGui::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({
updateResultGUI(it.text)
}, fabricPrivacy::logException)
updateResultGUI(it.text)
}, fabricPrivacy::logException)
updateGUI()
}
@ -96,7 +121,7 @@ class OpenAPSAMAFragment : DaggerFragment() {
binding.result.text = jsonFormatter.format(lastAPSResult.json)
binding.request.text = lastAPSResult.toSpanned()
}
openAPSAMAPlugin.lastDetermineBasalAdapterAMAJS?.let { determineBasalAdapterAMAJS ->
openAPSAMAPlugin.lastDetermineBasalAdapter?.let { determineBasalAdapterAMAJS ->
binding.glucosestatus.text = jsonFormatter.format(determineBasalAdapterAMAJS.glucoseStatusParam)
binding.currenttemp.text = jsonFormatter.format(determineBasalAdapterAMAJS.currentTempParam)
try {
@ -118,6 +143,7 @@ class OpenAPSAMAFragment : DaggerFragment() {
openAPSAMAPlugin.lastAutosensResult.let {
binding.autosensdata.text = jsonFormatter.format(it.json())
}
binding.swipeRefresh.isRefreshing = false
}
private fun updateResultGUI(text: String) {
@ -131,5 +157,6 @@ class OpenAPSAMAFragment : DaggerFragment() {
binding.scriptdebugdata.text = ""
binding.request.text = ""
binding.lastrun.text = ""
binding.swipeRefresh.isRefreshing = false
}
}

View file

@ -23,7 +23,7 @@ import info.nightscout.androidaps.utils.HardLimits
import info.nightscout.androidaps.utils.Profiler
import info.nightscout.androidaps.utils.Round
import info.nightscout.androidaps.extensions.target
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.interfaces.ResourceHelper
import org.json.JSONException
import javax.inject.Inject
import javax.inject.Singleton
@ -60,8 +60,8 @@ class OpenAPSAMAPlugin @Inject constructor(
// last values
override var lastAPSRun: Long = 0
override var lastAPSResult: DetermineBasalResultAMA? = null
var lastDetermineBasalAdapterAMAJS: DetermineBasalAdapterAMAJS? = null
var lastAutosensResult: AutosensResult = AutosensResult()
override var lastDetermineBasalAdapter: DetermineBasalAdapterInterface? = null
override var lastAutosensResult: AutosensResult = AutosensResult()
override fun specialEnableCondition(): Boolean {
return try {
@ -158,7 +158,7 @@ class OpenAPSAMAPlugin @Inject constructor(
// Fix bug determine basal
if (determineBasalResultAMA == null) {
aapsLogger.error(LTag.APS, "SMB calculation returned null")
lastDetermineBasalAdapterAMAJS = null
lastDetermineBasalAdapter = null
lastAPSResult = null
lastAPSRun = 0
} else {
@ -167,8 +167,8 @@ class OpenAPSAMAPlugin @Inject constructor(
val now = System.currentTimeMillis()
determineBasalResultAMA.json?.put("timestamp", dateUtil.toISOString(now))
determineBasalResultAMA.inputConstraints = inputConstraints
lastDetermineBasalAdapterAMAJS = determineBasalAdapterAMAJS
lastAPSResult = determineBasalResultAMA
lastDetermineBasalAdapter = determineBasalAdapterAMAJS
lastAPSResult = determineBasalResultAMA as DetermineBasalResultAMA
lastAPSRun = now
}
rxBus.send(EventOpenAPSUpdateGui())

View file

@ -7,19 +7,16 @@ import info.nightscout.androidaps.data.MealData
import info.nightscout.androidaps.extensions.convertedToAbsolute
import info.nightscout.androidaps.extensions.getPassedDurationToTimeInMinutes
import info.nightscout.androidaps.extensions.plannedRemainingMinutes
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.GlucoseUnit
import info.nightscout.androidaps.interfaces.IobCobCalculator
import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.interfaces.*
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.plugins.aps.logger.LoggerCallback
import info.nightscout.androidaps.plugins.aps.loop.APSResult
import info.nightscout.androidaps.plugins.aps.loop.ScriptReader
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus
import info.nightscout.shared.SafeParse
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONArray
import org.json.JSONException
@ -31,7 +28,7 @@ import java.lang.reflect.InvocationTargetException
import java.nio.charset.StandardCharsets
import javax.inject.Inject
class DetermineBasalAdapterSMBJS internal constructor(private val scriptReader: ScriptReader, private val injector: HasAndroidInjector) {
class DetermineBasalAdapterSMBJS internal constructor(private val scriptReader: ScriptReader, private val injector: HasAndroidInjector) : DetermineBasalAdapterInterface {
@Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var constraintChecker: ConstraintChecker
@ -51,21 +48,16 @@ class DetermineBasalAdapterSMBJS internal constructor(private val scriptReader:
private var smbAlwaysAllowed = false
private var currentTime: Long = 0
private var saveCgmSource = false
var currentTempParam: String? = null
private set
var iobDataParam: String? = null
private set
var glucoseStatusParam: String? = null
private set
var profileParam: String? = null
private set
var mealDataParam: String? = null
private set
var scriptDebug = ""
private set
override var currentTempParam: String? = null
override var iobDataParam: String? = null
override var glucoseStatusParam: String? = null
override var profileParam: String? = null
override var mealDataParam: String? = null
override var scriptDebug = ""
@Suppress("SpellCheckingInspection")
operator fun invoke(): DetermineBasalResultSMB? {
override operator fun invoke(): APSResult? {
aapsLogger.debug(LTag.APS, ">>> Invoking determine_basal <<<")
aapsLogger.debug(LTag.APS, "Glucose status: " + mGlucoseStatus.toString().also { glucoseStatusParam = it })
aapsLogger.debug(LTag.APS, "IOB data: " + iobData.toString().also { iobDataParam = it })
@ -155,22 +147,24 @@ class DetermineBasalAdapterSMBJS internal constructor(private val scriptReader:
return determineBasalResultSMB
}
@Suppress("SpellCheckingInspection") fun setData(profile: Profile,
maxIob: Double,
maxBasal: Double,
minBg: Double,
maxBg: Double,
targetBg: Double,
basalRate: Double,
iobArray: Array<IobTotal>,
glucoseStatus: GlucoseStatus,
mealData: MealData,
autosensDataRatio: Double,
tempTargetSet: Boolean,
microBolusAllowed: Boolean,
uamAllowed: Boolean,
advancedFiltering: Boolean,
isSaveCgmSource: Boolean
@Suppress("SpellCheckingInspection")
override fun setData(
profile: Profile,
maxIob: Double,
maxBasal: Double,
minBg: Double,
maxBg: Double,
targetBg: Double,
basalRate: Double,
iobArray: Array<IobTotal>,
glucoseStatus: GlucoseStatus,
mealData: MealData,
autosensDataRatio: Double,
tempTargetSet: Boolean,
microBolusAllowed: Boolean,
uamAllowed: Boolean,
advancedFiltering: Boolean,
isSaveCgmSource: Boolean
) {
val pump = activePlugin.activePump
val pumpBolusStep = pump.pumpDescription.bolusStep

View file

@ -10,6 +10,7 @@ class DetermineBasalResultSMB private constructor(injector: HasAndroidInjector)
private var eventualBG = 0.0
private var snoozeBG = 0.0
var variableSens: Double? = null
internal constructor(injector: HasAndroidInjector, result: JSONObject) : this(injector) {
date = dateUtil.now()
@ -50,6 +51,7 @@ class DetermineBasalResultSMB private constructor(injector: HasAndroidInjector)
aapsLogger.error(LTag.APS, "Error parsing 'deliverAt' date: $date", e)
}
}
if (result.has("variable_sens")) variableSens = result.getDouble("variable_sens")
} catch (e: JSONException) {
aapsLogger.error(LTag.APS, "Error parsing determine-basal result JSON", e)
}

View file

@ -3,24 +3,23 @@ package info.nightscout.androidaps.plugins.aps.openAPSSMB
import android.annotation.SuppressLint
import android.os.Bundle
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.*
import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.R
import info.nightscout.androidaps.databinding.OpenapsamaFragmentBinding
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.plugins.aps.events.EventOpenAPSUpdateGui
import info.nightscout.androidaps.plugins.aps.events.EventOpenAPSUpdateResultGui
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.JSONFormatter
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import org.json.JSONArray
import org.json.JSONException
import javax.inject.Inject
@ -34,9 +33,12 @@ class OpenAPSSMBFragment : DaggerFragment() {
@Inject lateinit var rxBus: RxBus
@Inject lateinit var rh: ResourceHelper
@Inject lateinit var fabricPrivacy: FabricPrivacy
@Inject lateinit var openAPSSMBPlugin: OpenAPSSMBPlugin
@Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var jsonFormatter: JSONFormatter
private lateinit var refreshDialog: Runnable
private val ID_MENU_RUN = 1
private var _binding: OpenapsamaFragmentBinding? = null
@ -44,20 +46,44 @@ class OpenAPSSMBFragment : DaggerFragment() {
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
_binding = OpenapsamaFragmentBinding.inflate(inflater, container, false)
return binding.root
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
OpenapsamaFragmentBinding.inflate(inflater, container, false).also {
_binding = it
setHasOptionsMenu(true)
}.root
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.run.setOnClickListener {
openAPSSMBPlugin.invoke("OpenAPSSMB button", false)
with(binding.swipeRefresh) {
setColorSchemeColors(rh.gac(context, R.attr.colorPrimaryDark), rh.gac(context, R.attr.colorPrimary), rh.gac(context, R.attr.colorSecondary))
setOnRefreshListener {
binding.lastrun.text = rh.gs(info.nightscout.androidaps.R.string.executing)
Thread { activePlugin.activeAPS.invoke("OpenAPSSMB swiperefresh", false) }.start()
}
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
if (isResumed) {
menu.removeItem(ID_MENU_RUN)
menu.add(Menu.FIRST, ID_MENU_RUN, 0, rh.gs(R.string.openapsma_run)).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
menu.setGroupDividerEnabled(true)
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean =
when (item.itemId) {
ID_MENU_RUN -> {
binding.lastrun.text = rh.gs(R.string.executing)
Thread { activePlugin.activeAPS.invoke("OpenAPSSMB menu", false) }.start()
true
}
else -> false
}
@Synchronized
override fun onResume() {
super.onResume()
@ -65,14 +91,14 @@ class OpenAPSSMBFragment : DaggerFragment() {
.toObservable(EventOpenAPSUpdateGui::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({
updateGUI()
}, fabricPrivacy::logException)
updateGUI()
}, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventOpenAPSUpdateResultGui::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({
updateResultGUI(it.text)
}, fabricPrivacy::logException)
updateResultGUI(it.text)
}, fabricPrivacy::logException)
updateGUI()
}
@ -92,11 +118,12 @@ class OpenAPSSMBFragment : DaggerFragment() {
@Synchronized
fun updateGUI() {
if (_binding == null) return
val openAPSSMBPlugin = activePlugin.activeAPS
openAPSSMBPlugin.lastAPSResult?.let { lastAPSResult ->
binding.result.text = jsonFormatter.format(lastAPSResult.json)
binding.request.text = lastAPSResult.toSpanned()
}
openAPSSMBPlugin.lastDetermineBasalAdapterSMBJS?.let { determineBasalAdapterSMBJS ->
openAPSSMBPlugin.lastDetermineBasalAdapter?.let { determineBasalAdapterSMBJS ->
binding.glucosestatus.text = jsonFormatter.format(determineBasalAdapterSMBJS.glucoseStatusParam)
binding.currenttemp.text = jsonFormatter.format(determineBasalAdapterSMBJS.currentTempParam)
try {
@ -121,6 +148,7 @@ class OpenAPSSMBFragment : DaggerFragment() {
openAPSSMBPlugin.lastAutosensResult.let {
binding.autosensdata.text = jsonFormatter.format(it.json())
}
binding.swipeRefresh.isRefreshing = false
}
@Synchronized
@ -136,5 +164,6 @@ class OpenAPSSMBFragment : DaggerFragment() {
binding.scriptdebugdata.text = ""
binding.request.text = ""
binding.lastrun.text = ""
binding.swipeRefresh.isRefreshing = false
}
}

View file

@ -10,8 +10,6 @@ import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.ValueWrapper
import info.nightscout.androidaps.extensions.target
import info.nightscout.androidaps.interfaces.*
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.plugins.aps.events.EventOpenAPSUpdateGui
import info.nightscout.androidaps.plugins.aps.events.EventOpenAPSUpdateResultGui
import info.nightscout.androidaps.plugins.aps.loop.ScriptReader
@ -23,7 +21,9 @@ import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.HardLimits
import info.nightscout.androidaps.utils.Profiler
import info.nightscout.androidaps.utils.Round
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import javax.inject.Inject
import javax.inject.Singleton
@ -37,7 +37,7 @@ class OpenAPSSMBPlugin @Inject constructor(
private val constraintChecker: ConstraintChecker,
rh: ResourceHelper,
private val profileFunction: ProfileFunction,
private val context: Context,
val context: Context,
private val activePlugin: ActivePlugin,
private val iobCobCalculator: IobCobCalculator,
private val hardLimits: HardLimits,
@ -46,23 +46,24 @@ class OpenAPSSMBPlugin @Inject constructor(
private val dateUtil: DateUtil,
private val repository: AppRepository,
private val glucoseStatusProvider: GlucoseStatusProvider
) : PluginBase(PluginDescription()
.mainType(PluginType.APS)
.fragmentClass(OpenAPSSMBFragment::class.java.name)
.pluginIcon(R.drawable.ic_generic_icon)
.pluginName(R.string.openapssmb)
.shortName(R.string.smb_shortname)
.preferencesId(R.xml.pref_openapssmb)
.description(R.string.description_smb)
.setDefault(),
) : PluginBase(
PluginDescription()
.mainType(PluginType.APS)
.fragmentClass(OpenAPSSMBFragment::class.java.name)
.pluginIcon(R.drawable.ic_generic_icon)
.pluginName(R.string.openapssmb)
.shortName(R.string.smb_shortname)
.preferencesId(R.xml.pref_openapssmb)
.description(R.string.description_smb)
.setDefault(),
aapsLogger, rh, injector
), APS, Constraints {
// last values
override var lastAPSRun: Long = 0
override var lastAPSResult: DetermineBasalResultSMB? = null
var lastDetermineBasalAdapterSMBJS: DetermineBasalAdapterSMBJS? = null
var lastAutosensResult = AutosensResult()
override var lastDetermineBasalAdapter: DetermineBasalAdapterInterface? = null
override var lastAutosensResult = AutosensResult()
override fun specialEnableCondition(): Boolean {
return try {
@ -120,15 +121,34 @@ class OpenAPSSMBPlugin @Inject constructor(
}.value()
var minBg = hardLimits.verifyHardLimits(Round.roundTo(profile.getTargetLowMgdl(), 0.1), R.string.profile_low_target, HardLimits.VERY_HARD_LIMIT_MIN_BG[0], HardLimits.VERY_HARD_LIMIT_MIN_BG[1])
var maxBg = hardLimits.verifyHardLimits(Round.roundTo(profile.getTargetHighMgdl(), 0.1), R.string.profile_high_target, HardLimits.VERY_HARD_LIMIT_MAX_BG[0], HardLimits.VERY_HARD_LIMIT_MAX_BG[1])
var maxBg =
hardLimits.verifyHardLimits(Round.roundTo(profile.getTargetHighMgdl(), 0.1), R.string.profile_high_target, HardLimits.VERY_HARD_LIMIT_MAX_BG[0], HardLimits.VERY_HARD_LIMIT_MAX_BG[1])
var targetBg = hardLimits.verifyHardLimits(profile.getTargetMgdl(), R.string.temp_target_value, HardLimits.VERY_HARD_LIMIT_TARGET_BG[0], HardLimits.VERY_HARD_LIMIT_TARGET_BG[1])
var isTempTarget = false
val tempTarget = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet()
if (tempTarget is ValueWrapper.Existing) {
isTempTarget = true
minBg = hardLimits.verifyHardLimits(tempTarget.value.lowTarget, R.string.temp_target_low_target, HardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[0].toDouble(), HardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[1].toDouble())
maxBg = hardLimits.verifyHardLimits(tempTarget.value.highTarget, R.string.temp_target_high_target, HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[0].toDouble(), HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[1].toDouble())
targetBg = hardLimits.verifyHardLimits(tempTarget.value.target(), R.string.temp_target_value, HardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[0].toDouble(), HardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[1].toDouble())
minBg =
hardLimits.verifyHardLimits(
tempTarget.value.lowTarget,
R.string.temp_target_low_target,
HardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[0].toDouble(),
HardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[1].toDouble()
)
maxBg =
hardLimits.verifyHardLimits(
tempTarget.value.highTarget,
R.string.temp_target_high_target,
HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[0].toDouble(),
HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[1].toDouble()
)
targetBg =
hardLimits.verifyHardLimits(
tempTarget.value.target(),
R.string.temp_target_value,
HardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[0].toDouble(),
HardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[1].toDouble()
)
}
if (!hardLimits.checkHardLimits(profile.dia, R.string.profile_dia, hardLimits.minDia(), hardLimits.maxDia())) return
if (!hardLimits.checkHardLimits(profile.getIcTimeFromMidnight(Profile.secondsFromMidnight()), R.string.profile_carbs_ratio_value, hardLimits.minIC(), hardLimits.maxIC())) return
@ -165,8 +185,9 @@ class OpenAPSSMBPlugin @Inject constructor(
profiler.log(LTag.APS, "SMB data gathering", start)
start = System.currentTimeMillis()
DetermineBasalAdapterSMBJS(ScriptReader(context), injector).also { determineBasalAdapterSMBJS ->
determineBasalAdapterSMBJS.setData(profile, maxIob, maxBasal, minBg, maxBg, targetBg,
provideDetermineBasalAdapter().also { determineBasalAdapterSMBJS ->
determineBasalAdapterSMBJS.setData(
profile, maxIob, maxBasal, minBg, maxBg, targetBg,
activePlugin.activePump.baseBasalRate,
iobArray,
glucoseStatus,
@ -176,24 +197,26 @@ class OpenAPSSMBPlugin @Inject constructor(
smbAllowed.value(),
uam.value(),
advancedFiltering.value(),
activePlugin.activeBgSource.javaClass.simpleName == "DexcomPlugin")
activePlugin.activeBgSource.javaClass.simpleName == "DexcomPlugin"
)
val now = System.currentTimeMillis()
val determineBasalResultSMB = determineBasalAdapterSMBJS.invoke()
profiler.log(LTag.APS, "SMB calculation", start)
if (determineBasalResultSMB == null) {
aapsLogger.error(LTag.APS, "SMB calculation returned null")
lastDetermineBasalAdapterSMBJS = null
lastDetermineBasalAdapter = null
lastAPSResult = null
lastAPSRun = 0
} else {
// TODO still needed with oref1?
// Fix bug determine basal
if (determineBasalResultSMB.rate == 0.0 && determineBasalResultSMB.duration == 0 && iobCobCalculator.getTempBasalIncludingConvertedExtended(dateUtil.now()) == null) determineBasalResultSMB.tempBasalRequested = false
if (determineBasalResultSMB.rate == 0.0 && determineBasalResultSMB.duration == 0 && iobCobCalculator.getTempBasalIncludingConvertedExtended(dateUtil.now()) == null) determineBasalResultSMB.tempBasalRequested =
false
determineBasalResultSMB.iob = iobArray[0]
determineBasalResultSMB.json?.put("timestamp", dateUtil.toISOString(now))
determineBasalResultSMB.inputConstraints = inputConstraints
lastDetermineBasalAdapterSMBJS = determineBasalAdapterSMBJS
lastAPSResult = determineBasalResultSMB
lastDetermineBasalAdapter = determineBasalAdapterSMBJS
lastAPSResult = determineBasalResultSMB as DetermineBasalResultSMB
lastAPSRun = now
}
}
@ -204,4 +227,6 @@ class OpenAPSSMBPlugin @Inject constructor(
value.set(aapsLogger, false)
return value
}
fun provideDetermineBasalAdapter(): DetermineBasalAdapterInterface = DetermineBasalAdapterSMBJS(ScriptReader(context), injector)
}

View file

@ -0,0 +1,313 @@
package info.nightscout.androidaps.plugins.aps.openAPSSMBDynamicISF
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R
import info.nightscout.androidaps.data.IobTotal
import info.nightscout.androidaps.data.MealData
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.extensions.convertedToAbsolute
import info.nightscout.androidaps.extensions.getPassedDurationToTimeInMinutes
import info.nightscout.androidaps.extensions.plannedRemainingMinutes
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.GlucoseUnit
import info.nightscout.androidaps.interfaces.IobCobCalculator
import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.plugins.aps.logger.LoggerCallback
import info.nightscout.androidaps.plugins.aps.loop.ScriptReader
import info.nightscout.androidaps.interfaces.DetermineBasalAdapterInterface
import info.nightscout.androidaps.plugins.aps.openAPSSMB.DetermineBasalResultSMB
import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.stats.TddCalculator
import info.nightscout.shared.SafeParse
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import org.mozilla.javascript.*
import org.mozilla.javascript.Function
import java.io.IOException
import java.lang.reflect.InvocationTargetException
import java.nio.charset.StandardCharsets
import javax.inject.Inject
class DetermineBasalAdapterSMBDynamicISFJS internal constructor(private val scriptReader: ScriptReader, private val injector: HasAndroidInjector) : DetermineBasalAdapterInterface {
@Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var constraintChecker: ConstraintChecker
@Inject lateinit var sp: SP
@Inject lateinit var rh: ResourceHelper
@Inject lateinit var profileFunction: ProfileFunction
@Inject lateinit var iobCobCalculator: IobCobCalculator
@Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var repository: AppRepository
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var tddCalculator: TddCalculator
private var profile = JSONObject()
private var mGlucoseStatus = JSONObject()
private var iobData: JSONArray? = null
private var mealData = JSONObject()
private var currentTemp = JSONObject()
private var autosensData = JSONObject()
private var microBolusAllowed = false
private var smbAlwaysAllowed = false
private var currentTime: Long = 0
private var saveCgmSource = false
override var currentTempParam: String? = null
override var iobDataParam: String? = null
override var glucoseStatusParam: String? = null
override var profileParam: String? = null
override var mealDataParam: String? = null
override var scriptDebug = ""
@Suppress("SpellCheckingInspection")
override operator fun invoke(): DetermineBasalResultSMB? {
aapsLogger.debug(LTag.APS, ">>> Invoking determine_basal <<<")
aapsLogger.debug(LTag.APS, "Glucose status: " + mGlucoseStatus.toString().also { glucoseStatusParam = it })
aapsLogger.debug(LTag.APS, "IOB data: " + iobData.toString().also { iobDataParam = it })
aapsLogger.debug(LTag.APS, "Current temp: " + currentTemp.toString().also { currentTempParam = it })
aapsLogger.debug(LTag.APS, "Profile: " + profile.toString().also { profileParam = it })
aapsLogger.debug(LTag.APS, "Meal data: " + mealData.toString().also { mealDataParam = it })
aapsLogger.debug(LTag.APS, "Autosens data: $autosensData")
aapsLogger.debug(LTag.APS, "Reservoir data: " + "undefined")
aapsLogger.debug(LTag.APS, "MicroBolusAllowed: $microBolusAllowed")
aapsLogger.debug(LTag.APS, "SMBAlwaysAllowed: $smbAlwaysAllowed")
aapsLogger.debug(LTag.APS, "CurrentTime: $currentTime")
aapsLogger.debug(LTag.APS, "isSaveCgmSource: $saveCgmSource")
var determineBasalResultSMB: DetermineBasalResultSMB? = null
val rhino = Context.enter()
val scope: Scriptable = rhino.initStandardObjects()
// Turn off optimization to make Rhino Android compatible
rhino.optimizationLevel = -1
try {
//register logger callback for console.log and console.error
ScriptableObject.defineClass(scope, LoggerCallback::class.java)
val myLogger = rhino.newObject(scope, "LoggerCallback", null)
scope.put("console2", scope, myLogger)
rhino.evaluateString(scope, readFile("OpenAPSAMA/loggerhelper.js"), "JavaScript", 0, null)
//set module parent
rhino.evaluateString(scope, "var module = {\"parent\":Boolean(1)};", "JavaScript", 0, null)
rhino.evaluateString(scope, "var round_basal = function round_basal(basal, profile) { return basal; };", "JavaScript", 0, null)
rhino.evaluateString(scope, "require = function() {return round_basal;};", "JavaScript", 0, null)
//generate functions "determine_basal" and "setTempBasal"
rhino.evaluateString(scope, readFile("OpenAPSSMBDynamicISF/determine-basal.js"), "JavaScript", 0, null)
rhino.evaluateString(scope, readFile("OpenAPSSMB/basal-set-temp.js"), "setTempBasal.js", 0, null)
val determineBasalObj = scope["determine_basal", scope]
val setTempBasalFunctionsObj = scope["tempBasalFunctions", scope]
//call determine-basal
if (determineBasalObj is Function && setTempBasalFunctionsObj is NativeObject) {
//prepare parameters
val params = arrayOf(
makeParam(mGlucoseStatus, rhino, scope),
makeParam(currentTemp, rhino, scope),
makeParamArray(iobData, rhino, scope),
makeParam(profile, rhino, scope),
makeParam(autosensData, rhino, scope),
makeParam(mealData, rhino, scope),
setTempBasalFunctionsObj,
java.lang.Boolean.valueOf(microBolusAllowed),
makeParam(null, rhino, scope), // reservoir data as undefined
java.lang.Long.valueOf(currentTime),
java.lang.Boolean.valueOf(saveCgmSource)
)
val jsResult = determineBasalObj.call(rhino, scope, scope, params) as NativeObject
scriptDebug = LoggerCallback.scriptDebug
// Parse the jsResult object to a JSON-String
val result = NativeJSON.stringify(rhino, scope, jsResult, null, null).toString()
aapsLogger.debug(LTag.APS, "Result: $result")
try {
val resultJson = JSONObject(result)
determineBasalResultSMB = DetermineBasalResultSMB(injector, resultJson)
} catch (e: JSONException) {
aapsLogger.error(LTag.APS, "Unhandled exception", e)
}
} else {
aapsLogger.error(LTag.APS, "Problem loading JS Functions")
}
} catch (e: IOException) {
aapsLogger.error(LTag.APS, "IOException")
} catch (e: RhinoException) {
aapsLogger.error(LTag.APS, "RhinoException: (" + e.lineNumber() + "," + e.columnNumber() + ") " + e.toString())
} catch (e: IllegalAccessException) {
aapsLogger.error(LTag.APS, e.toString())
} catch (e: InstantiationException) {
aapsLogger.error(LTag.APS, e.toString())
} catch (e: InvocationTargetException) {
aapsLogger.error(LTag.APS, e.toString())
} finally {
Context.exit()
}
glucoseStatusParam = mGlucoseStatus.toString()
iobDataParam = iobData.toString()
currentTempParam = currentTemp.toString()
profileParam = profile.toString()
mealDataParam = mealData.toString()
return determineBasalResultSMB
}
@Suppress("SpellCheckingInspection")
override fun setData(
profile: Profile,
maxIob: Double,
maxBasal: Double,
minBg: Double,
maxBg: Double,
targetBg: Double,
basalRate: Double,
iobArray: Array<IobTotal>,
glucoseStatus: GlucoseStatus,
mealData: MealData,
autosensDataRatio: Double,
tempTargetSet: Boolean,
microBolusAllowed: Boolean,
uamAllowed: Boolean,
advancedFiltering: Boolean,
isSaveCgmSource: Boolean
) {
val pump = activePlugin.activePump
val pumpBolusStep = pump.pumpDescription.bolusStep
this.profile.put("max_iob", maxIob)
//mProfile.put("dia", profile.getDia());
this.profile.put("type", "current")
this.profile.put("max_daily_basal", profile.getMaxDailyBasal())
this.profile.put("max_basal", maxBasal)
this.profile.put("min_bg", minBg)
this.profile.put("max_bg", maxBg)
this.profile.put("target_bg", targetBg)
this.profile.put("carb_ratio", profile.getIc())
this.profile.put("sens", profile.getIsfMgdl())
this.profile.put("max_daily_safety_multiplier", sp.getInt(R.string.key_openapsama_max_daily_safety_multiplier, 3))
this.profile.put("current_basal_safety_multiplier", sp.getDouble(R.string.key_openapsama_current_basal_safety_multiplier, 4.0))
this.profile.put("lgsThreshold", Profile.toMgdl(sp.getDouble(R.string.key_lgs_threshold, 65.0)))
val insulin = activePlugin.activeInsulin
val insulinType = insulin.friendlyName
val insulinPeak = insulin.peak
//mProfile.put("high_temptarget_raises_sensitivity", SP.getBoolean(R.string.key_high_temptarget_raises_sensitivity, SMBDefaults.high_temptarget_raises_sensitivity));
this.profile.put("high_temptarget_raises_sensitivity", sp.getBoolean(R.string.key_high_temptarget_raises_sensitivity, SMBDefaults.high_temptarget_raises_sensitivity))
//mProfile.put("low_temptarget_lowers_sensitivity", SP.getBoolean(R.string.key_low_temptarget_lowers_sensitivity, SMBDefaults.low_temptarget_lowers_sensitivity));
this.profile.put("low_temptarget_lowers_sensitivity", sp.getBoolean(R.string.key_low_temptarget_lowers_sensitivity, SMBDefaults.low_temptarget_lowers_sensitivity))
this.profile.put("sensitivity_raises_target", sp.getBoolean(R.string.key_sensitivity_raises_target, SMBDefaults.sensitivity_raises_target))
this.profile.put("resistance_lowers_target", sp.getBoolean(R.string.key_resistance_lowers_target, SMBDefaults.resistance_lowers_target))
this.profile.put("adv_target_adjustments", SMBDefaults.adv_target_adjustments)
this.profile.put("exercise_mode", SMBDefaults.exercise_mode)
this.profile.put("half_basal_exercise_target", SMBDefaults.half_basal_exercise_target)
this.profile.put("maxCOB", SMBDefaults.maxCOB)
this.profile.put("skip_neutral_temps", pump.setNeutralTempAtFullHour())
// min_5m_carbimpact is not used within SMB determinebasal
//if (mealData.usedMinCarbsImpact > 0) {
// mProfile.put("min_5m_carbimpact", mealData.usedMinCarbsImpact);
//} else {
// mProfile.put("min_5m_carbimpact", SP.getDouble(R.string.key_openapsama_min_5m_carbimpact, SMBDefaults.min_5m_carbimpact));
//}
this.profile.put("remainingCarbsCap", SMBDefaults.remainingCarbsCap)
this.profile.put("enableUAM", uamAllowed)
this.profile.put("A52_risk_enable", SMBDefaults.A52_risk_enable)
val smbEnabled = sp.getBoolean(R.string.key_use_smb, false)
this.profile.put("SMBInterval", sp.getInt(R.string.key_smbinterval, SMBDefaults.SMBInterval))
this.profile.put("enableSMB_with_COB", smbEnabled && sp.getBoolean(R.string.key_enableSMB_with_COB, false))
this.profile.put("enableSMB_with_temptarget", smbEnabled && sp.getBoolean(R.string.key_enableSMB_with_temptarget, false))
this.profile.put("allowSMB_with_high_temptarget", smbEnabled && sp.getBoolean(R.string.key_allowSMB_with_high_temptarget, false))
this.profile.put("enableSMB_always", smbEnabled && sp.getBoolean(R.string.key_enableSMB_always, false) && advancedFiltering)
this.profile.put("enableSMB_after_carbs", smbEnabled && sp.getBoolean(R.string.key_enableSMB_after_carbs, false) && advancedFiltering)
this.profile.put("maxSMBBasalMinutes", sp.getInt(R.string.key_smbmaxminutes, SMBDefaults.maxSMBBasalMinutes))
this.profile.put("maxUAMSMBBasalMinutes", sp.getInt(R.string.key_uamsmbmaxminutes, SMBDefaults.maxUAMSMBBasalMinutes))
this.profile.put("DynISFAdjust", SafeParse.stringToDouble(sp.getString(R.string.key_DynISFAdjust, "100")))
this.profile.put("insulinType", insulinType)
this.profile.put("insulinPeak", insulinPeak)
this.profile.put("maxUAMSMBBasalMinutes", sp.getInt(R.string.key_uamsmbmaxminutes, SMBDefaults.maxUAMSMBBasalMinutes))
//set the min SMB amount to be the amount set by the pump.
this.profile.put("bolus_increment", pumpBolusStep)
this.profile.put("carbsReqThreshold", sp.getInt(R.string.key_carbsReqThreshold, SMBDefaults.carbsReqThreshold))
this.profile.put("current_basal", basalRate)
this.profile.put("temptargetSet", tempTargetSet)
this.profile.put("autosens_max", SafeParse.stringToDouble(sp.getString(R.string.key_openapsama_autosens_max, "1.2")))
this.profile.put("autosens_min", SafeParse.stringToDouble(sp.getString(R.string.key_openapsama_autosens_min, "0.8")))
this.profile.put("openapsama_useautosens", sp.getBoolean(R.string.key_openapsama_useautosens, false))
//set the min SMB amount to be the amount set by the pump.
if (profileFunction.getUnits() == GlucoseUnit.MMOL) {
this.profile.put("out_units", "mmol/L")
}
val now = System.currentTimeMillis()
val tb = iobCobCalculator.getTempBasalIncludingConvertedExtended(now)
currentTemp.put("temp", "absolute")
currentTemp.put("duration", tb?.plannedRemainingMinutes ?: 0)
currentTemp.put("rate", tb?.convertedToAbsolute(now, profile) ?: 0.0)
// as we have non default temps longer than 30 mintues
if (tb != null) currentTemp.put("minutesrunning", tb.getPassedDurationToTimeInMinutes(now))
iobData = iobCobCalculator.convertToJSONArray(iobArray)
mGlucoseStatus.put("glucose", glucoseStatus.glucose)
mGlucoseStatus.put("noise", glucoseStatus.noise)
if (sp.getBoolean(R.string.key_always_use_shortavg, false)) {
mGlucoseStatus.put("delta", glucoseStatus.shortAvgDelta)
} else {
mGlucoseStatus.put("delta", glucoseStatus.delta)
}
mGlucoseStatus.put("short_avgdelta", glucoseStatus.shortAvgDelta)
mGlucoseStatus.put("long_avgdelta", glucoseStatus.longAvgDelta)
mGlucoseStatus.put("date", glucoseStatus.date)
this.mealData.put("carbs", mealData.carbs)
this.mealData.put("mealCOB", mealData.mealCOB)
this.mealData.put("slopeFromMaxDeviation", mealData.slopeFromMaxDeviation)
this.mealData.put("slopeFromMinDeviation", mealData.slopeFromMinDeviation)
this.mealData.put("lastBolusTime", mealData.lastBolusTime)
this.mealData.put("lastCarbTime", mealData.lastCarbTime)
this.mealData.put("TDDAIMI1", tddCalculator.averageTDD(tddCalculator.calculate(1))?.totalAmount)
this.mealData.put("TDDAIMI7", tddCalculator.averageTDD(tddCalculator.calculate(7))?.totalAmount)
this.mealData.put("TDDLast4", tddCalculator.calculateDaily(-4, 0).totalAmount)
this.mealData.put("TDD4to8", tddCalculator.calculateDaily(-8, -4).totalAmount)
this.mealData.put("TDD24", tddCalculator.calculateDaily(-24, 0).totalAmount)
if (constraintChecker.isAutosensModeEnabled().value()) {
autosensData.put("ratio", autosensDataRatio)
} else {
autosensData.put("ratio", 1.0)
}
this.microBolusAllowed = microBolusAllowed
smbAlwaysAllowed = advancedFiltering
currentTime = now
saveCgmSource = isSaveCgmSource
}
private fun makeParam(jsonObject: JSONObject?, rhino: Context, scope: Scriptable): Any {
return if (jsonObject == null) Undefined.instance
else NativeJSON.parse(rhino, scope, jsonObject.toString()) { _: Context?, _: Scriptable?, _: Scriptable?, objects: Array<Any?> -> objects[1] }
}
private fun makeParamArray(jsonArray: JSONArray?, rhino: Context, scope: Scriptable): Any {
return NativeJSON.parse(rhino, scope, jsonArray.toString()) { _: Context?, _: Scriptable?, _: Scriptable?, objects: Array<Any?> -> objects[1] }
}
@Throws(IOException::class) private fun readFile(filename: String): String {
val bytes = scriptReader.readFile(filename)
var string = String(bytes, StandardCharsets.UTF_8)
if (string.startsWith("#!/usr/bin/env node")) {
string = string.substring(20)
}
return string
}
init {
injector.androidInjector().inject(this)
}
}

View file

@ -0,0 +1,76 @@
package info.nightscout.androidaps.plugins.aps.openAPSSMBDynamicISF
import android.content.Context
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R
import info.nightscout.androidaps.annotations.OpenForTesting
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.IobCobCalculator
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.plugins.aps.loop.ScriptReader
import info.nightscout.androidaps.interfaces.DetermineBasalAdapterInterface
import info.nightscout.androidaps.plugins.aps.openAPSSMB.OpenAPSSMBPlugin
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProvider
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.HardLimits
import info.nightscout.androidaps.utils.Profiler
import info.nightscout.androidaps.interfaces.BuildHelper
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.sharedPreferences.SP
import javax.inject.Inject
import javax.inject.Singleton
@OpenForTesting
@Singleton
class OpenAPSSMBDynamicISFPlugin @Inject constructor(
injector: HasAndroidInjector,
aapsLogger: AAPSLogger,
rxBus: RxBus,
constraintChecker: ConstraintChecker,
rh: ResourceHelper,
profileFunction: ProfileFunction,
context: Context,
activePlugin: ActivePlugin,
iobCobCalculator: IobCobCalculator,
hardLimits: HardLimits,
profiler: Profiler,
sp: SP,
dateUtil: DateUtil,
repository: AppRepository,
glucoseStatusProvider: GlucoseStatusProvider,
private val buildHelper: BuildHelper
) : OpenAPSSMBPlugin(
injector,
aapsLogger,
rxBus,
constraintChecker,
rh,
profileFunction,
context,
activePlugin,
iobCobCalculator,
hardLimits,
profiler,
sp,
dateUtil,
repository,
glucoseStatusProvider
) {
init {
pluginDescription
.pluginName(R.string.openaps_smb_dynamic_isf)
.description(R.string.description_smb_dynamic_isf)
.shortName(R.string.dynisf_shortname)
.preferencesId(R.xml.pref_openapssmbdynamicisf)
.setDefault(false)
}
override fun specialEnableCondition(): Boolean = buildHelper.isEngineeringMode() && buildHelper.isDev()
override fun provideDetermineBasalAdapter(): DetermineBasalAdapterInterface = DetermineBasalAdapterSMBDynamicISFJS(ScriptReader(context), injector)
}

View file

@ -10,23 +10,26 @@ import android.widget.*
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.R
import info.nightscout.androidaps.activities.PreferencesActivity
import info.nightscout.androidaps.activities.SingleFragmentActivity
import info.nightscout.androidaps.databinding.ConfigbuilderFragmentBinding
import info.nightscout.androidaps.events.EventRebuildTabs
import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.interfaces.PluginBase
import info.nightscout.androidaps.interfaces.PluginType
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.configBuilder.events.EventConfigBuilderUpdateGui
import info.nightscout.androidaps.utils.FabricPrivacy
import io.reactivex.rxkotlin.plusAssign
import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.utils.buildHelper.BuildHelper
import info.nightscout.androidaps.interfaces.BuildHelper
import info.nightscout.androidaps.utils.protection.ProtectionCheck
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.protection.ProtectionCheck.Protection.PREFERENCES
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import io.reactivex.disposables.CompositeDisposable
import java.util.*
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import javax.inject.Inject
class ConfigBuilderFragment : DaggerFragment() {
@ -44,48 +47,36 @@ class ConfigBuilderFragment : DaggerFragment() {
private var disposable: CompositeDisposable = CompositeDisposable()
private val pluginViewHolders = ArrayList<PluginViewHolder>()
private var inMenu = false
private var queryingProtection = false
private var _binding: ConfigbuilderFragmentBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
// This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = ConfigbuilderFragmentBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (protectionCheck.isLocked(ProtectionCheck.Protection.PREFERENCES))
binding.mainLayout.visibility = View.GONE
else
binding.unlock.visibility = View.GONE
binding.unlock.setOnClickListener {
activity?.let { activity ->
protectionCheck.queryProtection(activity, ProtectionCheck.Protection.PREFERENCES, {
activity.runOnUiThread {
binding.mainLayout.visibility = View.VISIBLE
binding.unlock.visibility = View.GONE
}
})
}
}
val parentClass = this.activity?.let { it::class.java }
inMenu = parentClass == SingleFragmentActivity::class.java
updateProtectedUi()
binding.unlock.setOnClickListener { queryProtection() }
}
@Synchronized
override fun onResume() {
super.onResume()
if (inMenu) queryProtection() else updateProtectedUi()
disposable += rxBus
.toObservable(EventConfigBuilderUpdateGui::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({
for (pluginViewHolder in pluginViewHolders) pluginViewHolder.update()
}, fabricPrivacy::logException)
for (pluginViewHolder in pluginViewHolders) pluginViewHolder.update()
}, fabricPrivacy::logException)
updateGUI()
}
@ -215,4 +206,21 @@ class ConfigBuilderFragment : DaggerFragment() {
return type == PluginType.GENERAL || type == PluginType.CONSTRAINTS || type == PluginType.LOOP
}
}
private fun updateProtectedUi() {
val isLocked = protectionCheck.isLocked(PREFERENCES)
binding.mainLayout.visibility = isLocked.not().toVisibility()
binding.unlock.visibility = isLocked.toVisibility()
}
private fun queryProtection() {
val isLocked = protectionCheck.isLocked(PREFERENCES)
if (isLocked && !queryingProtection) {
activity?.let { activity ->
queryingProtection = true
val doUpdate = { activity.runOnUiThread { queryingProtection = false; updateProtectedUi() } }
protectionCheck.queryProtection(activity, PREFERENCES, doUpdate, doUpdate, doUpdate)
}
}
}
}

View file

@ -16,7 +16,7 @@ import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.configBuilder.events.EventConfigBuilderUpdateGui
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import java.util.*
import javax.inject.Inject

View file

@ -169,6 +169,9 @@ class PluginStore @Inject constructor(
override val activeSafety: Safety
get() = getSpecificPluginsListByInterface(Safety::class.java).first() as Safety
override val activeIobCobCalculator: IobCobCalculator
get() = getSpecificPluginsListByInterface(IobCobCalculator::class.java).first() as IobCobCalculator
override fun getPluginsList(): ArrayList<PluginBase> = ArrayList(plugins)
}

View file

@ -19,11 +19,11 @@ import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.HardLimits
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.shared.sharedPreferences.SP
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import javax.inject.Inject
import javax.inject.Singleton
@ -130,7 +130,6 @@ class ProfileFunctionImplementation @Inject constructor(
}
}
return null
}
@ -205,4 +204,4 @@ class ProfileFunctionImplementation @Inject constructor(
} else returnValue = false
return returnValue
}
}
}

View file

@ -8,12 +8,12 @@ import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventBucke
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.abs
@ -102,4 +102,11 @@ class BgQualityCheckPlugin @Inject constructor(
State.RECALCULATED -> R.drawable.ic_baseline_warning_24_yellow
State.DOUBLED -> R.drawable.ic_baseline_warning_24_red
}
fun stateDescription(): String =
when (state) {
State.RECALCULATED -> rh.gs(R.string.a11y_bg_quality_recalculated)
State.DOUBLED -> rh.gs(R.string.a11y_bg_quality_doubles)
else -> ""
}
}

View file

@ -10,7 +10,7 @@ import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotifi
import info.nightscout.androidaps.plugins.general.overview.notifications.Notification
import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationWithAction
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import java.util.*
import javax.inject.Inject

View file

@ -1,6 +1,6 @@
package info.nightscout.androidaps.plugins.constraints.objectives
import android.graphics.Color
import android.annotation.SuppressLint
import android.os.Bundle
import android.os.Handler
import android.os.HandlerThread
@ -37,11 +37,11 @@ import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.HtmlHelper
import info.nightscout.androidaps.utils.SntpClient
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import io.reactivex.rxkotlin.plusAssign
import info.nightscout.androidaps.utils.resources.ResourceHelper
import io.reactivex.rxjava3.kotlin.plusAssign
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.shared.sharedPreferences.SP
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxjava3.disposables.CompositeDisposable
import javax.inject.Inject
class ObjectivesFragment : DaggerFragment() {
@ -153,6 +153,7 @@ class ObjectivesFragment : DaggerFragment() {
return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.objectives_item, parent, false))
}
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val objective = objectivesPlugin.objectives[position]
holder.binding.title.text = rh.gs(R.string.nth_objective, position + 1)
@ -167,7 +168,7 @@ class ObjectivesFragment : DaggerFragment() {
} else
holder.binding.gate.visibility = View.GONE
if (!objective.isStarted) {
holder.binding.gate.setTextColor(-0x1)
holder.binding.gate.setTextColor(rh.gac(context, R.attr.defaultTextColor))
holder.binding.verify.visibility = View.GONE
holder.binding.progress.visibility = View.GONE
holder.binding.accomplished.visibility = View.GONE
@ -178,7 +179,7 @@ class ObjectivesFragment : DaggerFragment() {
else
holder.binding.start.visibility = View.GONE
} else if (objective.isAccomplished) {
holder.binding.gate.setTextColor(-0xb350b0)
holder.binding.gate.setTextColor(rh.gac(context, R.attr.isAccomplishedColor))
holder.binding.verify.visibility = View.GONE
holder.binding.progress.visibility = View.GONE
holder.binding.start.visibility = View.GONE
@ -186,7 +187,7 @@ class ObjectivesFragment : DaggerFragment() {
holder.binding.unfinish.visibility = View.VISIBLE
holder.binding.unstart.visibility = View.GONE
} else if (objective.isStarted) {
holder.binding.gate.setTextColor(-0x1)
holder.binding.gate.setTextColor(rh.gac(context,R.attr.defaultTextColor))
holder.binding.verify.visibility = View.VISIBLE
holder.binding.verify.isEnabled = objective.isCompleted || binding.fake.isChecked
holder.binding.start.visibility = View.GONE
@ -200,7 +201,7 @@ class ObjectivesFragment : DaggerFragment() {
// name
val name = TextView(holder.binding.progress.context)
name.text = "${rh.gs(task.task)}:"
name.setTextColor(-0x1)
name.setTextColor(rh.gac(context,R.attr.defaultTextColor) )
holder.binding.progress.addView(name, LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)
// hint
task.hints.forEach { h ->
@ -209,9 +210,9 @@ class ObjectivesFragment : DaggerFragment() {
}
// state
val state = TextView(holder.binding.progress.context)
state.setTextColor(-0x1)
state.setTextColor(rh.gac(context,R.attr.defaultTextColor))
val basicHTML = "<font color=\"%1\$s\"><b>%2\$s</b></font>"
val formattedHTML = String.format(basicHTML, if (task.isCompleted()) "#4CAF50" else "#FF9800", task.progress)
val formattedHTML = String.format(basicHTML, if (task.isCompleted()) rh.gac(context, R.attr.isCompletedColor) else rh.gac(context, R.attr.isNotCompletedColor), task.progress)
state.text = HtmlHelper.fromHtml(formattedHTML)
state.gravity = Gravity.END
holder.binding.progress.addView(state, LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)
@ -228,12 +229,12 @@ class ObjectivesFragment : DaggerFragment() {
}
// horizontal line
val separator = View(holder.binding.progress.context)
separator.setBackgroundColor(Color.DKGRAY)
separator.setBackgroundColor(rh.gac(context, R.attr.separatorColor))
holder.binding.progress.addView(separator, LinearLayout.LayoutParams.MATCH_PARENT, 2)
}
}
holder.binding.accomplished.text = rh.gs(R.string.accomplished, dateUtil.dateAndTimeString(objective.accomplishedOn))
holder.binding.accomplished.setTextColor(-0x3e3e3f)
holder.binding.accomplished.setTextColor(rh.gac(context,R.attr.defaultTextColor))
holder.binding.verify.setOnClickListener {
receiverStatusStore.updateNetworkStatus()
if (binding.fake.isChecked) {

View file

@ -14,7 +14,7 @@ import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.constraints.objectives.objectives.*
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import java.util.*
import javax.inject.Inject

View file

@ -15,7 +15,7 @@ import info.nightscout.androidaps.plugins.constraints.objectives.objectives.Obje
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.ToastUtils
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.interfaces.ResourceHelper
import javax.inject.Inject
class ObjectivesExamDialog : DaggerDialogFragment() {

View file

@ -11,7 +11,7 @@ import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import java.util.*
import javax.inject.Inject
@ -176,7 +176,7 @@ abstract class Objective(injector: HasAndroidInjector, spName: String, @StringRe
textView.setText(hint)
textView.autoLinkMask = Linkify.WEB_URLS
textView.linksClickable = true
textView.setLinkTextColor(Color.YELLOW)
textView.setLinkTextColor(rh.gac(context, R.attr.colorSecondary))
Linkify.addLinks(textView, Linkify.WEB_URLS)
return textView
}

View file

@ -126,6 +126,7 @@ class Objective2(injector: HasAndroidInjector) : Objective(injector, "exam", R.s
.option(Option(R.string.sensitivity_cannula, true))
.option(Option(R.string.sensitivity_time, true))
.hint(Hint(R.string.sensitivity_hint1))
.hint(Hint(R.string.sensitivity_hint2))
)
tasks.add(ExamTask(this, R.string.objectives_label, R.string.objectives_howtosave, "objectives")
.option(Option(R.string.objectives_notesettings, false))
@ -177,6 +178,21 @@ class Objective2(injector: HasAndroidInjector) : Objective(injector, "exam", R.s
.option(Option(R.string.iob_negiob, true))
.option(Option(R.string.iob_posiob, true))
)
tasks.add(ExamTask(this, R.string.cob_label, R.string.cob_question, "cob1")
.option(Option(R.string.cob_longer, true))
.option(Option(R.string.cob_shorter, false))
.option(Option(R.string.cob_no_effect, false))
)
tasks.add(ExamTask(this, R.string.cob_label, R.string.cob2_question, "cob2")
.option(Option(R.string.cob2_longer, false))
.option(Option(R.string.cob2_shorter, true))
.option(Option(R.string.cob2_no_effect, false))
)
tasks.add(ExamTask(this, R.string.cob_label, R.string.cob3_question, "cob3")
.option(Option(R.string.cob3_longer, false))
.option(Option(R.string.cob3_shorter, false))
.option(Option(R.string.cob3_no_effect, true))
)
tasks.add(ExamTask(this, R.string.breadgrams_label, R.string.blank, "breadgrams")
.option(Option(R.string.breadgrams_grams, true))
.option(Option(R.string.breadgrams_exchange, false))

View file

@ -2,7 +2,9 @@ package info.nightscout.androidaps.plugins.constraints.objectives.objectives
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R
import info.nightscout.androidaps.interfaces.Constraint
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.androidaps.plugins.constraints.safety.SafetyPlugin
import info.nightscout.androidaps.utils.T
import javax.inject.Inject
@ -10,14 +12,21 @@ import javax.inject.Inject
class Objective6(injector: HasAndroidInjector) : Objective(injector, "maxiob", R.string.objectives_maxiob_objective, R.string.objectives_maxiob_gate) {
@Inject lateinit var constraintChecker: ConstraintChecker
@Inject lateinit var safetyPlugin: SafetyPlugin
init {
tasks.add(MinimumDurationTask(this, T.days(1).msecs()))
tasks.add(object : Task(this, R.string.maxiobset) {
override fun isCompleted(): Boolean {
val maxIOB = constraintChecker.getMaxIOBAllowed().value()
return maxIOB > 0
}
})
tasks.add(
object : Task(this, R.string.closedmodeenabled) {
override fun isCompleted(): Boolean = sp.getString(R.string.key_aps_mode, "open") == "closed"
})
tasks.add(
object : Task(this, R.string.maxiobset) {
override fun isCompleted(): Boolean {
val maxIOB = constraintChecker.getMaxIOBAllowed().value()
return maxIOB > 0
}
})
}
}

View file

@ -10,7 +10,7 @@ import info.nightscout.androidaps.interfaces.PluginBase
import info.nightscout.androidaps.interfaces.PluginDescription
import info.nightscout.androidaps.interfaces.PluginType
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.interfaces.ResourceHelper
import javax.inject.Inject
import javax.inject.Singleton

View file

@ -9,6 +9,7 @@ import info.nightscout.androidaps.interfaces.*
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.androidaps.plugins.aps.openAPSAMA.OpenAPSAMAPlugin
import info.nightscout.androidaps.plugins.aps.openAPSSMB.OpenAPSSMBPlugin
import info.nightscout.androidaps.plugins.aps.openAPSSMBDynamicISF.OpenAPSSMBDynamicISFPlugin
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification
@ -18,8 +19,8 @@ import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.DecimalFormatter
import info.nightscout.androidaps.utils.HardLimits
import info.nightscout.androidaps.utils.Round
import info.nightscout.androidaps.utils.buildHelper.BuildHelper
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.interfaces.BuildHelper
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONObject
import javax.inject.Inject
@ -36,6 +37,7 @@ class SafetyPlugin @Inject constructor(
private val constraintChecker: ConstraintChecker,
private val openAPSAMAPlugin: OpenAPSAMAPlugin,
private val openAPSSMBPlugin: OpenAPSSMBPlugin,
private val openAPSSMBDynamicISFPlugin: OpenAPSSMBDynamicISFPlugin,
private val sensitivityOref1Plugin: SensitivityOref1Plugin,
private val activePlugin: ActivePlugin,
private val hardLimits: HardLimits,
@ -107,29 +109,29 @@ class SafetyPlugin @Inject constructor(
}
override fun applyBasalConstraints(absoluteRate: Constraint<Double>, profile: Profile): Constraint<Double> {
absoluteRate.setIfGreater(aapsLogger, 0.0, String.format(rh.gs(R.string.limitingbasalratio), 0.0, rh.gs(R.string.itmustbepositivevalue)), this)
absoluteRate.setIfGreater(aapsLogger, 0.0, rh.gs(R.string.limitingbasalratio, 0.0, rh.gs(R.string.itmustbepositivevalue)), this)
if (config.APS) {
var maxBasal = sp.getDouble(R.string.key_openapsma_max_basal, 1.0)
if (maxBasal < profile.getMaxDailyBasal()) {
maxBasal = profile.getMaxDailyBasal()
absoluteRate.addReason(rh.gs(R.string.increasingmaxbasal), this)
}
absoluteRate.setIfSmaller(aapsLogger, maxBasal, String.format(rh.gs(R.string.limitingbasalratio), maxBasal, rh.gs(R.string.maxvalueinpreferences)), this)
absoluteRate.setIfSmaller(aapsLogger, maxBasal,rh.gs(R.string.limitingbasalratio, maxBasal, rh.gs(R.string.maxvalueinpreferences)), this)
// Check percentRate but absolute rate too, because we know real current basal in pump
val maxBasalMultiplier = sp.getDouble(R.string.key_openapsama_current_basal_safety_multiplier, 4.0)
val maxFromBasalMultiplier = floor(maxBasalMultiplier * profile.getBasal() * 100) / 100
absoluteRate.setIfSmaller(aapsLogger, maxFromBasalMultiplier, String.format(rh.gs(R.string.limitingbasalratio), maxFromBasalMultiplier, rh.gs(R.string.maxbasalmultiplier)), this)
absoluteRate.setIfSmaller(aapsLogger, maxFromBasalMultiplier, rh.gs(R.string.limitingbasalratio, maxFromBasalMultiplier, rh.gs(R.string.maxbasalmultiplier)), this)
val maxBasalFromDaily = sp.getDouble(R.string.key_openapsama_max_daily_safety_multiplier, 3.0)
val maxFromDaily = floor(profile.getMaxDailyBasal() * maxBasalFromDaily * 100) / 100
absoluteRate.setIfSmaller(aapsLogger, maxFromDaily, String.format(rh.gs(R.string.limitingbasalratio), maxFromDaily, rh.gs(R.string.maxdailybasalmultiplier)), this)
absoluteRate.setIfSmaller(aapsLogger, maxFromDaily,rh.gs(R.string.limitingbasalratio, maxFromDaily, rh.gs(R.string.maxdailybasalmultiplier)), this)
}
absoluteRate.setIfSmaller(aapsLogger, hardLimits.maxBasal(), String.format(rh.gs(R.string.limitingbasalratio), hardLimits.maxBasal(), rh.gs(R.string.hardlimit)), this)
absoluteRate.setIfSmaller(aapsLogger, hardLimits.maxBasal(),rh.gs(R.string.limitingbasalratio, hardLimits.maxBasal(), rh.gs(R.string.hardlimit)), this)
val pump = activePlugin.activePump
// check for pump max
if (pump.pumpDescription.tempBasalStyle == PumpDescription.ABSOLUTE) {
val pumpLimit = pump.pumpDescription.pumpType.tbrSettings?.maxDose ?: 0.0
absoluteRate.setIfSmaller(aapsLogger, pumpLimit, String.format(rh.gs(R.string.limitingbasalratio), pumpLimit, rh.gs(R.string.pumplimit)), this)
absoluteRate.setIfSmaller(aapsLogger, pumpLimit, rh.gs(R.string.limitingbasalratio, pumpLimit, rh.gs(R.string.pumplimit)), this)
}
// do rounding
@ -149,19 +151,19 @@ class SafetyPlugin @Inject constructor(
val pump = activePlugin.activePump
var percentRateAfterConst = java.lang.Double.valueOf(absoluteConstraint.value() / currentBasal * 100).toInt()
percentRateAfterConst = if (percentRateAfterConst < 100) Round.ceilTo(percentRateAfterConst.toDouble(), pump.pumpDescription.tempPercentStep.toDouble()).toInt() else Round.floorTo(percentRateAfterConst.toDouble(), pump.pumpDescription.tempPercentStep.toDouble()).toInt()
percentRate.set(aapsLogger, percentRateAfterConst, String.format(rh.gs(R.string.limitingpercentrate), percentRateAfterConst, rh.gs(R.string.pumplimit)), this)
percentRate.set(aapsLogger, percentRateAfterConst, rh.gs(R.string.limitingpercentrate, percentRateAfterConst, rh.gs(R.string.pumplimit)), this)
if (pump.pumpDescription.tempBasalStyle == PumpDescription.PERCENT) {
val pumpLimit = pump.pumpDescription.pumpType.tbrSettings?.maxDose ?: 0.0
percentRate.setIfSmaller(aapsLogger, pumpLimit.toInt(), String.format(rh.gs(R.string.limitingbasalratio), pumpLimit, rh.gs(R.string.pumplimit)), this)
percentRate.setIfSmaller(aapsLogger, pumpLimit.toInt(), rh.gs(R.string.limitingbasalratio, pumpLimit, rh.gs(R.string.pumplimit)), this)
}
return percentRate
}
override fun applyBolusConstraints(insulin: Constraint<Double>): Constraint<Double> {
insulin.setIfGreater(aapsLogger, 0.0, String.format(rh.gs(R.string.limitingbolus), 0.0, rh.gs(R.string.itmustbepositivevalue)), this)
insulin.setIfGreater(aapsLogger, 0.0, rh.gs(R.string.limitingbolus, 0.0, rh.gs(R.string.itmustbepositivevalue)), this)
val maxBolus = sp.getDouble(R.string.key_treatmentssafety_maxbolus, 3.0)
insulin.setIfSmaller(aapsLogger, maxBolus, String.format(rh.gs(R.string.limitingbolus), maxBolus, rh.gs(R.string.maxvalueinpreferences)), this)
insulin.setIfSmaller(aapsLogger, hardLimits.maxBolus(), String.format(rh.gs(R.string.limitingbolus), hardLimits.maxBolus(), rh.gs(R.string.hardlimit)), this)
insulin.setIfSmaller(aapsLogger, maxBolus, rh.gs(R.string.limitingbolus, maxBolus, rh.gs(R.string.maxvalueinpreferences)), this)
insulin.setIfSmaller(aapsLogger, hardLimits.maxBolus(), rh.gs(R.string.limitingbolus, hardLimits.maxBolus(), rh.gs(R.string.hardlimit)), this)
val pump = activePlugin.activePump
val rounded = pump.pumpDescription.pumpType.determineCorrectBolusSize(insulin.value())
insulin.setIfDifferent(aapsLogger, rounded, rh.gs(R.string.pumplimit), this)
@ -169,10 +171,10 @@ class SafetyPlugin @Inject constructor(
}
override fun applyExtendedBolusConstraints(insulin: Constraint<Double>): Constraint<Double> {
insulin.setIfGreater(aapsLogger, 0.0, String.format(rh.gs(R.string.limitingextendedbolus), 0.0, rh.gs(R.string.itmustbepositivevalue)), this)
insulin.setIfGreater(aapsLogger, 0.0, rh.gs(R.string.limitingextendedbolus, 0.0, rh.gs(R.string.itmustbepositivevalue)), this)
val maxBolus = sp.getDouble(R.string.key_treatmentssafety_maxbolus, 3.0)
insulin.setIfSmaller(aapsLogger, maxBolus, String.format(rh.gs(R.string.limitingextendedbolus), maxBolus, rh.gs(R.string.maxvalueinpreferences)), this)
insulin.setIfSmaller(aapsLogger, hardLimits.maxBolus(), String.format(rh.gs(R.string.limitingextendedbolus), hardLimits.maxBolus(), rh.gs(R.string.hardlimit)), this)
insulin.setIfSmaller(aapsLogger, maxBolus, rh.gs(R.string.limitingextendedbolus, maxBolus, rh.gs(R.string.maxvalueinpreferences)), this)
insulin.setIfSmaller(aapsLogger, hardLimits.maxBolus(), rh.gs(R.string.limitingextendedbolus, hardLimits.maxBolus(), rh.gs(R.string.hardlimit)), this)
val pump = activePlugin.activePump
val rounded = pump.pumpDescription.pumpType.determineCorrectExtendedBolusSize(insulin.value())
insulin.setIfDifferent(aapsLogger, rounded, rh.gs(R.string.pumplimit), this)
@ -180,19 +182,21 @@ class SafetyPlugin @Inject constructor(
}
override fun applyCarbsConstraints(carbs: Constraint<Int>): Constraint<Int> {
carbs.setIfGreater(aapsLogger, 0, String.format(rh.gs(R.string.limitingcarbs), 0, rh.gs(R.string.itmustbepositivevalue)), this)
carbs.setIfGreater(aapsLogger, 0, rh.gs(R.string.limitingcarbs, 0, rh.gs(R.string.itmustbepositivevalue)), this)
val maxCarbs = sp.getInt(R.string.key_treatmentssafety_maxcarbs, 48)
carbs.setIfSmaller(aapsLogger, maxCarbs, String.format(rh.gs(R.string.limitingcarbs), maxCarbs, rh.gs(R.string.maxvalueinpreferences)), this)
carbs.setIfSmaller(aapsLogger, maxCarbs, rh.gs(R.string.limitingcarbs, maxCarbs, rh.gs(R.string.maxvalueinpreferences)), this)
return carbs
}
override fun applyMaxIOBConstraints(maxIob: Constraint<Double>): Constraint<Double> {
val apsMode = sp.getString(R.string.key_aps_mode, "open")
val maxIobPref: Double = if (openAPSSMBPlugin.isEnabled()) sp.getDouble(R.string.key_openapssmb_max_iob, 3.0) else sp.getDouble(R.string.key_openapsma_max_iob, 1.5)
maxIob.setIfSmaller(aapsLogger, maxIobPref, String.format(rh.gs(R.string.limitingiob), maxIobPref, rh.gs(R.string.maxvalueinpreferences)), this)
if (openAPSAMAPlugin.isEnabled()) maxIob.setIfSmaller(aapsLogger, hardLimits.maxIobAMA(), String.format(rh.gs(R.string.limitingiob), hardLimits.maxIobAMA(), rh.gs(R.string.hardlimit)), this)
if (openAPSSMBPlugin.isEnabled()) maxIob.setIfSmaller(aapsLogger, hardLimits.maxIobSMB(), String.format(rh.gs(R.string.limitingiob), hardLimits.maxIobSMB(), rh.gs(R.string.hardlimit)), this)
if (apsMode == "lgs") maxIob.setIfSmaller(aapsLogger, HardLimits.MAX_IOB_LGS, String.format(rh.gs(R.string.limitingiob), HardLimits.MAX_IOB_LGS, rh.gs(R.string.lowglucosesuspend)), this)
val maxIobPref: Double = if (openAPSSMBPlugin.isEnabled() || openAPSSMBDynamicISFPlugin.isEnabled()) sp.getDouble(R.string.key_openapssmb_max_iob, 3.0) else sp.getDouble(R.string
.key_openapsma_max_iob, 1.5)
maxIob.setIfSmaller(aapsLogger, maxIobPref, rh.gs(R.string.limitingiob, maxIobPref, rh.gs(R.string.maxvalueinpreferences)), this)
if (openAPSAMAPlugin.isEnabled()) maxIob.setIfSmaller(aapsLogger, hardLimits.maxIobAMA(), rh.gs(R.string.limitingiob, hardLimits.maxIobAMA(), rh.gs(R.string.hardlimit)), this)
if (openAPSSMBPlugin.isEnabled()) maxIob.setIfSmaller(aapsLogger, hardLimits.maxIobSMB(), rh.gs(R.string.limitingiob, hardLimits.maxIobSMB(), rh.gs(R.string.hardlimit)), this)
if (openAPSSMBDynamicISFPlugin.isEnabled()) maxIob.setIfSmaller(aapsLogger, hardLimits.maxIobSMB(), rh.gs(R.string.limitingiob, hardLimits.maxIobSMB(), rh.gs(R.string.hardlimit)), this)
if (apsMode == "lgs") maxIob.setIfSmaller(aapsLogger, HardLimits.MAX_IOB_LGS, rh.gs(R.string.limitingiob, HardLimits.MAX_IOB_LGS, rh.gs(R.string.lowglucosesuspend)), this)
return maxIob
}
@ -207,4 +211,4 @@ class SafetyPlugin @Inject constructor(
configuration.storeDouble(R.string.key_treatmentssafety_maxbolus, sp, rh)
configuration.storeInt(R.string.key_treatmentssafety_maxcarbs, sp, rh)
}
}
}

View file

@ -13,7 +13,7 @@ import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification
import info.nightscout.androidaps.plugins.general.overview.notifications.Notification
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import org.spongycastle.util.encoders.Hex
import java.io.*

View file

@ -16,7 +16,7 @@ import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification
import info.nightscout.androidaps.plugins.general.overview.notifications.Notification
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.interfaces.ResourceHelper
import javax.inject.Inject
import javax.inject.Singleton

View file

@ -9,7 +9,7 @@ import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotifi
import info.nightscout.androidaps.plugins.general.overview.notifications.Notification
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.extensions.daysToMillis
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.sharedPreferences.SP
import java.util.concurrent.TimeUnit
@ -80,7 +80,6 @@ class VersionCheckerPlugin @Inject constructor(
return
}
if (isOldVersion(gracePeriod.warning.daysToMillis()) && shouldWarnAgain()) {
// store last notification time
sp.putLong(R.string.key_last_versionchecker_plugin_warning, now)

View file

@ -8,10 +8,8 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.content.ContextCompat
import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R
import info.nightscout.androidaps.activities.ErrorHelperActivity
import info.nightscout.androidaps.activities.HistoryBrowseActivity
@ -20,7 +18,7 @@ import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.ValueWrapper
import info.nightscout.androidaps.database.entities.UserEntry.Action
import info.nightscout.androidaps.database.entities.UserEntry.Sources
import info.nightscout.androidaps.diaconn.DiaconnG8Plugin
import info.nightscout.androidaps.databinding.ActionsFragmentBinding
import info.nightscout.androidaps.dialogs.*
import info.nightscout.androidaps.events.EventCustomActionsChanged
import info.nightscout.androidaps.events.EventExtendedBolusChange
@ -36,21 +34,20 @@ import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.actions.defs.CustomAction
import info.nightscout.androidaps.plugins.general.overview.StatusLightHandler
import info.nightscout.androidaps.plugins.pump.omnipod.eros.OmnipodErosPumpPlugin
import info.nightscout.androidaps.queue.Callback
import info.nightscout.androidaps.skins.SkinProvider
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.buildHelper.BuildHelper
import info.nightscout.androidaps.interfaces.BuildHelper
import info.nightscout.androidaps.utils.protection.ProtectionCheck
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.androidaps.utils.ui.SingleClickButton
import info.nightscout.androidaps.utils.ui.UIRunnable
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import java.util.*
import javax.inject.Inject
@ -81,88 +78,48 @@ class ActionsFragment : DaggerFragment() {
private val pumpCustomActions = HashMap<String, CustomAction>()
private val pumpCustomButtons = ArrayList<SingleClickButton>()
private var smallWidth = false
private var smallHeight = false
private lateinit var dm: DisplayMetrics
private var buttonsLayout: LinearLayout? = null
private var profileSwitch: SingleClickButton? = null
private var tempTarget: SingleClickButton? = null
private var extendedBolus: SingleClickButton? = null
private var extendedBolusCancel: SingleClickButton? = null
private var setTempBasal: SingleClickButton? = null
private var cancelTempBasal: SingleClickButton? = null
private var fill: SingleClickButton? = null
private var historyBrowser: SingleClickButton? = null
private var tddStats: SingleClickButton? = null
private var pumpBatteryChange: SingleClickButton? = null
private var _binding: ActionsFragmentBinding? = null
// This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!!
private var cannulaAge: TextView? = null
private var insulinAge: TextView? = null
private var reservoirLevel: TextView? = null
private var sensorAge: TextView? = null
private var sensorLevel: TextView? = null
private var pbAge: TextView? = null
private var batteryLevel: TextView? = null
private var sensorLevelLabel: TextView? = null
private var insulinLevelLabel: TextView? = null
private var pbLevelLabel: TextView? = null
private var cannulaOrPatch: TextView? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
//check screen width
dm = DisplayMetrics()
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R)
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
@Suppress("DEPRECATION")
activity?.display?.getRealMetrics(dm)
else
@Suppress("DEPRECATION") activity?.windowManager?.defaultDisplay?.getMetrics(dm)
val screenWidth = dm.widthPixels
val screenHeight = dm.heightPixels
smallWidth = screenWidth <= Constants.SMALL_WIDTH
smallHeight = screenHeight <= Constants.SMALL_HEIGHT
val landscape = screenHeight < screenWidth
return inflater.inflate(skinProvider.activeSkin().actionsLayout(landscape, smallWidth), container, false)
} else {
@Suppress("DEPRECATION")
activity?.windowManager?.defaultDisplay?.getMetrics(dm)
}
_binding = ActionsFragmentBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
buttonsLayout = view.findViewById(R.id.action_buttons_layout)
profileSwitch = view.findViewById(R.id.actions_profileswitch)
tempTarget = view.findViewById(R.id.actions_temptarget)
extendedBolus = view.findViewById(R.id.actions_extendedbolus)
extendedBolusCancel = view.findViewById(R.id.actions_extendedbolus_cancel)
setTempBasal = view.findViewById(R.id.actions_settempbasal)
cancelTempBasal = view.findViewById(R.id.actions_canceltempbasal)
fill = view.findViewById(R.id.actions_fill)
historyBrowser = view.findViewById(R.id.actions_historybrowser)
tddStats = view.findViewById(R.id.actions_tddstats)
pumpBatteryChange = view.findViewById(R.id.actions_pumpbatterychange)
skinProvider.activeSkin().preProcessLandscapeActionsLayout(dm, binding)
cannulaAge = view.findViewById(R.id.cannula_age)
insulinAge = view.findViewById(R.id.insulin_age)
reservoirLevel = view.findViewById(R.id.reservoir_level)
sensorAge = view.findViewById(R.id.sensor_age)
sensorLevel = view.findViewById(R.id.sensor_level)
pbAge = view.findViewById(R.id.pb_age)
batteryLevel = view.findViewById(R.id.battery_level)
sensorLevelLabel = view.findViewById(R.id.sensor_level_label)
insulinLevelLabel = view.findViewById(R.id.insulin_level_label)
pbLevelLabel = view.findViewById(R.id.pb_level_label)
cannulaOrPatch = view.findViewById(R.id.cannula_or_patch)
profileSwitch?.setOnClickListener {
ProfileSwitchDialog().show(childFragmentManager, "ProfileSwitchDialog")
binding.profileSwitch.setOnClickListener {
activity?.let { activity ->
protectionCheck.queryProtection(
activity,
ProtectionCheck.Protection.BOLUS,
UIRunnable { ProfileSwitchDialog().show(childFragmentManager, "ProfileSwitchDialog")})
}
}
tempTarget?.setOnClickListener {
TempTargetDialog().show(childFragmentManager, "Actions")
binding.tempTarget.setOnClickListener {
activity?.let { activity ->
protectionCheck.queryProtection(
activity,
ProtectionCheck.Protection.BOLUS,
UIRunnable { TempTargetDialog().show(childFragmentManager, "Actions") })
}
}
extendedBolus?.setOnClickListener {
binding.extendedBolus.setOnClickListener {
activity?.let { activity ->
protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, UIRunnable {
OKDialog.showConfirmation(
@ -174,7 +131,7 @@ class ActionsFragment : DaggerFragment() {
})
}
}
extendedBolusCancel?.setOnClickListener {
binding.extendedBolusCancel.setOnClickListener {
if (iobCobCalculator.getExtendedBolus(dateUtil.now()) != null) {
uel.log(Action.CANCEL_EXTENDED_BOLUS, Sources.Actions)
commandQueue.cancelExtended(object : Callback() {
@ -186,10 +143,15 @@ class ActionsFragment : DaggerFragment() {
})
}
}
setTempBasal?.setOnClickListener {
TempBasalDialog().show(childFragmentManager, "Actions")
binding.setTempBasal.setOnClickListener {
activity?.let { activity ->
protectionCheck.queryProtection(
activity,
ProtectionCheck.Protection.BOLUS,
UIRunnable { TempBasalDialog().show(childFragmentManager, "Actions") })
}
}
cancelTempBasal?.setOnClickListener {
binding.cancelTempBasal.setOnClickListener {
if (iobCobCalculator.getTempBasalIncludingConvertedExtended(dateUtil.now()) != null) {
uel.log(Action.CANCEL_TEMP_BASAL, Sources.Actions)
commandQueue.cancelTempBasal(true, object : Callback() {
@ -201,32 +163,32 @@ class ActionsFragment : DaggerFragment() {
})
}
}
fill?.setOnClickListener {
binding.fill.setOnClickListener {
activity?.let { activity ->
protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, UIRunnable { FillDialog().show(childFragmentManager, "FillDialog") })
}
}
historyBrowser?.setOnClickListener { startActivity(Intent(context, HistoryBrowseActivity::class.java)) }
tddStats?.setOnClickListener { startActivity(Intent(context, TDDStatsActivity::class.java)) }
view.findViewById<SingleClickButton>(R.id.actions_bgcheck).setOnClickListener {
binding.historyBrowser.setOnClickListener { startActivity(Intent(context, HistoryBrowseActivity::class.java)) }
binding.tddStats.setOnClickListener { startActivity(Intent(context, TDDStatsActivity::class.java)) }
binding.bgCheck.setOnClickListener {
CareDialog().setOptions(CareDialog.EventType.BGCHECK, R.string.careportal_bgcheck).show(childFragmentManager, "Actions")
}
view.findViewById<SingleClickButton>(R.id.actions_cgmsensorinsert).setOnClickListener {
binding.cgmSensorInsert.setOnClickListener {
CareDialog().setOptions(CareDialog.EventType.SENSOR_INSERT, R.string.careportal_cgmsensorinsert).show(childFragmentManager, "Actions")
}
pumpBatteryChange?.setOnClickListener {
binding.pumpBatteryChange.setOnClickListener {
CareDialog().setOptions(CareDialog.EventType.BATTERY_CHANGE, R.string.careportal_pumpbatterychange).show(childFragmentManager, "Actions")
}
view.findViewById<SingleClickButton>(R.id.actions_note).setOnClickListener {
binding.note.setOnClickListener {
CareDialog().setOptions(CareDialog.EventType.NOTE, R.string.careportal_note).show(childFragmentManager, "Actions")
}
view.findViewById<SingleClickButton>(R.id.actions_exercise).setOnClickListener {
binding.exercise.setOnClickListener {
CareDialog().setOptions(CareDialog.EventType.EXERCISE, R.string.careportal_exercise).show(childFragmentManager, "Actions")
}
view.findViewById<SingleClickButton>(R.id.actions_question).setOnClickListener {
binding.question.setOnClickListener {
CareDialog().setOptions(CareDialog.EventType.QUESTION, R.string.careportal_question).show(childFragmentManager, "Actions")
}
view.findViewById<SingleClickButton>(R.id.actions_announcement).setOnClickListener {
binding.announcement.setOnClickListener {
CareDialog().setOptions(CareDialog.EventType.ANNOUNCEMENT, R.string.careportal_announcement).show(childFragmentManager, "Actions")
}
@ -265,13 +227,18 @@ class ActionsFragment : DaggerFragment() {
disposable.clear()
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
@Synchronized
fun updateGui() {
val profile = profileFunction.getProfile()
val pump = activePlugin.activePump
profileSwitch?.visibility = (
binding.profileSwitch.visibility = (
activePlugin.activeProfileSource.profile != null &&
pump.pumpDescription.isSetBasalProfileCapable &&
pump.isInitialized() &&
@ -279,60 +246,58 @@ class ActionsFragment : DaggerFragment() {
!loop.isDisconnected).toVisibility()
if (!pump.pumpDescription.isExtendedBolusCapable || !pump.isInitialized() || pump.isSuspended() || loop.isDisconnected || pump.isFakingTempsByExtendedBoluses || config.NSCLIENT) {
extendedBolus?.visibility = View.GONE
extendedBolusCancel?.visibility = View.GONE
binding.extendedBolus.visibility = View.GONE
binding.extendedBolusCancel.visibility = View.GONE
} else {
val activeExtendedBolus = repository.getExtendedBolusActiveAt(dateUtil.now()).blockingGet()
if (activeExtendedBolus is ValueWrapper.Existing) {
extendedBolus?.visibility = View.GONE
extendedBolusCancel?.visibility = View.VISIBLE
binding.extendedBolus.visibility = View.GONE
binding.extendedBolusCancel.visibility = View.VISIBLE
@Suppress("SetTextI18n")
extendedBolusCancel?.text = rh.gs(R.string.cancel) + " " + activeExtendedBolus.value.toStringMedium(dateUtil)
binding.extendedBolusCancel.text = rh.gs(R.string.cancel) + " " + activeExtendedBolus.value.toStringMedium(dateUtil)
} else {
extendedBolus?.visibility = View.VISIBLE
extendedBolusCancel?.visibility = View.GONE
binding.extendedBolus.visibility = View.VISIBLE
binding.extendedBolusCancel.visibility = View.GONE
}
}
if (!pump.pumpDescription.isTempBasalCapable || !pump.isInitialized() || pump.isSuspended() || loop.isDisconnected || config.NSCLIENT) {
setTempBasal?.visibility = View.GONE
cancelTempBasal?.visibility = View.GONE
binding.setTempBasal.visibility = View.GONE
binding.cancelTempBasal.visibility = View.GONE
} else {
val activeTemp = iobCobCalculator.getTempBasalIncludingConvertedExtended(System.currentTimeMillis())
if (activeTemp != null) {
setTempBasal?.visibility = View.GONE
cancelTempBasal?.visibility = View.VISIBLE
binding.setTempBasal.visibility = View.GONE
binding.cancelTempBasal.visibility = View.VISIBLE
@Suppress("SetTextI18n")
cancelTempBasal?.text = rh.gs(R.string.cancel) + " " + activeTemp.toStringShort()
binding.cancelTempBasal.text = rh.gs(R.string.cancel) + " " + activeTemp.toStringShort()
} else {
setTempBasal?.visibility = View.VISIBLE
cancelTempBasal?.visibility = View.GONE
binding.setTempBasal.visibility = View.VISIBLE
binding.cancelTempBasal.visibility = View.GONE
}
}
val activeBgSource = activePlugin.activeBgSource
historyBrowser?.visibility = (profile != null).toVisibility()
fill?.visibility = (pump.pumpDescription.isRefillingCapable && pump.isInitialized() && !pump.isSuspended()).toVisibility()
if (pump is DiaconnG8Plugin) {
pumpBatteryChange?.visibility = (pump.pumpDescription.isBatteryReplaceable && !pump.isBatteryChangeLoggingEnabled()).toVisibility()
} else {
pumpBatteryChange?.visibility =
(pump.pumpDescription.isBatteryReplaceable || (pump is OmnipodErosPumpPlugin && pump.isUseRileyLinkBatteryLevel && pump.isBatteryChangeLoggingEnabled)).toVisibility()
}
tempTarget?.visibility = (profile != null && !loop.isDisconnected).toVisibility()
tddStats?.visibility = pump.pumpDescription.supportsTDDs.toVisibility()
binding.historyBrowser.visibility = (profile != null).toVisibility()
binding.fill.visibility = (pump.pumpDescription.isRefillingCapable && pump.isInitialized() && !pump.isSuspended()).toVisibility()
binding.pumpBatteryChange.visibility = (pump.pumpDescription.isBatteryReplaceable || pump.isBatteryChangeLoggingEnabled()).toVisibility()
binding.tempTarget.visibility = (profile != null && !loop.isDisconnected).toVisibility()
binding.tddStats.visibility = pump.pumpDescription.supportsTDDs.toVisibility()
val isPatchPump = pump.pumpDescription.isPatchPump
binding.status.apply {
cannulaOrPatch.text = if (isPatchPump) rh.gs(R.string.patch_pump) else rh.gs(R.string.cannula)
val imageResource = if (isPatchPump) R.drawable.ic_patch_pump_outline else R.drawable.ic_cp_age_cannula
cannulaOrPatch.setCompoundDrawablesWithIntrinsicBounds(imageResource, 0, 0, 0)
batteryLayout.visibility = (!isPatchPump || pump.pumpDescription.useHardwareLink).toVisibility()
cannulaOrPatch?.text = if (pump.pumpDescription.isPatchPump) rh.gs(R.string.patch_pump) else rh.gs(R.string.cannula)
val imageResource = if (pump.pumpDescription.isPatchPump) R.drawable.ic_patch_pump_outline else R.drawable.ic_cp_age_cannula
cannulaOrPatch?.setCompoundDrawablesWithIntrinsicBounds(imageResource, 0, 0, 0)
if (!config.NSCLIENT) {
statusLightHandler.updateStatusLights(cannulaAge, insulinAge, reservoirLevel, sensorAge, sensorLevel, pbAge, batteryLevel)
sensorLevelLabel?.text = if (activeBgSource.sensorBatteryLevel == -1) "" else rh.gs(R.string.careportal_level_label)
} else {
statusLightHandler.updateStatusLights(cannulaAge, insulinAge, null, sensorAge, null, pbAge, null)
sensorLevelLabel?.text = ""
insulinLevelLabel?.text = ""
pbLevelLabel?.text = ""
if (!config.NSCLIENT) {
statusLightHandler.updateStatusLights(cannulaAge, insulinAge, reservoirLevel, sensorAge, sensorLevel, pbAge, batteryLevel)
sensorLevelLabel.text = if (activeBgSource.sensorBatteryLevel == -1) "" else rh.gs(R.string.careportal_level_label)
} else {
statusLightHandler.updateStatusLights(cannulaAge, insulinAge, null, sensorAge, null, pbAge, null)
sensorLevelLabel.text = ""
insulinLevelLabel.text = ""
pbLevelLabel.text = ""
}
}
checkPumpCustomActions()
@ -365,7 +330,7 @@ class ActionsFragment : DaggerFragment() {
val top = activity?.let { ContextCompat.getDrawable(it, customAction.iconResourceId) }
btn.setCompoundDrawablesWithIntrinsicBounds(null, top, null, null)
buttonsLayout?.addView(btn)
binding.buttonsLayout.addView(btn)
this.pumpCustomActions[rh.gs(customAction.name)] = customAction
this.pumpCustomButtons.add(btn)
@ -373,7 +338,7 @@ class ActionsFragment : DaggerFragment() {
}
private fun removePumpCustomActions() {
for (customButton in pumpCustomButtons) buttonsLayout?.removeView(customButton)
for (customButton in pumpCustomButtons) binding.buttonsLayout.removeView(customButton)
pumpCustomButtons.clear()
}
}
}

View file

@ -7,7 +7,7 @@ import info.nightscout.androidaps.interfaces.PluginBase
import info.nightscout.androidaps.interfaces.PluginDescription
import info.nightscout.androidaps.interfaces.PluginType
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.interfaces.ResourceHelper
import javax.inject.Inject
import javax.inject.Singleton

View file

@ -0,0 +1,516 @@
package info.nightscout.androidaps.plugins.general.autotune
import info.nightscout.androidaps.R
import info.nightscout.androidaps.data.LocalInsulin
import info.nightscout.androidaps.plugins.general.autotune.data.ATProfile
import info.nightscout.androidaps.plugins.general.autotune.data.PreppedGlucose
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin
import info.nightscout.androidaps.utils.Round
import info.nightscout.shared.sharedPreferences.SP
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class AutotuneCore @Inject constructor(
private val sp: SP,
private val autotuneFS: AutotuneFS
) {
fun tuneAllTheThings(preppedGlucose: PreppedGlucose, previousAutotune: ATProfile, pumpProfile: ATProfile): ATProfile {
//var pumpBasalProfile = pumpProfile.basalprofile;
val pumpBasalProfile = pumpProfile.basal
//console.error(pumpBasalProfile);
var basalProfile = previousAutotune.basal
//console.error(basalProfile);
//console.error(isfProfile);
var isf = previousAutotune.isf
//console.error(isf);
var carbRatio = previousAutotune.ic
//console.error(carbRatio);
val csf = isf / carbRatio
val dia = previousAutotune.dia
val peak = previousAutotune.peak
val csfGlucose = preppedGlucose.csfGlucoseData
val isfGlucose = preppedGlucose.isfGlucoseData
val basalGlucose = preppedGlucose.basalGlucoseData
val crData = preppedGlucose.crData
val diaDeviations = preppedGlucose.diaDeviations
val peakDeviations = preppedGlucose.peakDeviations
val pumpISF = pumpProfile.isf
val pumpCarbRatio = pumpProfile.ic
val pumpCSF = pumpISF / pumpCarbRatio
// Autosens constraints
val autotuneMax = sp.getDouble(R.string.key_openapsama_autosens_max, 1.2)
val autotuneMin = sp.getDouble(R.string.key_openapsama_autosens_min, 0.7)
val min5minCarbImpact = sp.getDouble(R.string.key_openapsama_min_5m_carbimpact, 3.0)
// tune DIA
var newDia = dia
if (diaDeviations.size > 0)
{
val currentDiaMeanDev = diaDeviations[2].meanDeviation
val currentDiaRMSDev = diaDeviations[2].rmsDeviation
//Console.WriteLine(DIA,currentDIAMeanDev,currentDIARMSDev);
var minMeanDeviations = 1000000.0
var minRmsDeviations = 1000000.0
var meanBest = 2
var rmsBest = 2
for (i in 0..diaDeviations.size-1)
{
val meanDeviations = diaDeviations[i].meanDeviation
val rmsDeviations = diaDeviations[i].rmsDeviation
if (meanDeviations < minMeanDeviations)
{
minMeanDeviations = Round.roundTo(meanDeviations, 0.001)
meanBest = i
}
if (rmsDeviations < minRmsDeviations)
{
minRmsDeviations = Round.roundTo(rmsDeviations, 0.001)
rmsBest = i
}
}
log("Best insulinEndTime for meanDeviations: ${diaDeviations[meanBest].dia} hours")
log("Best insulinEndTime for RMSDeviations: ${diaDeviations[rmsBest].dia} hours")
if (meanBest < 2 && rmsBest < 2)
{
if (diaDeviations[1].meanDeviation < currentDiaMeanDev * 0.99 && diaDeviations[1].rmsDeviation < currentDiaRMSDev * 0.99)
newDia = diaDeviations[1].dia
}
else if (meanBest > 2 && rmsBest > 2)
{
if (diaDeviations[3].meanDeviation < currentDiaMeanDev * 0.99 && diaDeviations[3].rmsDeviation < currentDiaRMSDev * 0.99)
newDia = diaDeviations[3].dia
}
if (newDia > 12.0)
{
log("insulinEndTime maximum is 12h: not raising further")
newDia = 12.0
}
if (newDia != dia)
log("Adjusting insulinEndTime from $dia to $newDia hours")
else
log("Leaving insulinEndTime unchanged at $dia hours")
}
// tune insulinPeakTime
var newPeak = peak
if (peakDeviations.size > 2)
{
val currentPeakMeanDev = peakDeviations[2].meanDeviation
val currentPeakRMSDev = peakDeviations[2].rmsDeviation
//Console.WriteLine(currentPeakMeanDev);
var minMeanDeviations = 1000000.0
var minRmsDeviations = 1000000.0
var meanBest = 2
var rmsBest = 2
for (i in 0..peakDeviations.size-1)
{
val meanDeviations = peakDeviations[i].meanDeviation;
val rmsDeviations = peakDeviations[i].rmsDeviation;
if (meanDeviations < minMeanDeviations)
{
minMeanDeviations = Round.roundTo(meanDeviations, 0.001)
meanBest = i
}
if (rmsDeviations < minRmsDeviations)
{
minRmsDeviations = Round.roundTo(rmsDeviations, 0.001)
rmsBest = i
}
}
log("Best insulinPeakTime for meanDeviations: ${peakDeviations[meanBest].peak} minutes")
log("Best insulinPeakTime for RMSDeviations: ${peakDeviations[rmsBest].peak} minutes")
if (meanBest < 2 && rmsBest < 2)
{
if (peakDeviations[1].meanDeviation < currentPeakMeanDev * 0.99 && peakDeviations[1].rmsDeviation < currentPeakRMSDev * 0.99)
newPeak = peakDeviations[1].peak
}
else if (meanBest > 2 && rmsBest > 2)
{
if (peakDeviations[3].meanDeviation < currentPeakMeanDev * 0.99 && peakDeviations[3].rmsDeviation < currentPeakRMSDev * 0.99)
newPeak = peakDeviations[3].peak
}
if (newPeak != peak)
log("Adjusting insulinPeakTime from " + peak + " to " + newPeak + " minutes")
else
log("Leaving insulinPeakTime unchanged at " + peak)
}
// Calculate carb ratio (CR) independently of csf and isf
// Use the time period from meal bolus/carbs until COB is zero and IOB is < currentBasal/2
// For now, if another meal IOB/COB stacks on top of it, consider them together
// Compare beginning and ending BGs, and calculate how much more/less insulin is needed to neutralize
// Use entered carbs vs. starting IOB + delivered insulin + needed-at-end insulin to directly calculate CR.
//autotune-core (lib/autotune/index.js) #149-#165
var crTotalCarbs = 0.0
var crTotalInsulin = 0.0
for (i in crData.indices) {
val crDatum = crData[i]
val crBGChange = crDatum.crEndBG - crDatum.crInitialBG
val crInsulinReq = crBGChange / isf
//val crIOBChange = crDatum.crEndIOB - crDatum.crInitialIOB
crDatum.crInsulinTotal = crDatum.crInitialIOB + crDatum.crInsulin + crInsulinReq
//log(crDatum.crInitialIOB + " " + crDatum.crInsulin + " " + crInsulinReq + " " + crDatum.crInsulinTotal);
//val cr = Round.roundTo(crDatum.crCarbs / crDatum.crInsulinTotal, 0.001)
//log(crBGChange + " " + crInsulinReq + " " + crIOBChange + " " + crDatum.crInsulinTotal);
//log("CRCarbs: " + crDatum.crCarbs + " CRInsulin: " + crDatum.crInsulinTotal + " CR:" + cr);
if (crDatum.crInsulinTotal > 0) {
crTotalCarbs += crDatum.crCarbs
crTotalInsulin += crDatum.crInsulinTotal
}
}
//autotune-core (lib/autotune/index.js) #166-#169
crTotalInsulin = Round.roundTo(crTotalInsulin, 0.001)
var totalCR = 0.0
if (crTotalInsulin != 0.0)
totalCR = Round.roundTo(crTotalCarbs / crTotalInsulin, 0.001)
log("crTotalCarbs: $crTotalCarbs crTotalInsulin: $crTotalInsulin totalCR: $totalCR")
//autotune-core (lib/autotune/index.js) #170-#209 (already hourly in aaps)
// convert the basal profile to hourly if it isn't already
val hourlyBasalProfile = basalProfile
//log(hourlyPumpProfile.toString());
//log(hourlyBasalProfile.toString());
val newHourlyBasalProfile = DoubleArray(24)
for (i in 0..23) {
newHourlyBasalProfile[i] = hourlyBasalProfile[i]
}
val basalUntuned = previousAutotune.basalUntuned
//autotune-core (lib/autotune/index.js) #210-#266
// look at net deviations for each hour
for (hour in 0..23) {
var deviations = 0.0
for (i in basalGlucose.indices) {
val BGTime = Calendar.getInstance()
//var BGTime: Date? = null
if (basalGlucose[i].date != 0L) {
BGTime.setTimeInMillis(basalGlucose[i].date)
//BGTime = Date(basalGlucose[i].date)
} else {
log("Could not determine last BG time")
}
val myHour = BGTime.get(Calendar.HOUR_OF_DAY)
//val myHour = BGTime!!.hours
if (hour == myHour) {
//log.debug(basalGlucose[i].deviation);
deviations += basalGlucose[i].deviation
}
}
deviations = Round.roundTo(deviations, 0.001)
log("Hour $hour total deviations: $deviations mg/dL")
// calculate how much less or additional basal insulin would have been required to eliminate the deviations
// only apply 20% of the needed adjustment to keep things relatively stable
var basalNeeded = 0.2 * deviations / isf
basalNeeded = Round.roundTo(basalNeeded, 0.01)
// if basalNeeded is positive, adjust each of the 1-3 hour prior basals by 10% of the needed adjustment
log("Hour $hour basal adjustment needed: $basalNeeded U/hr")
if (basalNeeded > 0) {
for (offset in -3..-1) {
var offsetHour = hour + offset
if (offsetHour < 0) {
offsetHour += 24
}
//log.debug(offsetHour);
newHourlyBasalProfile[offsetHour] = newHourlyBasalProfile[offsetHour] + basalNeeded / 3
newHourlyBasalProfile[offsetHour] = Round.roundTo(newHourlyBasalProfile[offsetHour], 0.001)
}
// otherwise, figure out the percentage reduction required to the 1-3 hour prior basals
// and adjust all of them downward proportionally
} else if (basalNeeded < 0) {
var threeHourBasal = 0.0
for (offset in -3..-1) {
var offsetHour = hour + offset
if (offsetHour < 0) {
offsetHour += 24
}
threeHourBasal += newHourlyBasalProfile[offsetHour]
}
val adjustmentRatio = 1.0 + basalNeeded / threeHourBasal
//log.debug(adjustmentRatio);
for (offset in -3..-1) {
var offsetHour = hour + offset
if (offsetHour < 0) {
offsetHour += 24
}
newHourlyBasalProfile[offsetHour] = newHourlyBasalProfile[offsetHour] * adjustmentRatio
newHourlyBasalProfile[offsetHour] = Round.roundTo(newHourlyBasalProfile[offsetHour], 0.001)
}
}
}
//autotune-core (lib/autotune/index.js) #267-#294
for (hour in 0..23) {
//log.debug(newHourlyBasalProfile[hour],hourlyPumpProfile[hour].rate*1.2);
// cap adjustments at autosens_max and autosens_min
val maxRate = pumpBasalProfile[hour] * autotuneMax
val minRate = pumpBasalProfile[hour] * autotuneMin
if (newHourlyBasalProfile[hour] > maxRate) {
log("Limiting hour " + hour + " basal to " + Round.roundTo(maxRate, 0.01) + " (which is " + Round.roundTo(autotuneMax, 0.01) + " * pump basal of " + pumpBasalProfile[hour] + ")")
//log.debug("Limiting hour",hour,"basal to",maxRate.toFixed(2),"(which is 20% above pump basal of",hourlyPumpProfile[hour].rate,")");
newHourlyBasalProfile[hour] = maxRate
} else if (newHourlyBasalProfile[hour] < minRate) {
log("Limiting hour " + hour + " basal to " + Round.roundTo(minRate, 0.01) + " (which is " + autotuneMin + " * pump basal of " + newHourlyBasalProfile[hour] + ")")
//log.debug("Limiting hour",hour,"basal to",minRate.toFixed(2),"(which is 20% below pump basal of",hourlyPumpProfile[hour].rate,")");
newHourlyBasalProfile[hour] = minRate
}
newHourlyBasalProfile[hour] = Round.roundTo(newHourlyBasalProfile[hour], 0.001)
}
// some hours of the day rarely have data to tune basals due to meals.
// when no adjustments are needed to a particular hour, we should adjust it toward the average of the
// periods before and after it that do have data to be tuned
var lastAdjustedHour = 0
// scan through newHourlyBasalProfile and find hours where the rate is unchanged
//autotune-core (lib/autotune/index.js) #302-#323
for (hour in 0..23) {
if (hourlyBasalProfile[hour] == newHourlyBasalProfile[hour]) {
var nextAdjustedHour = 23
for (nextHour in hour..23) {
if (hourlyBasalProfile[nextHour] != newHourlyBasalProfile[nextHour]) {
nextAdjustedHour = nextHour
break
//} else {
// log("At hour: "+nextHour +" " + hourlyBasalProfile[nextHour] + " " +newHourlyBasalProfile[nextHour]);
}
}
//log.debug(hour, newHourlyBasalProfile);
newHourlyBasalProfile[hour] = Round.roundTo(0.8 * hourlyBasalProfile[hour] + 0.1 * newHourlyBasalProfile[lastAdjustedHour] + 0.1 * newHourlyBasalProfile[nextAdjustedHour], 0.001)
basalUntuned[hour]++
log("Adjusting hour " + hour + " basal from " + hourlyBasalProfile[hour] + " to " + newHourlyBasalProfile[hour] + " based on hour " + lastAdjustedHour + " = " + newHourlyBasalProfile[lastAdjustedHour] + " and hour " + nextAdjustedHour + " = " + newHourlyBasalProfile[nextAdjustedHour])
} else {
lastAdjustedHour = hour
}
}
//log(newHourlyBasalProfile.toString());
basalProfile = newHourlyBasalProfile
// Calculate carb ratio (CR) independently of csf and isf
// Use the time period from meal bolus/carbs until COB is zero and IOB is < currentBasal/2
// For now, if another meal IOB/COB stacks on top of it, consider them together
// Compare beginning and ending BGs, and calculate how much more/less insulin is needed to neutralize
// Use entered carbs vs. starting IOB + delivered insulin + needed-at-end insulin to directly calculate CR.
// calculate net deviations while carbs are absorbing
// measured from carb entry until COB and deviations both drop to zero
var deviations = 0.0
var mealCarbs = 0
var totalMealCarbs = 0
var totalDeviations = 0.0
val fullNewCSF: Double
//log.debug(CSFGlucose[0].mealAbsorption);
//log.debug(CSFGlucose[0]);
//autotune-core (lib/autotune/index.js) #346-#365
for (i in csfGlucose.indices) {
//log.debug(CSFGlucose[i].mealAbsorption, i);
if (csfGlucose[i].mealAbsorption === "start") {
deviations = 0.0
mealCarbs = csfGlucose[i].mealCarbs
} else if (csfGlucose[i].mealAbsorption === "end") {
deviations += csfGlucose[i].deviation
// compare the sum of deviations from start to end vs. current csf * mealCarbs
//log.debug(csf,mealCarbs);
//val csfRise = csf * mealCarbs
//log.debug(deviations,isf);
//log.debug("csfRise:",csfRise,"deviations:",deviations);
totalMealCarbs += mealCarbs
totalDeviations += deviations
} else {
//todo Philoul check 0 * min5minCarbImpact ???
deviations += Math.max(0 * min5minCarbImpact, csfGlucose[i].deviation)
mealCarbs = Math.max(mealCarbs, csfGlucose[i].mealCarbs)
}
}
// at midnight, write down the mealcarbs as total meal carbs (to prevent special case of when only one meal and it not finishing absorbing by midnight)
// TODO: figure out what to do with dinner carbs that don't finish absorbing by midnight
if (totalMealCarbs == 0) {
totalMealCarbs += mealCarbs
}
if (totalDeviations == 0.0) {
totalDeviations += deviations
}
//log.debug(totalDeviations, totalMealCarbs);
fullNewCSF = if (totalMealCarbs == 0) {
// if no meals today, csf is unchanged
csf
} else {
// how much change would be required to account for all of the deviations
Round.roundTo(totalDeviations / totalMealCarbs, 0.01)
}
// only adjust by 20%
var newCSF = 0.8 * csf + 0.2 * fullNewCSF
// safety cap csf
if (pumpCSF != 0.0) {
val maxCSF = pumpCSF * autotuneMax
val minCSF = pumpCSF * autotuneMin
if (newCSF > maxCSF) {
log("Limiting csf to " + Round.roundTo(maxCSF, 0.01) + " (which is " + autotuneMax + "* pump csf of " + pumpCSF + ")")
newCSF = maxCSF
} else if (newCSF < minCSF) {
log("Limiting csf to " + Round.roundTo(minCSF, 0.01) + " (which is" + autotuneMin + "* pump csf of " + pumpCSF + ")")
newCSF = minCSF
} //else { log.debug("newCSF",newCSF,"is close enough to",pumpCSF); }
}
val oldCSF = Round.roundTo(csf, 0.001)
newCSF = Round.roundTo(newCSF, 0.001)
totalDeviations = Round.roundTo(totalDeviations, 0.001)
log("totalMealCarbs: $totalMealCarbs totalDeviations: $totalDeviations oldCSF $oldCSF fullNewCSF: $fullNewCSF newCSF: $newCSF")
// this is where csf is set based on the outputs
//if (newCSF != 0.0) {
// csf = newCSF
//}
var fullNewCR: Double
fullNewCR = if (totalCR == 0.0) {
// if no meals today, CR is unchanged
carbRatio
} else {
// how much change would be required to account for all of the deviations
totalCR
}
// don't tune CR out of bounds
var maxCR = pumpCarbRatio * autotuneMax
if (maxCR > 150) {
maxCR = 150.0
}
var minCR = pumpCarbRatio * autotuneMin
if (minCR < 3) {
minCR = 3.0
}
// safety cap fullNewCR
if (pumpCarbRatio != 0.0) {
if (fullNewCR > maxCR) {
log("Limiting fullNewCR from " + fullNewCR + " to " + Round.roundTo(maxCR, 0.01) + " (which is " + autotuneMax + " * pump CR of " + pumpCarbRatio + ")")
fullNewCR = maxCR
} else if (fullNewCR < minCR) {
log("Limiting fullNewCR from " + fullNewCR + " to " + Round.roundTo(minCR, 0.01) + " (which is " + autotuneMin + " * pump CR of " + pumpCarbRatio + ")")
fullNewCR = minCR
} //else { log.debug("newCR",newCR,"is close enough to",pumpCarbRatio); }
}
// only adjust by 20%
var newCR = 0.8 * carbRatio + 0.2 * fullNewCR
// safety cap newCR
if (pumpCarbRatio != 0.0) {
if (newCR > maxCR) {
log("Limiting CR to " + Round.roundTo(maxCR, 0.01) + " (which is " + autotuneMax + " * pump CR of " + pumpCarbRatio + ")")
newCR = maxCR
} else if (newCR < minCR) {
log("Limiting CR to " + Round.roundTo(minCR, 0.01) + " (which is " + autotuneMin + " * pump CR of " + pumpCarbRatio + ")")
newCR = minCR
} //else { log.debug("newCR",newCR,"is close enough to",pumpCarbRatio); }
}
newCR = Round.roundTo(newCR, 0.001)
log("oldCR: $carbRatio fullNewCR: $fullNewCR newCR: $newCR")
// this is where CR is set based on the outputs
//var ISFFromCRAndCSF = isf;
if (newCR != 0.0) {
carbRatio = newCR
//ISFFromCRAndCSF = Math.round( carbRatio * csf * 1000)/1000;
}
// calculate median deviation and bgi in data attributable to isf
val isfDeviations: MutableList<Double> = ArrayList()
val bGIs: MutableList<Double> = ArrayList()
val avgDeltas: MutableList<Double> = ArrayList()
val ratios: MutableList<Double> = ArrayList()
var count = 0
for (i in isfGlucose.indices) {
val deviation = isfGlucose[i].deviation
isfDeviations.add(deviation)
val BGI = isfGlucose[i].bgi
bGIs.add(BGI)
val avgDelta = isfGlucose[i].avgDelta
avgDeltas.add(avgDelta)
val ratio = 1 + deviation / BGI
//log.debug("Deviation:",deviation,"BGI:",BGI,"avgDelta:",avgDelta,"ratio:",ratio);
ratios.add(ratio)
count++
}
Collections.sort(avgDeltas)
Collections.sort(bGIs)
Collections.sort(isfDeviations)
Collections.sort(ratios)
var p50deviation = IobCobCalculatorPlugin.percentile(isfDeviations.toTypedArray(), 0.50)
var p50BGI = IobCobCalculatorPlugin.percentile(bGIs.toTypedArray(), 0.50)
val p50ratios = Round.roundTo(IobCobCalculatorPlugin.percentile(ratios.toTypedArray(), 0.50), 0.001)
var fullNewISF = isf
if (count < 10) {
// leave isf unchanged if fewer than 5 isf data points
log("Only found " + isfGlucose.size + " ISF data points, leaving ISF unchanged at " + isf)
} else {
// calculate what adjustments to isf would have been necessary to bring median deviation to zero
fullNewISF = isf * p50ratios
}
fullNewISF = Round.roundTo(fullNewISF, 0.001)
// adjust the target isf to be a weighted average of fullNewISF and pumpISF
val adjustmentFraction: Double
/*
// TODO: philoul may be allow adjustmentFraction in settings with safety limits ?)
if (typeof(pumpProfile.autotune_isf_adjustmentFraction) !== 'undefined') {
adjustmentFraction = pumpProfile.autotune_isf_adjustmentFraction;
} else {*/
adjustmentFraction = 1.0
// }
// low autosens ratio = high isf
val maxISF = pumpISF / autotuneMin
// high autosens ratio = low isf
val minISF = pumpISF / autotuneMax
var adjustedISF = 0.0
var newISF = 0.0
if (pumpISF != 0.0) {
adjustedISF = if (fullNewISF < 0) {
isf
} else {
adjustmentFraction * fullNewISF + (1 - adjustmentFraction) * pumpISF
}
// cap adjustedISF before applying 10%
//log.debug(adjustedISF, maxISF, minISF);
if (adjustedISF > maxISF) {
log("Limiting adjusted isf of " + Round.roundTo(adjustedISF, 0.01) + " to " + Round.roundTo(maxISF, 0.01) + "(which is pump isf of " + pumpISF + "/" + autotuneMin + ")")
adjustedISF = maxISF
} else if (adjustedISF < minISF) {
log("Limiting adjusted isf of" + Round.roundTo(adjustedISF, 0.01) + " to " + Round.roundTo(minISF, 0.01) + "(which is pump isf of " + pumpISF + "/" + autotuneMax + ")")
adjustedISF = minISF
}
// and apply 20% of that adjustment
newISF = 0.8 * isf + 0.2 * adjustedISF
if (newISF > maxISF) {
log("Limiting isf of" + Round.roundTo(newISF, 0.01) + "to" + Round.roundTo(maxISF, 0.01) + "(which is pump isf of" + pumpISF + "/" + autotuneMin + ")")
newISF = maxISF
} else if (newISF < minISF) {
log("Limiting isf of" + Round.roundTo(newISF, 0.01) + "to" + Round.roundTo(minISF, 0.01) + "(which is pump isf of" + pumpISF + "/" + autotuneMax + ")")
newISF = minISF
}
}
newISF = Round.roundTo(newISF, 0.001)
//log.debug(avgRatio);
//log.debug(newISF);
p50deviation = Round.roundTo(p50deviation, 0.001)
p50BGI = Round.roundTo(p50BGI, 0.001)
adjustedISF = Round.roundTo(adjustedISF, 0.001)
log("p50deviation: $p50deviation p50BGI $p50BGI p50ratios: $p50ratios Old isf: $isf fullNewISF: $fullNewISF adjustedISF: $adjustedISF newISF: $newISF")
if (newISF != 0.0) {
isf = newISF
}
previousAutotune.from = preppedGlucose.from
previousAutotune.basal = basalProfile
previousAutotune.isf = isf
previousAutotune.ic = Round.roundTo(carbRatio, 0.001)
previousAutotune.basalUntuned = basalUntuned
previousAutotune.dia = newDia
previousAutotune.peak = newPeak
val localInsulin = LocalInsulin("Ins_$newPeak-$newDia", newPeak, newDia)
previousAutotune.localInsulin = localInsulin
previousAutotune.updateProfile()
return previousAutotune
}
private fun log(message: String) {
autotuneFS.atLog("[Core] $message")
}
}

View file

@ -0,0 +1,204 @@
package info.nightscout.androidaps.plugins.general.autotune
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.plugins.general.autotune.data.ATProfile
import info.nightscout.androidaps.plugins.general.autotune.data.PreppedGlucose
import info.nightscout.androidaps.plugins.general.maintenance.LoggerUtils
import info.nightscout.androidaps.R
import org.json.JSONException
import org.slf4j.LoggerFactory
import java.io.*
import java.text.SimpleDateFormat
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class AutotuneFS @Inject constructor(
private val rh: ResourceHelper,
private val loggerUtils: LoggerUtils
) {
val AUTOTUNEFOLDER = "autotune"
val SETTINGSFOLDER = "settings"
val RECOMMENDATIONS = "autotune_recommendations.log"
val ENTRIESPREF = "aaps-entries."
val TREATMENTSPREF = "aaps-treatments."
val AAPSBOLUSESPREF = "aaps-boluses."
val PREPPEDPREF = "aaps-autotune."
val SETTINGS = "settings.json"
val PROFIL = "profil"
val PUMPPROFILE = "pumpprofile.json"
val TUNEDPROFILE = "newaapsprofile."
val LOGPREF = "autotune."
val ZIPPREF = "autotune_"
lateinit var autotunePath: File
lateinit var autotuneSettings: File
private var logString = ""
val BUFFER_SIZE = 2048
private val log = LoggerFactory.getLogger(AutotunePlugin::class.java)
/*****************************************************************************
* Create autotune folder for all files created during an autotune session
*****************************************************************************/
fun createAutotuneFolder() {
//create autotune subfolder for autotune files if not exists
autotunePath = File(loggerUtils.logDirectory, AUTOTUNEFOLDER)
if (!(autotunePath.exists() && autotunePath.isDirectory)) {
autotunePath.mkdir()
log("Create $AUTOTUNEFOLDER subfolder in ${loggerUtils.logDirectory}")
}
autotuneSettings = File(loggerUtils.logDirectory, SETTINGSFOLDER)
if (!(autotuneSettings.exists() && autotuneSettings.isDirectory)) {
autotuneSettings.mkdir()
log("Create $SETTINGSFOLDER subfolder in ${loggerUtils.logDirectory}")
}
}
/*****************************************************************************
* between each run of autotune, clean autotune folder content
*****************************************************************************/
fun deleteAutotuneFiles() {
autotunePath.listFiles()?.let { listFiles ->
for (file in listFiles) {
if (file.isFile) file.delete()
}
}
autotuneSettings.listFiles()?.let { listFiles ->
for (file in listFiles) {
if (file.isFile) file.delete()
}
}
log("Delete previous Autotune files")
}
/*****************************************************************************
* Create a JSON autotune files or settings files
*****************************************************************************/
fun exportSettings(settings: String) {
createAutotunefile(SETTINGS, settings, true)
}
fun exportPumpProfile(profile: ATProfile) {
createAutotunefile(PUMPPROFILE, profile.profiletoOrefJSON(), true)
createAutotunefile(PUMPPROFILE, profile.profiletoOrefJSON())
}
fun exportTunedProfile(tunedProfile: ATProfile) {
createAutotunefile(TUNEDPROFILE + formatDate(tunedProfile.from) + ".json", tunedProfile.profiletoOrefJSON())
try {
createAutotunefile(rh.gs(R.string.autotune_tunedprofile_name) + ".json", tunedProfile.profiletoOrefJSON(), true)
} catch (e: JSONException) {
}
}
fun exportEntries(autotuneIob: AutotuneIob) {
try {
createAutotunefile(ENTRIESPREF + formatDate(autotuneIob.startBG) + ".json", autotuneIob.glucoseToJSON())
} catch (e: JSONException) {
}
}
fun exportTreatments(autotuneIob: AutotuneIob) {
try {
createAutotunefile(TREATMENTSPREF + formatDate(autotuneIob.startBG) + ".json", autotuneIob.nsHistoryToJSON())
createAutotunefile(AAPSBOLUSESPREF + formatDate(autotuneIob.startBG) + ".json", autotuneIob.bolusesToJSON())
} catch (e: JSONException) {
}
}
fun exportPreppedGlucose(preppedGlucose: PreppedGlucose) {
createAutotunefile(PREPPEDPREF + formatDate(preppedGlucose.from) + ".json", preppedGlucose.toString(2))
}
fun exportResult(result: String) {
createAutotunefile(RECOMMENDATIONS, result)
}
fun exportLog(lastRun: Long, index: Int = 0) {
val suffix = if (index == 0) "" else "_" + index
log("Create " + LOGPREF + formatDate(lastRun) + suffix + ".log" + " file in " + AUTOTUNEFOLDER + " folder")
createAutotunefile(LOGPREF + formatDate(lastRun) + suffix + ".log", logString)
logString = ""
}
fun exportLogAndZip(lastRun: Long) {
log("Create " + LOGPREF + formatDate(lastRun) + ".log" + " file in " + AUTOTUNEFOLDER + " folder")
createAutotunefile(LOGPREF + formatDate(lastRun) + ".log", logString)
zipAutotune(lastRun)
logString = ""
}
private fun createAutotunefile(fileName: String, stringFile: String, isSettingFile: Boolean = false) {
val autotuneFile = File(if (isSettingFile) autotuneSettings.absolutePath else autotunePath.absolutePath, fileName)
try {
val fw = FileWriter(autotuneFile)
val pw = PrintWriter(fw)
pw.println(stringFile)
pw.close()
fw.close()
log("Create " + fileName + " file in " + (if (isSettingFile) SETTINGSFOLDER else AUTOTUNEFOLDER) + " folder")
} catch (e: FileNotFoundException) {
//log.error("Unhandled exception", e);
} catch (e: IOException) {
//log.error("Unhandled exception", e);
}
}
/**********************************************************************************
* create a zip file with all autotune files and settings in autotune folder at the end of run
**********************************************************************************/
fun zipAutotune(lastRun: Long) {
try {
val zipFileName = ZIPPREF + formatDate(lastRun, true) + ".zip"
val zipFile = File(loggerUtils.logDirectory, zipFileName)
val out = ZipOutputStream(BufferedOutputStream(FileOutputStream(zipFile)))
zipDirectory(autotunePath, autotunePath.name, out)
zipDirectory(autotuneSettings, autotuneSettings.name, out)
out.flush()
out.close()
log("Create $zipFileName file in ${loggerUtils.logDirectory} folder")
} catch (e: IOException) {
//log.error("Unhandled exception", e);
}
}
private fun log(message: String) {
atLog("[FS] $message")
}
fun atLog(message: String) {
logString += "$message\n"
log.debug(message)
}
private fun zipDirectory(folder: File, parentFolder: String, out: ZipOutputStream) {
folder.listFiles()?.let { listFiles ->
for (file in listFiles) {
if (file.isDirectory) {
zipDirectory(file, parentFolder + "/" + file.name, out)
continue
}
try {
out.putNextEntry(ZipEntry(parentFolder + "/" + file.name))
val bis = BufferedInputStream(FileInputStream(file))
//long bytesRead = 0;
val bytesIn = ByteArray(BUFFER_SIZE)
var read: Int
while (bis.read(bytesIn).also { read = it } != -1) {
out.write(bytesIn, 0, read)
}
out.closeEntry()
} catch (e: IOException) {
//log.error("Unhandled exception", e);
}
}
}
}
private fun formatDate(date: Long, dateHour: Boolean = false): String {
val dateFormat = if (dateHour) SimpleDateFormat("yyyy-MM-dd_HH-mm-ss") else SimpleDateFormat("yyyy-MM-dd")
return dateFormat.format(date)
}
}

View file

@ -0,0 +1,544 @@
package info.nightscout.androidaps.plugins.general.autotune
import android.graphics.Paint
import android.graphics.Typeface
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.TableLayout
import android.widget.TableRow
import android.widget.TextView
import dagger.android.HasAndroidInjector
import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R
import info.nightscout.androidaps.data.LocalInsulin
import info.nightscout.androidaps.data.ProfileSealed
import info.nightscout.androidaps.database.entities.UserEntry
import info.nightscout.androidaps.database.entities.ValueWithUnit
import info.nightscout.androidaps.databinding.AutotuneFragmentBinding
import info.nightscout.androidaps.dialogs.ProfileViewerDialog
import info.nightscout.androidaps.extensions.runOnUiThread
import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.autotune.data.ATProfile
import info.nightscout.androidaps.plugins.general.autotune.events.EventAutotuneUpdateGui
import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin
import info.nightscout.androidaps.plugins.profile.local.events.EventLocalProfileChanged
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.MidnightTime
import info.nightscout.androidaps.utils.Round
import info.nightscout.androidaps.utils.alertDialogs.OKDialog.showConfirmation
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.shared.SafeParse
import info.nightscout.shared.sharedPreferences.SP
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import org.json.JSONObject
import java.text.DecimalFormat
import javax.inject.Inject
class AutotuneFragment : DaggerFragment() {
@Inject lateinit var profileFunction: ProfileFunction
@Inject lateinit var autotunePlugin: AutotunePlugin
@Inject lateinit var autotuneFS: AutotuneFS
@Inject lateinit var sp: SP
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var localProfilePlugin: LocalProfilePlugin
@Inject lateinit var fabricPrivacy: FabricPrivacy
@Inject lateinit var uel: UserEntryLogger
@Inject lateinit var rh: ResourceHelper
@Inject lateinit var rxBus: RxBus
@Inject lateinit var injector: HasAndroidInjector
@Inject lateinit var aapsSchedulers: AapsSchedulers
private var disposable: CompositeDisposable = CompositeDisposable()
//private val log = LoggerFactory.getLogger(AutotunePlugin::class.java)
private var _binding: AutotuneFragmentBinding? = null
private lateinit var profileStore: ProfileStore
private var profileName = ""
private var profile: ATProfile? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = AutotuneFragmentBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sp.putBoolean(R.string.key_autotune_tune_insulin_curve, false) // put to false tune insulin curve
sp.putBoolean(R.string.key_autotune_additional_log, false) // put to false additional log
autotunePlugin.loadLastRun()
if (autotunePlugin.lastNbDays.isEmpty())
autotunePlugin.lastNbDays = sp.getInt(R.string.key_autotune_default_tune_days, 5).toString()
val defaultValue = sp.getInt(R.string.key_autotune_default_tune_days, 5).toDouble()
profileStore = activePlugin.activeProfileSource.profile ?: ProfileStore(injector, JSONObject(), dateUtil)
profileName = if (binding.profileList.text.toString() == rh.gs(R.string.active)) "" else binding.profileList.text.toString()
profileFunction.getProfile()?.let { currentProfile ->
profile = ATProfile(profileStore.getSpecificProfile(profileName)?.let { ProfileSealed.Pure(it) } ?: currentProfile, LocalInsulin(""), injector)
}
binding.tuneDays.setParams(
savedInstanceState?.getDouble("tunedays")
?: defaultValue, 1.0, 30.0, 1.0, DecimalFormat("0"), false, null, textWatcher
)
binding.autotuneRun.setOnClickListener {
val daysBack = SafeParse.stringToInt(binding.tuneDays.text)
autotunePlugin.lastNbDays = daysBack.toString()
log("Run Autotune $profileName, $daysBack days")
Thread {
autotunePlugin.aapsAutotune(daysBack, false, profileName)
}.start()
updateGui()
}
binding.profileList.onItemClickListener = AdapterView.OnItemClickListener { _, _, _, _ ->
if (!autotunePlugin.calculationRunning) {
profileName = if (binding.profileList.text.toString() == rh.gs(R.string.active)) "" else binding.profileList.text.toString()
profileFunction.getProfile()?.let { currentProfile ->
profile = ATProfile(profileStore.getSpecificProfile(profileName)?.let { ProfileSealed.Pure(it) } ?: currentProfile, LocalInsulin(""), injector)
}
autotunePlugin.selectedProfile = profileName
resetParam()
}
updateGui()
}
binding.autotuneCopylocal.setOnClickListener {
val localName = rh.gs(R.string.autotune_tunedprofile_name) + " " + dateUtil.dateAndTimeString(autotunePlugin.lastRun)
val circadian = sp.getBoolean(R.string.key_autotune_circadian_ic_isf, false)
autotunePlugin.tunedProfile?.let { tunedProfile ->
showConfirmation(requireContext(),
rh.gs(R.string.autotune_copy_localprofile_button),
rh.gs(R.string.autotune_copy_local_profile_message) + "\n" + localName,
Runnable {
localProfilePlugin.addProfile(localProfilePlugin.copyFrom(tunedProfile.getProfile(circadian), localName))
rxBus.send(EventLocalProfileChanged())
uel.log(
UserEntry.Action.NEW_PROFILE,
UserEntry.Sources.Autotune,
ValueWithUnit.SimpleString(localName)
)
updateGui()
})
}
}
binding.autotuneUpdateProfile.setOnClickListener {
val localName = autotunePlugin.pumpProfile.profilename
showConfirmation(requireContext(),
rh.gs(R.string.autotune_update_input_profile_button),
rh.gs(R.string.autotune_update_local_profile_message, localName),
Runnable {
autotunePlugin.tunedProfile?.profilename = localName
autotunePlugin.updateProfile(autotunePlugin.tunedProfile)
autotunePlugin.updateButtonVisibility = View.GONE
autotunePlugin.saveLastRun()
uel.log(
UserEntry.Action.STORE_PROFILE,
UserEntry.Sources.Autotune,
ValueWithUnit.SimpleString(localName)
)
updateGui()
}
)
}
binding.autotuneRevertProfile.setOnClickListener {
val localName = autotunePlugin.pumpProfile.profilename
showConfirmation(requireContext(),
rh.gs(R.string.autotune_revert_input_profile_button),
rh.gs(R.string.autotune_revert_local_profile_message, localName),
Runnable {
autotunePlugin.tunedProfile?.profilename = ""
autotunePlugin.updateProfile(autotunePlugin.pumpProfile)
autotunePlugin.updateButtonVisibility = View.VISIBLE
autotunePlugin.saveLastRun()
uel.log(
UserEntry.Action.STORE_PROFILE,
UserEntry.Sources.Autotune,
ValueWithUnit.SimpleString(localName)
)
updateGui()
}
)
}
binding.autotuneCheckInputProfile.setOnClickListener {
val pumpProfile = profileFunction.getProfile()?.let { currentProfile ->
profileStore.getSpecificProfile(profileName)?.let { specificProfile ->
ATProfile(ProfileSealed.Pure(specificProfile), LocalInsulin(""), injector).also {
it.profilename = profileName
}
}
?: ATProfile(currentProfile, LocalInsulin(""), injector).also {
it.profilename = profileFunction.getProfileName()
}
}
pumpProfile?.let {
ProfileViewerDialog().also { pvd ->
pvd.arguments = Bundle().also {
it.putLong("time", dateUtil.now())
it.putInt("mode", ProfileViewerDialog.Mode.CUSTOM_PROFILE.ordinal)
it.putString("customProfile", pumpProfile.profile.toPureNsJson(dateUtil).toString())
it.putString("customProfileUnits", profileFunction.getUnits().asText)
it.putString("customProfileName", pumpProfile.profilename)
}
}.show(childFragmentManager, "ProfileViewDialog")
}
}
binding.autotuneCompare.setOnClickListener {
val pumpProfile = autotunePlugin.pumpProfile
val circadian = sp.getBoolean(R.string.key_autotune_circadian_ic_isf, false)
val tunedProfile = if (circadian) autotunePlugin.tunedProfile?.circadianProfile else autotunePlugin.tunedProfile?.profile
ProfileViewerDialog().also { pvd ->
pvd.arguments = Bundle().also {
it.putLong("time", dateUtil.now())
it.putInt("mode", ProfileViewerDialog.Mode.PROFILE_COMPARE.ordinal)
it.putString("customProfile", pumpProfile.profile.toPureNsJson(dateUtil).toString())
it.putString("customProfile2", tunedProfile?.toPureNsJson(dateUtil).toString())
it.putString("customProfileUnits", profileFunction.getUnits().asText)
it.putString("customProfileName", pumpProfile.profilename + "\n" + rh.gs(R.string.autotune_tunedprofile_name))
}
}.show(childFragmentManager, "ProfileViewDialog")
}
binding.autotuneProfileswitch.setOnClickListener {
val tunedProfile = autotunePlugin.tunedProfile
autotunePlugin.updateProfile(tunedProfile)
val circadian = sp.getBoolean(R.string.key_autotune_circadian_ic_isf, false)
tunedProfile?.let { tunedP ->
tunedP.profileStore(circadian)?.let {
showConfirmation(requireContext(),
rh.gs(R.string.activate_profile) + ": " + tunedP.profilename + " ?",
Runnable {
uel.log(
UserEntry.Action.STORE_PROFILE,
UserEntry.Sources.Autotune,
ValueWithUnit.SimpleString(tunedP.profilename)
)
val now = dateUtil.now()
if (profileFunction.createProfileSwitch(
it,
profileName = tunedP.profilename,
durationInMinutes = 0,
percentage = 100,
timeShiftInHours = 0,
timestamp = now
)
) {
uel.log(
UserEntry.Action.PROFILE_SWITCH,
UserEntry.Sources.Autotune,
"Autotune AutoSwitch",
ValueWithUnit.SimpleString(autotunePlugin.tunedProfile!!.profilename)
)
}
rxBus.send(EventLocalProfileChanged())
updateGui()
}
)
}
}
}
binding.tuneLastrun.setOnClickListener {
if (!autotunePlugin.calculationRunning) {
autotunePlugin.loadLastRun()
updateGui()
}
}
binding.tuneLastrun.paintFlags = binding.tuneLastrun.paintFlags or Paint.UNDERLINE_TEXT_FLAG
}
@Synchronized
override fun onResume() {
super.onResume()
disposable += rxBus
.toObservable(EventAutotuneUpdateGui::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({ updateGui() }, fabricPrivacy::logException)
checkNewDay()
updateGui()
}
@Synchronized
override fun onPause() {
super.onPause()
disposable.clear()
}
@Synchronized
private fun updateGui() {
_binding ?: return
binding.tuneDays.value = autotunePlugin.lastNbDays.toDouble()
profileStore = activePlugin.activeProfileSource.profile ?: ProfileStore(injector, JSONObject(), dateUtil)
profileName = if (binding.profileList.text.toString() == rh.gs(R.string.active)) "" else binding.profileList.text.toString()
profileFunction.getProfile()?.let { currentProfile ->
profile = ATProfile(profileStore.getSpecificProfile(profileName)?.let { ProfileSealed.Pure(it) } ?: currentProfile, LocalInsulin(""), injector)
}
val profileList: ArrayList<CharSequence> = profileStore.getProfileList()
profileList.add(0, rh.gs(R.string.active))
context?.let { context ->
binding.profileList.setAdapter(ArrayAdapter(context, R.layout.spinner_centered, profileList))
} ?: return
// set selected to actual profile
if (autotunePlugin.selectedProfile.isNotEmpty())
binding.profileList.setText(autotunePlugin.selectedProfile, false)
else {
binding.profileList.setText(profileList[0], false)
}
binding.autotuneRun.visibility = View.GONE
binding.autotuneCheckInputProfile.visibility = View.GONE
binding.autotuneCopylocal.visibility = View.GONE
binding.autotuneUpdateProfile.visibility = View.GONE
binding.autotuneRevertProfile.visibility = View.GONE
binding.autotuneProfileswitch.visibility = View.GONE
binding.autotuneCompare.visibility = View.GONE
when {
autotunePlugin.calculationRunning -> {
binding.tuneWarning.text = rh.gs(R.string.autotune_warning_during_run)
}
autotunePlugin.lastRunSuccess -> {
binding.autotuneCopylocal.visibility = View.VISIBLE
binding.autotuneUpdateProfile.visibility = autotunePlugin.updateButtonVisibility
binding.autotuneRevertProfile.visibility = if (autotunePlugin.updateButtonVisibility == View.VISIBLE) View.GONE else View.VISIBLE
binding.autotuneProfileswitch.visibility = View.VISIBLE
binding.tuneWarning.text = rh.gs(R.string.autotune_warning_after_run)
binding.autotuneCompare.visibility = View.VISIBLE
}
else -> {
binding.autotuneRun.visibility = (profile?.isValid == true).toVisibility()
binding.autotuneCheckInputProfile.visibility = View.VISIBLE
}
}
binding.tuneLastrun.text = dateUtil.dateAndTimeString(autotunePlugin.lastRun)
showResults()
}
private fun checkNewDay() {
val runToday = autotunePlugin.lastRun > MidnightTime.calc(dateUtil.now() - autotunePlugin.autotuneStartHour * 3600 * 1000L) + autotunePlugin.autotuneStartHour * 3600 * 1000L
if (runToday && autotunePlugin.result != "") {
binding.tuneWarning.text = rh.gs(R.string.autotune_warning_after_run)
} else if (!runToday || autotunePlugin.result.isEmpty()) { //if new day re-init result, default days, warning and button's visibility
resetParam(!runToday)
}
}
private fun addWarnings(): String {
var warning = ""
var nl = ""
if (profileFunction.getProfile() == null) {
warning = rh.gs(R.string.profileswitch_ismissing)
return warning
}
profileFunction.getProfile()?.let { currentProfile ->
profile = ATProfile(profileStore.getSpecificProfile(profileName)?.let { ProfileSealed.Pure(it) } ?: currentProfile, LocalInsulin(""), injector).also { profile ->
if (!profile.isValid) return rh.gs(R.string.autotune_profile_invalid)
if (profile.icSize > 1) {
warning += nl + rh.gs(R.string.autotune_ic_warning, profile.icSize, profile.ic)
nl = "\n"
}
if (profile.isfSize > 1) {
warning += nl + rh.gs(R.string.autotune_isf_warning, profile.isfSize, Profile.fromMgdlToUnits(profile.isf, profileFunction.getUnits()), profileFunction.getUnits().asText)
}
}
}
return warning
}
private fun resetParam(resetDay: Boolean = true) {
binding.tuneWarning.text = addWarnings()
if (resetDay)
autotunePlugin.lastNbDays = sp.getInt(R.string.key_autotune_default_tune_days, 5).toString()
autotunePlugin.result = ""
binding.autotuneResults.removeAllViews()
autotunePlugin.tunedProfile = null
autotunePlugin.lastRunSuccess = false
autotunePlugin.updateButtonVisibility = View.GONE
}
private val textWatcher = object : TextWatcher {
override fun afterTextChanged(s: Editable) {
updateGui()
}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
if (binding.tuneDays.text.isNotEmpty()) {
try {
if (autotunePlugin.calculationRunning)
binding.tuneDays.value = autotunePlugin.lastNbDays.toDouble()
if (binding.tuneDays.value != autotunePlugin.lastNbDays.toDouble()) {
autotunePlugin.lastNbDays = binding.tuneDays.text
resetParam(false)
}
} catch (e: Exception) {
fabricPrivacy.logException(e)
}
}
}
}
private fun showResults() {
context?.let { context ->
runOnUiThread {
_binding?.let {
binding.autotuneResults.removeAllViews()
if (autotunePlugin.result.isNotBlank()) {
var toMgDl = 1.0
if (profileFunction.getUnits() == GlucoseUnit.MMOL) toMgDl = Constants.MMOLL_TO_MGDL
val isfFormat = if (profileFunction.getUnits() == GlucoseUnit.MMOL) "%.2f" else "%.1f"
binding.autotuneResults.addView(
TableLayout(context).also { layout ->
layout.addView(
TextView(context).apply {
text = autotunePlugin.result
setTypeface(typeface, Typeface.BOLD)
gravity = Gravity.CENTER_HORIZONTAL
setTextAppearance(android.R.style.TextAppearance_Material_Medium)
})
autotunePlugin.tunedProfile?.let { tuned ->
layout.addView(toTableRowHeader())
val tuneInsulin = sp.getBoolean(R.string.key_autotune_tune_insulin_curve, false)
if (tuneInsulin) {
layout.addView(
toTableRowValue(
rh.gs(R.string.insulin_peak),
autotunePlugin.pumpProfile.localInsulin.peak.toDouble(),
tuned.localInsulin.peak.toDouble(),
"%.0f"
)
)
layout.addView(
toTableRowValue(
rh.gs(R.string.dia),
Round.roundTo(autotunePlugin.pumpProfile.localInsulin.dia, 0.1),
Round.roundTo(tuned.localInsulin.dia, 0.1),
"%.1f"
)
)
}
layout.addView(
toTableRowValue(
rh.gs(R.string.isf_short),
Round.roundTo(autotunePlugin.pumpProfile.isf / toMgDl, 0.001),
Round.roundTo(tuned.isf / toMgDl, 0.001),
isfFormat
)
)
layout.addView(toTableRowValue(rh.gs(R.string.ic_short), Round.roundTo(autotunePlugin.pumpProfile.ic, 0.001), Round.roundTo(tuned.ic, 0.001), "%.2f"))
layout.addView(
TextView(context).apply {
text = rh.gs(R.string.basal)
setTypeface(typeface, Typeface.BOLD)
gravity = Gravity.CENTER_HORIZONTAL
setTextAppearance(android.R.style.TextAppearance_Material_Medium)
}
)
layout.addView(toTableRowHeader(true))
var totalPump = 0.0
var totalTuned = 0.0
for (h in 0 until tuned.basal.size) {
val df = DecimalFormat("00")
val time = df.format(h.toLong()) + ":00"
totalPump += autotunePlugin.pumpProfile.basal[h]
totalTuned += tuned.basal[h]
layout.addView(toTableRowValue(time, autotunePlugin.pumpProfile.basal[h], tuned.basal[h], "%.3f", tuned.basalUntuned[h].toString()))
}
layout.addView(toTableRowValue("", totalPump, totalTuned, "%.3f", " "))
}
}
)
}
binding.autotuneResultsCard.visibility = if (autotunePlugin.calculationRunning && autotunePlugin.result.isEmpty()) View.GONE else View.VISIBLE
}
}
}
}
private fun toTableRowHeader(basal: Boolean = false): TableRow =
TableRow(context).also { header ->
val lp = TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT, TableRow.LayoutParams.WRAP_CONTENT).apply { weight = 1f }
header.layoutParams = TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.WRAP_CONTENT).apply { gravity = Gravity.CENTER_HORIZONTAL }
header.addView(TextView(context).apply {
layoutParams = lp.apply { column = 0 }
textAlignment = TextView.TEXT_ALIGNMENT_CENTER
text = if (basal) rh.gs(R.string.time) else rh.gs(R.string.autotune_param)
})
header.addView(TextView(context).apply {
layoutParams = lp.apply { column = 1 }
textAlignment = TextView.TEXT_ALIGNMENT_CENTER
text = rh.gs(R.string.profile)
})
header.addView(TextView(context).apply {
layoutParams = lp.apply { column = 2 }
textAlignment = TextView.TEXT_ALIGNMENT_CENTER
text = rh.gs(R.string.autotune_tunedprofile_name)
})
header.addView(TextView(context).apply {
layoutParams = lp.apply { column = 3 }
textAlignment = TextView.TEXT_ALIGNMENT_CENTER
text = rh.gs(R.string.autotune_percent)
})
header.addView(TextView(context).apply {
layoutParams = lp.apply { column = 4 }
textAlignment = TextView.TEXT_ALIGNMENT_CENTER
text = if (basal) rh.gs(R.string.autotune_missing) else " "
})
}
private fun toTableRowValue(hour: String, inputValue: Double, tunedValue: Double, format: String = "%.3f", missing: String = ""): TableRow =
TableRow(context).also { row ->
val percentValue = Round.roundTo(tunedValue / inputValue * 100 - 100, 1.0).toInt().toString() + "%"
val lp = TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT, TableRow.LayoutParams.WRAP_CONTENT).apply { weight = 1f }
row.layoutParams = TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.WRAP_CONTENT).apply { gravity = Gravity.CENTER_HORIZONTAL }
row.addView(TextView(context).apply {
layoutParams = lp.apply { column = 0 }
textAlignment = TextView.TEXT_ALIGNMENT_CENTER
text = hour
})
row.addView(TextView(context).apply {
layoutParams = lp.apply { column = 1 }
textAlignment = TextView.TEXT_ALIGNMENT_CENTER
text = String.format(format, inputValue)
})
row.addView(TextView(context).apply {
layoutParams = lp.apply { column = 2 }
textAlignment = TextView.TEXT_ALIGNMENT_CENTER
text = String.format(format, tunedValue)
})
row.addView(TextView(context).apply {
layoutParams = lp.apply { column = 3 }
textAlignment = TextView.TEXT_ALIGNMENT_CENTER
text = percentValue
})
row.addView(TextView(context).apply {
layoutParams = lp.apply { column = 4 }
textAlignment = TextView.TEXT_ALIGNMENT_CENTER
text = missing
})
}
private fun log(message: String) {
autotuneFS.atLog("[Fragment] $message")
}
}

View file

@ -0,0 +1,398 @@
package info.nightscout.androidaps.plugins.general.autotune
import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R
import info.nightscout.androidaps.data.IobTotal
import info.nightscout.androidaps.data.LocalInsulin
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.embedments.InterfaceIDs
import info.nightscout.androidaps.database.entities.*
import info.nightscout.androidaps.extensions.durationInMinutes
import info.nightscout.androidaps.extensions.iobCalc
import info.nightscout.androidaps.extensions.toJson
import info.nightscout.androidaps.extensions.toTemporaryBasal
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.Profile
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.plugins.general.autotune.data.ATProfile
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.Round
import info.nightscout.androidaps.utils.T
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONArray
import org.json.JSONObject
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.ceil
@Singleton
open class AutotuneIob @Inject constructor(
private val aapsLogger: AAPSLogger,
private val repository: AppRepository,
private val profileFunction: ProfileFunction,
private val sp: SP,
private val dateUtil: DateUtil,
private val activePlugin: ActivePlugin,
private val autotuneFS: AutotuneFS
) {
private var nsTreatments = ArrayList<NsTreatment>()
private var dia: Double = Constants.defaultDIA
var boluses: ArrayList<Bolus> = ArrayList()
var meals = ArrayList<Carbs>()
lateinit var glucose: List<GlucoseValue> // newest at index 0
private lateinit var tempBasals: ArrayList<TemporaryBasal>
var startBG: Long = 0
var endBG: Long = 0
private fun range(): Long = (60 * 60 * 1000L * dia + T.hours(2).msecs()).toLong()
fun initializeData(from: Long, to: Long, tunedProfile: ATProfile) {
dia = tunedProfile.dia
startBG = from
endBG = to
nsTreatments.clear()
tempBasals = ArrayList<TemporaryBasal>()
initializeBgreadings(from, to)
initializeTreatmentData(from - range(), to)
initializeTempBasalData(from - range(), to, tunedProfile)
initializeExtendedBolusData(from - range(), to, tunedProfile)
sortTempBasal()
addNeutralTempBasal(from - range(), to, tunedProfile) // Without Neutral TBR, Autotune Web will ignore iob for periods without TBR running
sortNsTreatments()
sortBoluses()
aapsLogger.debug(LTag.AUTOTUNE, "Nb Treatments: " + nsTreatments.size + " Nb meals: " + meals.size)
}
@Synchronized
private fun sortTempBasal() {
tempBasals = ArrayList(tempBasals.toList().sortedWith { o1: TemporaryBasal, o2: TemporaryBasal -> (o2.timestamp - o1.timestamp).toInt() })
}
@Synchronized
private fun sortNsTreatments() {
nsTreatments = ArrayList(nsTreatments.toList().sortedWith { o1: NsTreatment, o2: NsTreatment -> (o2.date - o1.date).toInt() })
}
@Synchronized
private fun sortBoluses() {
boluses = ArrayList(boluses.toList().sortedWith { o1: Bolus, o2: Bolus -> (o2.timestamp - o1.timestamp).toInt() })
}
private fun initializeBgreadings(from: Long, to: Long) {
glucose = repository.compatGetBgReadingsDataFromTime(from, to, false).blockingGet()
}
//nsTreatment is used only for export data, meals is used in AutotunePrep
private fun initializeTreatmentData(from: Long, to: Long) {
val oldestBgDate = if (glucose.isNotEmpty()) glucose[glucose.size - 1].timestamp else from
aapsLogger.debug(LTag.AUTOTUNE, "Check BG date: BG Size: " + glucose.size + " OldestBG: " + dateUtil.dateAndTimeAndSecondsString(oldestBgDate) + " to: " + dateUtil.dateAndTimeAndSecondsString(to))
val tmpCarbs = repository.getCarbsDataFromTimeToTimeExpanded(from, to, false).blockingGet()
aapsLogger.debug(LTag.AUTOTUNE, "Nb treatments after query: " + tmpCarbs.size)
meals.clear()
boluses.clear()
var nbCarbs = 0
for (i in tmpCarbs.indices) {
val tp = tmpCarbs[i]
if (tp.isValid) {
nsTreatments.add(NsTreatment(tp))
//only carbs after first BGReadings are taken into account in calculation of Autotune
if (tp.amount > 0.0 && tp.timestamp >= oldestBgDate) meals.add(tmpCarbs[i])
if (tp.timestamp < to && tp.amount > 0.0)
nbCarbs++
}
}
val tmpBolus = repository.getBolusesDataFromTimeToTime(from, to, false).blockingGet()
var nbSMB = 0
var nbBolus = 0
for (i in tmpBolus.indices) {
val tp = tmpBolus[i]
if (tp.isValid && tp.type != Bolus.Type.PRIMING) {
boluses.add(tp)
nsTreatments.add(NsTreatment(tp))
//only carbs after first BGReadings are taken into account in calculation of Autotune
if (tp.timestamp < to) {
if (tp.type == Bolus.Type.SMB)
nbSMB++
else if (tp.amount > 0.0)
nbBolus++
}
}
}
//log.debug("AutotunePlugin Nb Meals: $nbCarbs Nb Bolus: $nbBolus Nb SMB: $nbSMB")
}
//nsTreatment is used only for export data
private fun initializeTempBasalData(from: Long, to: Long, tunedProfile: ATProfile) {
val tBRs = repository.getTemporaryBasalsDataFromTimeToTime(from, to, false).blockingGet()
//log.debug("D/AutotunePlugin tempBasal size before cleaning:" + tBRs.size);
for (i in tBRs.indices) {
if (tBRs[i].isValid)
toSplittedTimestampTB(tBRs[i], tunedProfile)
}
//log.debug("D/AutotunePlugin: tempBasal size: " + tempBasals.size)
}
//nsTreatment is used only for export data
private fun initializeExtendedBolusData(from: Long, to: Long, tunedProfile: ATProfile) {
val extendedBoluses = repository.getExtendedBolusDataFromTimeToTime(from, to, false).blockingGet()
val pumpInterface = activePlugin.activePump
if (pumpInterface.isFakingTempsByExtendedBoluses) {
for (i in extendedBoluses.indices) {
val eb = extendedBoluses[i]
if (eb.isValid)
profileFunction.getProfile(eb.timestamp)?.let {
toSplittedTimestampTB(eb.toTemporaryBasal(it), tunedProfile)
}
}
} else {
for (i in extendedBoluses.indices) {
val eb = extendedBoluses[i]
if (eb.isValid) {
nsTreatments.add(NsTreatment(eb))
boluses.addAll(convertToBoluses(eb))
}
}
}
}
// addNeutralTempBasal will add a fake neutral TBR (100%) to have correct basal rate in exported file for periods without TBR running
// to be able to compare results between oref0 algo and aaps
@Synchronized
private fun addNeutralTempBasal(from: Long, to: Long, tunedProfile: ATProfile) {
var previousStart = to
for (i in tempBasals.indices) {
val newStart = tempBasals[i].timestamp + tempBasals[i].duration
if (previousStart - newStart > T.mins(1).msecs()) { // fill neutral only if more than 1 min
val neutralTbr = TemporaryBasal(
isValid = true,
isAbsolute = false,
timestamp = newStart,
rate = 100.0,
duration = previousStart - newStart,
interfaceIDs_backing = InterfaceIDs(nightscoutId = "neutral_" + newStart.toString()),
type = TemporaryBasal.Type.NORMAL
)
toSplittedTimestampTB(neutralTbr, tunedProfile)
}
previousStart = tempBasals[i].timestamp
}
if (previousStart - from > T.mins(1).msecs()) { // fill neutral only if more than 1 min
val neutralTbr = TemporaryBasal(
isValid = true,
isAbsolute = false,
timestamp = from,
rate = 100.0,
duration = previousStart - from,
interfaceIDs_backing = InterfaceIDs(nightscoutId = "neutral_" + from.toString()),
type = TemporaryBasal.Type.NORMAL
)
toSplittedTimestampTB(neutralTbr, tunedProfile)
}
}
// toSplittedTimestampTB will split all TBR across hours in different TBR with correct absolute value to be sure to have correct basal rate
// even if profile rate is not the same
@Synchronized
private fun toSplittedTimestampTB(tb: TemporaryBasal, tunedProfile: ATProfile) {
var splittedTimestamp = tb.timestamp
val cutInMilliSec = T.mins(60).msecs() //30 min to compare with oref0, 60 min to improve accuracy
var splittedDuration = tb.duration
if (tb.isValid && tb.durationInMinutes > 0) {
val endTimestamp = splittedTimestamp + splittedDuration
while (splittedDuration > 0) {
if (Profile.milliSecFromMidnight(splittedTimestamp) / cutInMilliSec == Profile.milliSecFromMidnight(endTimestamp) / cutInMilliSec) {
val newtb = TemporaryBasal(
isValid = true,
isAbsolute = tb.isAbsolute,
timestamp = splittedTimestamp,
rate = tb.rate,
duration = splittedDuration,
interfaceIDs_backing = tb.interfaceIDs_backing,
type = tb.type
)
tempBasals.add(newtb)
nsTreatments.add(NsTreatment(newtb))
splittedDuration = 0
val profile = profileFunction.getProfile(newtb.timestamp) ?:continue
boluses.addAll(convertToBoluses(newtb, profile, tunedProfile.profile)) //
// required for correct iob calculation with oref0 algo
} else {
val durationFilled = (cutInMilliSec - Profile.milliSecFromMidnight(splittedTimestamp) % cutInMilliSec)
val newtb = TemporaryBasal(
isValid = true,
isAbsolute = tb.isAbsolute,
timestamp = splittedTimestamp,
rate = tb.rate,
duration = durationFilled,
interfaceIDs_backing = tb.interfaceIDs_backing,
type = tb.type
)
tempBasals.add(newtb)
nsTreatments.add(NsTreatment(newtb))
splittedTimestamp += durationFilled
splittedDuration -= durationFilled
val profile = profileFunction.getProfile(newtb.timestamp) ?:continue
boluses.addAll(convertToBoluses(newtb, profile, tunedProfile.profile)) // required for correct iob calculation with oref0 algo
}
}
}
}
open fun getIOB(time: Long, localInsulin: LocalInsulin): IobTotal {
val bolusIob = getCalculationToTimeTreatments(time, localInsulin).round()
return bolusIob
}
fun getCalculationToTimeTreatments(time: Long, localInsulin: LocalInsulin): IobTotal {
val total = IobTotal(time)
val detailedLog = sp.getBoolean(R.string.key_autotune_additional_log, false)
for (pos in boluses.indices) {
val t = boluses[pos]
if (!t.isValid) continue
if (t.timestamp > time || t.timestamp < time - localInsulin.duration) continue
val tIOB = t.iobCalc(time, localInsulin)
if (detailedLog)
log("iobCalc;${t.interfaceIDs.nightscoutId};$time;${t.timestamp};${tIOB.iobContrib};${tIOB.activityContrib};${dateUtil.dateAndTimeAndSecondsString(time)};${dateUtil.dateAndTimeAndSecondsString(t.timestamp)}")
total.iob += tIOB.iobContrib
total.activity += tIOB.activityContrib
}
return total
}
fun convertToBoluses(eb: ExtendedBolus): MutableList<Bolus> {
val result: MutableList<Bolus> = ArrayList()
val aboutFiveMinIntervals = ceil(eb.duration / 5.0).toInt()
val spacing = eb.duration / aboutFiveMinIntervals.toDouble()
for (j in 0L until aboutFiveMinIntervals) {
// find middle of the interval
val calcDate = (eb.timestamp + j * spacing * 60 * 1000 + 0.5 * spacing * 60 * 1000).toLong()
val tempBolusSize: Double = eb.amount / aboutFiveMinIntervals
val bolusInterfaceIDs = InterfaceIDs().also { it.nightscoutId = eb.interfaceIDs.nightscoutId + "_eb_$j" }
val tempBolusPart = Bolus(
interfaceIDs_backing = bolusInterfaceIDs,
timestamp = calcDate,
amount = tempBolusSize,
type = Bolus.Type.NORMAL
)
result.add(tempBolusPart)
}
return result
}
fun convertToBoluses(tbr: TemporaryBasal, profile: Profile, tunedProfile: Profile): MutableList<Bolus> {
val result: MutableList<Bolus> = ArrayList()
val realDuration = tbr.durationInMinutes
val basalRate = profile.getBasal(tbr.timestamp)
val tunedRate = tunedProfile.getBasal(tbr.timestamp)
val netBasalRate = Round.roundTo(if (tbr.isAbsolute) {
tbr.rate - tunedRate
} else {
tbr.rate / 100.0 * basalRate - tunedRate
}, 0.001)
val aboutFiveMinIntervals = ceil(realDuration / 5.0).toInt()
val tempBolusSpacing = realDuration / aboutFiveMinIntervals.toDouble()
for (j in 0L until aboutFiveMinIntervals) {
// find middle of the interval
val calcDate = (tbr.timestamp + j * tempBolusSpacing * 60 * 1000 + 0.5 * tempBolusSpacing * 60 * 1000).toLong()
val tempBolusSize = netBasalRate * tempBolusSpacing / 60.0
val bolusInterfaceIDs = InterfaceIDs().also { it.nightscoutId = tbr.interfaceIDs.nightscoutId + "_tbr_$j" }
val tempBolusPart = Bolus(
interfaceIDs_backing = bolusInterfaceIDs,
timestamp = calcDate,
amount = tempBolusSize,
type = Bolus.Type.NORMAL
)
result.add(tempBolusPart)
}
return result
}
@Synchronized
fun glucoseToJSON(): String {
val glucoseJson = JSONArray()
for (bgreading in glucose)
glucoseJson.put(bgreading.toJson(true, dateUtil))
return glucoseJson.toString(2)
}
@Synchronized
fun bolusesToJSON(): String {
val bolusesJson = JSONArray()
for (bolus in boluses)
bolusesJson.put(bolus.toJson(true, dateUtil))
return bolusesJson.toString(2)
}
@Synchronized
fun nsHistoryToJSON(): String {
val json = JSONArray()
for (t in nsTreatments) {
json.put(t.toJson())
}
return json.toString(2).replace("\\/", "/")
}
//I add this internal class to be able to export easily ns-treatment files with same contain and format than NS query used by oref0-autotune
private inner class NsTreatment {
var date: Long = 0
var eventType: TherapyEvent.Type? = null
var carbsTreatment: Carbs? = null
var bolusTreatment: Bolus? = null
var temporaryBasal: TemporaryBasal? = null
var extendedBolus: ExtendedBolus? = null
constructor(t: Carbs) {
carbsTreatment = t
date = t.timestamp
eventType = TherapyEvent.Type.CARBS_CORRECTION
}
constructor(t: Bolus) {
bolusTreatment = t
date = t.timestamp
eventType = TherapyEvent.Type.CORRECTION_BOLUS
}
constructor(t: TemporaryBasal) {
temporaryBasal = t
date = t.timestamp
eventType = TherapyEvent.Type.TEMPORARY_BASAL
}
constructor(t: ExtendedBolus) {
extendedBolus = t
date = t.timestamp
eventType = TherapyEvent.Type.COMBO_BOLUS
}
fun toJson(): JSONObject? {
val cPjson = JSONObject()
return when (eventType) {
TherapyEvent.Type.TEMPORARY_BASAL ->
temporaryBasal?.let { tbr ->
val profile = profileFunction.getProfile(tbr.timestamp)
profile?.let {
tbr.toJson(true, it, dateUtil)
}
}
TherapyEvent.Type.COMBO_BOLUS ->
extendedBolus?.let {
val profile = profileFunction.getProfile(it.timestamp)
it.toJson(true, profile!!, dateUtil)
}
TherapyEvent.Type.CORRECTION_BOLUS -> bolusTreatment?.toJson(true, dateUtil)
TherapyEvent.Type.CARBS_CORRECTION -> carbsTreatment?.toJson(true, dateUtil)
else -> cPjson
}
}
}
private fun log(message: String) {
autotuneFS.atLog("[iob] $message")
}
}

View file

@ -0,0 +1,390 @@
package info.nightscout.androidaps.plugins.general.autotune
import android.view.View
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R
import info.nightscout.androidaps.data.LocalInsulin
import info.nightscout.androidaps.data.ProfileSealed
import info.nightscout.androidaps.database.entities.UserEntry
import info.nightscout.androidaps.database.entities.ValueWithUnit
import info.nightscout.androidaps.extensions.pureProfileFromJson
import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.autotune.data.ATProfile
import info.nightscout.androidaps.plugins.general.autotune.data.PreppedGlucose
import info.nightscout.androidaps.plugins.general.autotune.events.EventAutotuneUpdateGui
import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin
import info.nightscout.androidaps.plugins.profile.local.events.EventLocalProfileChanged
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.JsonHelper
import info.nightscout.androidaps.utils.MidnightTime
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.interfaces.BuildHelper
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONException
import org.json.JSONObject
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
/**
* adaptation from oref0 autotune started by philoul on 2020 (complete refactoring of AutotunePlugin initialised by Rumen Georgiev on 1/29/2018.)
*
* TODO: replace Thread by Worker
* TODO: future version: Allow day of the week selection to tune specifics days (training days, working days, WE days)
*/
@Singleton
class AutotunePlugin @Inject constructor(
injector: HasAndroidInjector,
resourceHelper: ResourceHelper,
private val sp: SP,
private val rxBus: RxBus,
private val profileFunction: ProfileFunction,
private val dateUtil: DateUtil,
private val activePlugin: ActivePlugin,
private val localProfilePlugin: LocalProfilePlugin,
private val autotuneFS: AutotuneFS,
private val autotuneIob: AutotuneIob,
private val autotunePrep: AutotunePrep,
private val autotuneCore: AutotuneCore,
private val buildHelper: BuildHelper,
private val uel: UserEntryLogger,
aapsLogger: AAPSLogger
) : PluginBase(PluginDescription()
.mainType(PluginType.GENERAL)
.fragmentClass(AutotuneFragment::class.qualifiedName)
.pluginIcon(R.drawable.ic_autotune)
.pluginName(R.string.autotune)
.shortName(R.string.autotune_shortname)
.preferencesId(R.xml.pref_autotune)
.description(R.string.autotune_description),
aapsLogger, resourceHelper, injector
), Autotune {
@Volatile override var lastRunSuccess: Boolean = false
@Volatile var result: String = ""
@Volatile override var calculationRunning: Boolean = false
@Volatile var lastRun: Long = 0
@Volatile var selectedProfile = ""
@Volatile var lastNbDays: String = ""
@Volatile var updateButtonVisibility: Int = 0
@Volatile lateinit var pumpProfile: ATProfile
@Volatile var tunedProfile: ATProfile? = null
private var preppedGlucose: PreppedGlucose? = null
private lateinit var profile: Profile
val autotuneStartHour: Int = 4
override fun aapsAutotune(daysBack: Int, autoSwitch: Boolean, profileToTune: String) {
lastRunSuccess = false
if (calculationRunning) {
aapsLogger.debug(LTag.AUTOMATION, "Autotune run detected, Autotune Run Cancelled")
return
}
calculationRunning = true
tunedProfile = null
updateButtonVisibility = View.GONE
var logResult = ""
result = ""
if (profileFunction.getProfile() == null) {
result = rh.gs(R.string.profileswitch_ismissing)
rxBus.send(EventAutotuneUpdateGui())
calculationRunning = false
return
}
val detailedLog = sp.getBoolean(R.string.key_autotune_additional_log, false)
calculationRunning = true
lastNbDays = "" + daysBack
lastRun = dateUtil.now()
val profileStore = activePlugin.activeProfileSource.profile
if (profileStore == null) {
result = rh.gs(R.string.profileswitch_ismissing)
rxBus.send(EventAutotuneUpdateGui())
calculationRunning = false
return
}
selectedProfile = if (profileToTune.isEmpty()) profileFunction.getProfileName() else profileToTune
profileFunction.getProfile()?.let { currentProfile ->
profile = profileStore.getSpecificProfile(profileToTune)?.let { ProfileSealed.Pure(it) } ?: currentProfile
}
val localInsulin = LocalInsulin("PumpInsulin", activePlugin.activeInsulin.peak, profile.dia) // var because localInsulin could be updated later with Tune Insulin peak/dia
log("Start Autotune with $daysBack days back")
autotuneFS.createAutotuneFolder() //create autotune subfolder for autotune files if not exists
autotuneFS.deleteAutotuneFiles() //clean autotune folder before run
// Today at 4 AM
var endTime = MidnightTime.calc(lastRun) + autotuneStartHour * 60 * 60 * 1000L
if (endTime > lastRun) endTime -= 24 * 60 * 60 * 1000L // Check if 4 AM is before now
val starttime = endTime - daysBack * 24 * 60 * 60 * 1000L
autotuneFS.exportSettings(settings(lastRun, daysBack, starttime, endTime))
tunedProfile = ATProfile(profile, localInsulin, injector).also {
it.profilename = rh.gs(R.string.autotune_tunedprofile_name)
}
pumpProfile = ATProfile(profile, localInsulin, injector).also {
it.profilename = selectedProfile
}
autotuneFS.exportPumpProfile(pumpProfile)
for (i in 0 until daysBack) {
val from = starttime + i * 24 * 60 * 60 * 1000L // get 24 hours BG values from 4 AM to 4 AM next day
val to = from + 24 * 60 * 60 * 1000L
log("Tune day " + (i + 1) + " of " + daysBack)
tunedProfile?.let { it ->
autotuneIob.initializeData(from, to, it) //autotuneIob contains BG and Treatments data from history (<=> query for ns-treatments and ns-entries)
autotuneFS.exportEntries(autotuneIob) //<=> ns-entries.yyyymmdd.json files exported for results compare with oref0 autotune on virtual machine
autotuneFS.exportTreatments(autotuneIob) //<=> ns-treatments.yyyymmdd.json files exported for results compare with oref0 autotune on virtual machine (include treatments ,tempBasal and extended
preppedGlucose = autotunePrep.categorize(it) //<=> autotune.yyyymmdd.json files exported for results compare with oref0 autotune on virtual machine
preppedGlucose?.let { preppedGlucose ->
autotuneFS.exportPreppedGlucose(preppedGlucose)
tunedProfile = autotuneCore.tuneAllTheThings(preppedGlucose, it, pumpProfile).also { tunedProfile ->
autotuneFS.exportTunedProfile(tunedProfile) //<=> newprofile.yyyymmdd.json files exported for results compare with oref0 autotune on virtual machine
if (i < daysBack - 1) {
log("Partial result for day ${i + 1}".trimIndent())
result = rh.gs(R.string.autotune_partial_result, i + 1, daysBack)
rxBus.send(EventAutotuneUpdateGui())
}
logResult = showResults(tunedProfile, pumpProfile)
if (detailedLog)
autotuneFS.exportLog(lastRun, i + 1)
}
}
?: {
log("preppedGlucose is null on day ${i + 1}")
tunedProfile = null
}
}
if (tunedProfile == null) {
result = rh.gs(R.string.autotune_error)
log("TunedProfile is null on day ${i + 1}")
autotuneFS.exportResult(result)
autotuneFS.exportLogAndZip(lastRun)
rxBus.send(EventAutotuneUpdateGui())
calculationRunning = false
return
}
}
result = rh.gs(R.string.autotune_result, dateUtil.dateAndTimeString(lastRun))
if (!detailedLog)
autotuneFS.exportLog(lastRun)
autotuneFS.exportResult(logResult)
autotuneFS.zipAutotune(lastRun)
updateButtonVisibility = View.VISIBLE
if (autoSwitch) {
val circadian = sp.getBoolean(R.string.key_autotune_circadian_ic_isf, false)
tunedProfile?.let { tunedP ->
tunedP.profilename = pumpProfile.profilename
updateProfile(tunedP)
uel.log(
UserEntry.Action.STORE_PROFILE,
UserEntry.Sources.Automation,
rh.gs(R.string.autotune),
ValueWithUnit.SimpleString(tunedP.profilename)
)
updateButtonVisibility = View.GONE
tunedP.profileStore(circadian)?.let { profilestore ->
if (profileFunction.createProfileSwitch(
profilestore,
profileName = tunedP.profilename,
durationInMinutes = 0,
percentage = 100,
timeShiftInHours = 0,
timestamp = dateUtil.now()
)
) {
log("Profile Switch succeed ${tunedP.profilename}")
uel.log(
UserEntry.Action.PROFILE_SWITCH,
UserEntry.Sources.Automation,
rh.gs(R.string.autotune),
ValueWithUnit.SimpleString(tunedP.profilename))
}
rxBus.send(EventLocalProfileChanged())
}
}
}
tunedProfile?.let {
saveLastRun()
lastRunSuccess = true
rxBus.send(EventAutotuneUpdateGui())
calculationRunning = false
return
}
result = rh.gs(R.string.autotune_error)
rxBus.send(EventAutotuneUpdateGui())
calculationRunning = false
return
}
private fun showResults(tunedProfile: ATProfile?, pumpProfile: ATProfile): String {
if (tunedProfile == null)
return "No Result" // should never occurs
val line = rh.gs(R.string.autotune_log_separator)
var strResult = line
strResult += rh.gs(R.string.autotune_log_title)
strResult += line
val tuneInsulin = sp.getBoolean(R.string.key_autotune_tune_insulin_curve, false)
if (tuneInsulin) {
strResult += rh.gs(R.string.autotune_log_peak, rh.gs(R.string.insulin_peak), pumpProfile.localInsulin.peak, tunedProfile.localInsulin.peak)
strResult += rh.gs(R.string.autotune_log_dia, rh.gs(R.string.ic_short), pumpProfile.localInsulin.dia, tunedProfile.localInsulin.dia)
}
// show ISF and CR
strResult += rh.gs(R.string.autotune_log_ic_isf, rh.gs(R.string.isf_short), pumpProfile.isf, tunedProfile.isf)
strResult += rh.gs(R.string.autotune_log_ic_isf, rh.gs(R.string.ic_short), pumpProfile.ic, tunedProfile.ic)
strResult += line
var totalBasal = 0.0
var totalTuned = 0.0
for (i in 0..23) {
totalBasal += pumpProfile.basal[i]
totalTuned += tunedProfile.basal[i]
val percentageChangeValue = tunedProfile.basal[i] / pumpProfile.basal[i] * 100 - 100
strResult += rh.gs(R.string.autotune_log_basal, i.toDouble(), pumpProfile.basal[i], tunedProfile.basal[i], tunedProfile.basalUntuned[i], percentageChangeValue)
}
strResult += line
strResult += rh.gs(R.string.autotune_log_sum_basal, totalBasal, totalTuned)
strResult += line
log(strResult)
return strResult
}
private fun settings(runDate: Long, nbDays: Int, firstloopstart: Long, lastloopend: Long): String {
var jsonString = ""
val jsonSettings = JSONObject()
val insulinInterface = activePlugin.activeInsulin
val utcOffset = T.msecs(TimeZone.getDefault().getOffset(dateUtil.now()).toLong()).hours()
val startDateString = dateUtil.toISOString(firstloopstart).substring(0,10)
val endDateString = dateUtil.toISOString(lastloopend - 24 * 60 * 60 * 1000L).substring(0,10)
val nsUrl = sp.getString(R.string.key_nsclientinternal_url, "")
val optCategorizeUam = if (sp.getBoolean(R.string.key_autotune_categorize_uam_as_basal, false)) "-c=true" else ""
val optInsulinCurve = if (sp.getBoolean(R.string.key_autotune_tune_insulin_curve, false)) "-i=true" else ""
try {
jsonSettings.put("datestring", dateUtil.toISOString(runDate))
jsonSettings.put("dateutc", dateUtil.toISOAsUTC(runDate))
jsonSettings.put("utcOffset", utcOffset)
jsonSettings.put("units", profileFunction.getUnits().asText)
jsonSettings.put("timezone", TimeZone.getDefault().id)
jsonSettings.put("url_nightscout", sp.getString(R.string.key_nsclientinternal_url, ""))
jsonSettings.put("nbdays", nbDays)
jsonSettings.put("startdate", startDateString)
jsonSettings.put("enddate", endDateString)
// command to change timezone
jsonSettings.put("timezone_command", "sudo ln -sf /usr/share/zoneinfo/" + TimeZone.getDefault().id + " /etc/localtime")
// oref0_command is for running oref0-autotune on a virtual machine in a dedicated ~/aaps subfolder
jsonSettings.put("oref0_command", "oref0-autotune -d=~/aaps -n=$nsUrl -s=$startDateString -e=$endDateString $optCategorizeUam $optInsulinCurve")
// aaps_command is for running modified oref0-autotune with exported data from aaps (ns-entries and ns-treatment json files copied in ~/aaps/autotune folder and pumpprofile.json copied in ~/aaps/settings/
jsonSettings.put("aaps_command", "aaps-autotune -d=~/aaps -s=$startDateString -e=$endDateString $optCategorizeUam $optInsulinCurve")
jsonSettings.put("categorize_uam_as_basal", sp.getBoolean(R.string.key_autotune_categorize_uam_as_basal, false))
jsonSettings.put("tune_insulin_curve", false)
val peaktime: Int = insulinInterface.peak
if (insulinInterface.id === Insulin.InsulinType.OREF_ULTRA_RAPID_ACTING)
jsonSettings.put("curve","ultra-rapid")
else if (insulinInterface.id === Insulin.InsulinType.OREF_RAPID_ACTING)
jsonSettings.put("curve", "rapid-acting")
else if (insulinInterface.id === Insulin.InsulinType.OREF_LYUMJEV) {
jsonSettings.put("curve", "ultra-rapid")
jsonSettings.put("useCustomPeakTime", true)
jsonSettings.put("insulinPeakTime", peaktime)
} else if (insulinInterface.id === Insulin.InsulinType.OREF_FREE_PEAK) {
jsonSettings.put("curve", if (peaktime > 55) "rapid-acting" else "ultra-rapid")
jsonSettings.put("useCustomPeakTime", true)
jsonSettings.put("insulinPeakTime", peaktime)
}
jsonString = jsonSettings.toString(4).replace("\\/", "/")
} catch (e: JSONException) { }
return jsonString
}
fun updateProfile(newProfile: ATProfile?) {
if (newProfile == null) return
val circadian = sp.getBoolean(R.string.key_autotune_circadian_ic_isf, false)
val profileStore = activePlugin.activeProfileSource.profile ?: ProfileStore(injector, JSONObject(), dateUtil)
val profileList: ArrayList<CharSequence> = profileStore.getProfileList()
var indexLocalProfile = -1
for (p in profileList.indices)
if (profileList[p] == newProfile.profilename)
indexLocalProfile = p
if (indexLocalProfile == -1) {
localProfilePlugin.addProfile(localProfilePlugin.copyFrom(newProfile.getProfile(circadian), newProfile.profilename))
return
}
localProfilePlugin.currentProfileIndex = indexLocalProfile
localProfilePlugin.currentProfile()?.dia = newProfile.dia
localProfilePlugin.currentProfile()?.basal = newProfile.basal()
localProfilePlugin.currentProfile()?.ic = newProfile.ic(circadian)
localProfilePlugin.currentProfile()?.isf = newProfile.isf(circadian)
localProfilePlugin.storeSettings()
}
fun saveLastRun() {
val json = JSONObject()
json.put("lastNbDays", lastNbDays)
json.put("lastRun",lastRun)
json.put("pumpProfile", pumpProfile.profile.toPureNsJson(dateUtil))
json.put("pumpProfileName", pumpProfile.profilename)
json.put("pumpPeak", pumpProfile.peak)
json.put("pumpDia", pumpProfile.dia)
tunedProfile?.let { atProfile ->
json.put("tunedProfile", atProfile.profile.toPureNsJson(dateUtil))
json.put("tunedCircadianProfile", atProfile.circadianProfile.toPureNsJson(dateUtil))
json.put("tunedProfileName", atProfile.profilename)
json.put("tunedPeak", atProfile.peak)
json.put("tunedDia", atProfile.dia)
for (i in 0..23) {
json.put("missingDays_$i", atProfile.basalUntuned[i])
}
}
json.put("result", result)
json.put("updateButtonVisibility", updateButtonVisibility)
sp.putString(R.string.key_autotune_last_run, json.toString())
}
fun loadLastRun() {
result = ""
lastRunSuccess = false
try {
val json = JSONObject(sp.getString(R.string.key_autotune_last_run, ""))
lastNbDays = JsonHelper.safeGetString(json, "lastNbDays", "")
lastRun = JsonHelper.safeGetLong(json, "lastRun")
val pumpPeak = JsonHelper.safeGetInt(json, "pumpPeak")
val pumpDia = JsonHelper.safeGetDouble(json, "pumpDia")
var localInsulin = LocalInsulin("PumpInsulin", pumpPeak, pumpDia)
selectedProfile = JsonHelper.safeGetString(json, "pumpProfileName", "")
val profile = JsonHelper.safeGetJSONObject(json, "pumpProfile", null)?.let { pureProfileFromJson(it, dateUtil) }
?: return
pumpProfile = ATProfile(ProfileSealed.Pure(profile), localInsulin, injector).also { it.profilename = selectedProfile }
val tunedPeak = JsonHelper.safeGetInt(json, "tunedPeak")
val tunedDia = JsonHelper.safeGetDouble(json, "tunedDia")
localInsulin = LocalInsulin("PumpInsulin", tunedPeak, tunedDia)
val tunedProfileName = JsonHelper.safeGetString(json, "tunedProfileName", "")
val tuned = JsonHelper.safeGetJSONObject(json, "tunedProfile", null)?.let { pureProfileFromJson(it, dateUtil) }
?: return
val circadianTuned = JsonHelper.safeGetJSONObject(json, "tunedCircadianProfile", null)?.let { pureProfileFromJson(it, dateUtil) }
?: return
tunedProfile = ATProfile(ProfileSealed.Pure(tuned), localInsulin, injector).also { atProfile ->
atProfile.profilename = tunedProfileName
atProfile.circadianProfile = ProfileSealed.Pure(circadianTuned)
for (i in 0..23) {
atProfile.basalUntuned[i] = JsonHelper.safeGetInt(json,"missingDays_$i")
}
}
result = JsonHelper.safeGetString(json, "result", "")
updateButtonVisibility = JsonHelper.safeGetInt(json, "updateButtonVisibility")
lastRunSuccess = true
} catch (e: Exception) {
}
}
private fun log(message: String) {
atLog("[Plugin] $message")
}
override fun specialEnableCondition(): Boolean = buildHelper.isEngineeringMode() && buildHelper.isDev()
override fun atLog(message: String) {
autotuneFS.atLog(message)
}
}

View file

@ -0,0 +1,563 @@
package info.nightscout.androidaps.plugins.general.autotune
import info.nightscout.androidaps.R
import info.nightscout.androidaps.data.LocalInsulin
import info.nightscout.androidaps.database.entities.GlucoseValue
import info.nightscout.androidaps.plugins.general.autotune.data.*
import info.nightscout.androidaps.database.entities.Bolus
import info.nightscout.androidaps.database.entities.Carbs
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.MidnightTime
import info.nightscout.androidaps.utils.Round
import info.nightscout.androidaps.utils.T
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class AutotunePrep @Inject constructor(
private val aapsLogger: AAPSLogger,
private val sp: SP,
private val dateUtil: DateUtil,
private val autotuneFS: AutotuneFS,
private val autotuneIob: AutotuneIob
) {
fun categorize(tunedprofile: ATProfile): PreppedGlucose? {
val preppedGlucose = categorizeBGDatums(tunedprofile, tunedprofile.localInsulin)
val tuneInsulin = sp.getBoolean(R.string.key_autotune_tune_insulin_curve, false)
if (tuneInsulin) {
var minDeviations = 1000000.0
val diaDeviations: MutableList<DiaDeviation> = ArrayList()
val peakDeviations: MutableList<PeakDeviation> = ArrayList()
val currentDIA = tunedprofile.localInsulin.dia
val currentPeak = tunedprofile.localInsulin.peak
var dia = currentDIA - 2
val endDIA = currentDIA + 2
while (dia <= endDIA)
{
var sqrtDeviations = 0.0
var deviations = 0.0
var deviationsSq = 0.0
val localInsulin = LocalInsulin("Ins_$currentPeak-$dia", currentPeak, dia)
val curve_output = categorizeBGDatums(tunedprofile, localInsulin, false)
val basalGlucose = curve_output?.basalGlucoseData
basalGlucose?.let {
for (hour in 0..23) {
for (i in 0..(basalGlucose.size-1)) {
val myHour = ((basalGlucose[i].date - MidnightTime.calc(basalGlucose[i].date)) / T.hours(1).msecs()).toInt()
if (hour == myHour) {
sqrtDeviations += Math.pow(Math.abs(basalGlucose[i].deviation), 0.5)
deviations += Math.abs(basalGlucose[i].deviation)
deviationsSq += Math.pow(basalGlucose[i].deviation, 2.0)
}
}
}
val meanDeviation = Round.roundTo(Math.abs(deviations / basalGlucose.size), 0.001)
val smrDeviation = Round.roundTo(Math.pow(sqrtDeviations / basalGlucose.size, 2.0), 0.001)
val rmsDeviation = Round.roundTo(Math.pow(deviationsSq / basalGlucose.size, 0.5), 0.001)
log("insulinEndTime $dia meanDeviation: $meanDeviation SMRDeviation: $smrDeviation RMSDeviation: $rmsDeviation (mg/dL)")
diaDeviations.add(
DiaDeviation(
dia = dia,
meanDeviation = meanDeviation,
smrDeviation = smrDeviation,
rmsDeviation = rmsDeviation
)
)
}
preppedGlucose?.diaDeviations = diaDeviations
deviations = Round.roundTo(deviations, 0.001)
if (deviations < minDeviations)
minDeviations = Round.roundTo(deviations, 0.001)
dia += 1.0
}
// consoleError('Optimum insulinEndTime', newDIA, 'mean deviation:', JSMath.Round(minDeviations/basalGlucose.length*1000)/1000, '(mg/dL)');
//consoleError(diaDeviations);
minDeviations = 1000000.0
var peak = currentPeak - 10
val endPeak = currentPeak + 10
while (peak <= endPeak)
{
var sqrtDeviations = 0.0
var deviations = 0.0
var deviationsSq = 0.0
val localInsulin = LocalInsulin("Ins_$peak-$currentDIA", peak, currentDIA)
val curve_output = categorizeBGDatums(tunedprofile, localInsulin, false)
val basalGlucose = curve_output?.basalGlucoseData
basalGlucose?.let {
for (hour in 0..23) {
for (i in 0..(basalGlucose.size - 1)) {
val myHour = ((basalGlucose[i].date - MidnightTime.calc(basalGlucose[i].date)) / T.hours(1).msecs()).toInt()
if (hour == myHour) {
//console.error(basalGlucose[i].deviation);
sqrtDeviations += Math.pow(Math.abs(basalGlucose[i].deviation), 0.5)
deviations += Math.abs(basalGlucose[i].deviation)
deviationsSq += Math.pow(basalGlucose[i].deviation, 2.0)
}
}
}
val meanDeviation = Round.roundTo(deviations / basalGlucose.size, 0.001)
val smrDeviation = Round.roundTo(Math.pow(sqrtDeviations / basalGlucose.size, 2.0), 0.001)
val rmsDeviation = Round.roundTo(Math.pow(deviationsSq / basalGlucose.size, 0.5), 0.001)
log("insulinPeakTime $peak meanDeviation: $meanDeviation SMRDeviation: $smrDeviation RMSDeviation: $rmsDeviation (mg/dL)")
peakDeviations.add(
PeakDeviation
(
peak = peak,
meanDeviation = meanDeviation,
smrDeviation = smrDeviation,
rmsDeviation = rmsDeviation,
)
)
}
deviations = Round.roundTo(deviations, 0.001);
if (deviations < minDeviations)
minDeviations = Round.roundTo(deviations, 0.001)
peak += 5
}
//consoleError($"Optimum insulinPeakTime {newPeak} mean deviation: {JSMath.Round(minDeviations/basalGlucose.Count, 3)} (mg/dL)");
//consoleError(peakDeviations);
preppedGlucose?.peakDeviations = peakDeviations
}
return preppedGlucose
}
// private static Logger log = LoggerFactory.getLogger(AutotunePlugin.class);
fun categorizeBGDatums(tunedprofile: ATProfile, localInsulin: LocalInsulin, verbose: Boolean = true): PreppedGlucose? {
//lib/meals is called before to get only meals data (in AAPS it's done in AutotuneIob)
val treatments: MutableList<Carbs> = autotuneIob.meals
val boluses: MutableList<Bolus> = autotuneIob.boluses
// Bloc between #21 and # 54 replaced by bloc below (just remove BG value below 39, Collections.sort probably not necessary because BG values already sorted...)
val glucose = autotuneIob.glucose
val glucoseData: MutableList<GlucoseValue> = ArrayList()
for (i in glucose.indices) {
if (glucose[i].value > 39) {
glucoseData.add(glucose[i])
}
}
if (glucose.size == 0 || glucoseData.size == 0 ) {
//aapsLogger.debug(LTag.AUTOTUNE, "No BG value received")
if (verbose)
log("No BG value received")
return null
}
glucoseData.sortWith(object: Comparator<GlucoseValue>{ override fun compare(o1: GlucoseValue, o2: GlucoseValue): Int = (o2.timestamp - o1.timestamp).toInt() })
// Bloc below replace bloc between #55 and #71
// boluses and maxCarbs not used here ?,
// IOBInputs are for iob calculation (done here in AutotuneIob Class)
//val boluses = 0
//val maxCarbs = 0
if (treatments.size == 0) {
//aapsLogger.debug(LTag.AUTOTUNE, "No Carbs entries")
if (verbose)
log("No Carbs entries")
//return null
}
if (autotuneIob.boluses.size == 0) {
//aapsLogger.debug(LTag.AUTOTUNE, "No treatment received")
if (verbose)
log("No treatment received")
return null
}
var csfGlucoseData: MutableList<BGDatum> = ArrayList()
var isfGlucoseData: MutableList<BGDatum> = ArrayList()
var basalGlucoseData: MutableList<BGDatum> = ArrayList()
val uamGlucoseData: MutableList<BGDatum> = ArrayList()
val crData: MutableList<CRDatum> = ArrayList()
//Bloc below replace bloc between #72 and #93
// I keep it because BG lines in log are consistent between AAPS and Oref0
val bucketedData: MutableList<BGDatum> = ArrayList()
bucketedData.add(BGDatum(glucoseData[0], dateUtil))
//int j=0;
var k = 0 // index of first value used by bucket
//for loop to validate and bucket the data
for (i in 1 until glucoseData.size) {
val BGTime = glucoseData[i].timestamp
val lastBGTime = glucoseData[k].timestamp
val elapsedMinutes = (BGTime - lastBGTime) / (60 * 1000)
if (Math.abs(elapsedMinutes) >= 2) {
//j++; // move to next bucket
k = i // store index of first value used by bucket
bucketedData.add(BGDatum(glucoseData[i], dateUtil))
} else {
// average all readings within time deadband
val average = glucoseData[k]
for (l in k + 1 until i + 1) {
average.value += glucoseData[l].value
}
average.value = average.value / (i - k + 1)
bucketedData.add(BGDatum(average, dateUtil))
}
}
// Here treatments contains only meals data
// bloc between #94 and #114 remove meals before first BG value
// Bloc below replace bloc between #115 and #122 (initialize data before main loop)
// crInitialxx are declaration to be able to use these data in whole loop
var calculatingCR = false
var absorbing = false
var uam = false // unannounced meal
var mealCOB = 0.0
var mealCarbs = 0.0
var crCarbs = 0.0
var type = ""
var crInitialIOB = 0.0
var crInitialBG = 0.0
var crInitialCarbTime = 0L
//categorize.js#123 (Note: don't need fullHistory because data are managed in AutotuneIob Class)
//Here is main loop between #125 and #366
// main for loop
for (i in bucketedData.size - 5 downTo 1) {
val glucoseDatum = bucketedData[i]
//log.debug(glucoseDatum);
val BGTime = glucoseDatum.date
// As we're processing each data point, go through the treatment.carbs and see if any of them are older than
// the current BG data point. If so, add those carbs to COB.
val treatment = if (treatments.size > 0) treatments[treatments.size - 1] else null
var myCarbs = 0.0
if (treatment != null) {
if (treatment.timestamp < BGTime) {
if (treatment.amount > 0.0) {
mealCOB += treatment.amount
mealCarbs += treatment.amount
myCarbs = treatment.amount
}
treatments.removeAt(treatments.size - 1)
}
}
var bg = 0.0
var avgDelta = 0.0
// TODO: re-implement interpolation to avoid issues here with gaps
// calculate avgDelta as last 4 datapoints to better catch more rises after COB hits zero
if (bucketedData[i].value != 0.0 && bucketedData[i + 4].value != 0.0) {
//log.debug(bucketedData[i]);
bg = bucketedData[i].value
if (bg < 40 || bucketedData[i + 4].value < 40) {
//process.stderr.write("!");
continue
}
avgDelta = (bg - bucketedData[i + 4].value) / 4
} else {
//aapsLogger.debug(LTag.AUTOTUNE, "Could not find glucose data")
if (verbose)
log("Could not find glucose data")
}
avgDelta = Round.roundTo(avgDelta, 0.01)
glucoseDatum.avgDelta = avgDelta
//sens = ISF
val sens = tunedprofile.isf
// for IOB calculations, use the average of the last 4 hours' basals to help convergence;
// this helps since the basal this hour could be different from previous, especially if with autotune they start to diverge.
// use the pumpbasalprofile to properly calculate IOB during periods where no temp basal is set
/* Note Philoul currentPumpBasal never used in oref0 Autotune code
var currentPumpBasal = pumpprofile.profile.getBasal(BGTime)
currentPumpBasal += pumpprofile.profile.getBasal(BGTime - 1 * 60 * 60 * 1000)
currentPumpBasal += pumpprofile.profile.getBasal(BGTime - 2 * 60 * 60 * 1000)
currentPumpBasal += pumpprofile.profile.getBasal(BGTime - 3 * 60 * 60 * 1000)
currentPumpBasal = Round.roundTo(currentPumpBasal / 4, 0.001) //CurrentPumpBasal for iob calculation is average of 4 last pumpProfile Basal rate
*/
// this is the current autotuned basal, used for everything else besides IOB calculations
val currentBasal = tunedprofile.getBasal(BGTime)
// basalBGI is BGI of basal insulin activity.
val basalBGI = Round.roundTo(currentBasal * sens / 60 * 5, 0.01) // U/hr * mg/dL/U * 1 hr / 60 minutes * 5 = mg/dL/5m
//console.log(JSON.stringify(IOBInputs.profile));
// call iob since calculated elsewhere
//var iob = getIOB(IOBInputs)[0];
// in autotune iob is calculated with 6 hours of history data, tunedProfile and average pumpProfile basal rate...
//log("currentBasal: " + currentBasal + " BGTime: " + BGTime + " / " + dateUtil!!.timeStringWithSeconds(BGTime) + "******************************************************************************************")
val iob = autotuneIob.getIOB(BGTime, localInsulin) // add localInsulin to be independent to InsulinPlugin
// activity times ISF times 5 minutes is BGI
val BGI = Round.roundTo(-iob.activity * sens * 5, 0.01)
// datum = one glucose data point (being prepped to store in output)
glucoseDatum.bgi = BGI
// calculating deviation
var deviation = avgDelta - BGI
// set positive deviations to zero if BG is below 80
if (bg < 80 && deviation > 0) {
deviation = 0.0
}
// rounding and storing deviation
deviation = Round.roundTo(deviation, 0.01)
glucoseDatum.deviation = deviation
// Then, calculate carb absorption for that 5m interval using the deviation.
if (mealCOB > 0) {
val ci = Math.max(deviation, sp.getDouble(R.string.key_openapsama_min_5m_carbimpact, 3.0))
val absorbed = ci * tunedprofile.ic / sens
// Store the COB, and use it as the starting point for the next data point.
mealCOB = Math.max(0.0, mealCOB - absorbed)
}
// Calculate carb ratio (CR) independently of CSF and ISF
// Use the time period from meal bolus/carbs until COB is zero and IOB is < currentBasal/2
// For now, if another meal IOB/COB stacks on top of it, consider them together
// Compare beginning and ending BGs, and calculate how much more/less insulin is needed to neutralize
// Use entered carbs vs. starting IOB + delivered insulin + needed-at-end insulin to directly calculate CR.
if (mealCOB > 0 || calculatingCR) {
// set initial values when we first see COB
crCarbs += myCarbs
if (calculatingCR == false) {
crInitialIOB = iob.iob
crInitialBG = glucoseDatum.value
crInitialCarbTime = glucoseDatum.date
//aapsLogger.debug(LTag.AUTOTUNE, "CRInitialIOB: $crInitialIOB CRInitialBG: $crInitialBG CRInitialCarbTime: ${dateUtil.toISOString(crInitialCarbTime)}")
if (verbose)
log("CRInitialIOB: $crInitialIOB CRInitialBG: $crInitialBG CRInitialCarbTime: ${dateUtil.toISOString(crInitialCarbTime)}")
}
// keep calculatingCR as long as we have COB or enough IOB
if (mealCOB > 0 && i > 1) {
calculatingCR = true
} else if (iob.iob > currentBasal / 2 && i > 1) {
calculatingCR = true
// when COB=0 and IOB drops low enough, record end values and be done calculatingCR
} else {
val crEndIOB = iob.iob
val crEndBG = glucoseDatum.value
val crEndTime = glucoseDatum.date
//aapsLogger.debug(LTag.AUTOTUNE, "CREndIOB: $crEndIOB CREndBG: $crEndBG CREndTime: ${dateUtil.toISOString(crEndTime)}")
if (verbose)
log("CREndIOB: $crEndIOB CREndBG: $crEndBG CREndTime: ${dateUtil.toISOString(crEndTime)}")
val crDatum = CRDatum(dateUtil)
crDatum.crInitialBG = crInitialBG
crDatum.crInitialIOB = crInitialIOB
crDatum.crInitialCarbTime = crInitialCarbTime
crDatum.crEndBG = crEndBG
crDatum.crEndIOB = crEndIOB
crDatum.crEndTime = crEndTime
crDatum.crCarbs = crCarbs
//log.debug(CRDatum);
//String crDataString = "{\"CRInitialIOB\": " + CRInitialIOB + ", \"CRInitialBG\": " + CRInitialBG + ", \"CRInitialCarbTime\": " + CRInitialCarbTime + ", \"CREndIOB\": " + CREndIOB + ", \"CREndBG\": " + CREndBG + ", \"CREndTime\": " + CREndTime + ", \"CRCarbs\": " + CRCarbs + "}";
val CRElapsedMinutes = Math.round((crEndTime - crInitialCarbTime) / (1000 * 60).toFloat())
//log.debug(CREndTime - CRInitialCarbTime, CRElapsedMinutes);
if (CRElapsedMinutes < 60 || i == 1 && mealCOB > 0) {
//aapsLogger.debug(LTag.AUTOTUNE, "Ignoring $CRElapsedMinutes m CR period.")
if (verbose)
log("Ignoring $CRElapsedMinutes m CR period.")
} else {
crData.add(crDatum)
}
crCarbs = 0.0
calculatingCR = false
}
}
// If mealCOB is zero but all deviations since hitting COB=0 are positive, assign those data points to CSFGlucoseData
// Once deviations go negative for at least one data point after COB=0, we can use the rest of the data to tune ISF or basals
if (mealCOB > 0 || absorbing || mealCarbs > 0) {
// if meal IOB has decayed, then end absorption after this data point unless COB > 0
absorbing = if (iob.iob < currentBasal / 2) {
false
// otherwise, as long as deviations are positive, keep tracking carb deviations
} else if (deviation > 0) {
true
} else {
false
}
if (!absorbing && mealCOB == 0.0) {
mealCarbs = 0.0
}
// check previous "type" value, and if it wasn't csf, set a mealAbsorption start flag
//log.debug(type);
if (type != "csf") {
glucoseDatum.mealAbsorption = "start"
//aapsLogger.debug(LTag.AUTOTUNE, "${glucoseDatum.mealAbsorption} carb absorption")
if (verbose)
log("${glucoseDatum.mealAbsorption} carb absorption")
}
type = "csf"
glucoseDatum.mealCarbs = mealCarbs.toInt()
//if (i == 0) { glucoseDatum.mealAbsorption = "end"; }
csfGlucoseData.add(glucoseDatum)
} else {
// check previous "type" value, and if it was csf, set a mealAbsorption end flag
if (type == "csf") {
csfGlucoseData[csfGlucoseData.size - 1].mealAbsorption = "end"
//aapsLogger.debug(LTag.AUTOTUNE, "${csfGlucoseData[csfGlucoseData.size - 1].mealAbsorption} carb absorption")
if (verbose)
log("${csfGlucoseData[csfGlucoseData.size - 1].mealAbsorption} carb absorption")
}
if (iob.iob > 2 * currentBasal || deviation > 6 || uam) {
uam = if (deviation > 0) {
true
} else {
false
}
if (type != "uam") {
glucoseDatum.uamAbsorption = "start"
//aapsLogger.debug(LTag.AUTOTUNE, "${glucoseDatum.uamAbsorption} unannnounced meal absorption")
if (verbose)
log(glucoseDatum.uamAbsorption + " unannnounced meal absorption")
}
type = "uam"
uamGlucoseData.add(glucoseDatum)
} else {
if (type == "uam") {
//aapsLogger.debug(LTag.AUTOTUNE, "end unannounced meal absorption")
if (verbose)
log("end unannounced meal absorption")
}
// Go through the remaining time periods and divide them into periods where scheduled basal insulin activity dominates. This would be determined by calculating the BG impact of scheduled basal insulin
// (for example 1U/hr * 48 mg/dL/U ISF = 48 mg/dL/hr = 5 mg/dL/5m), and comparing that to BGI from bolus and net basal insulin activity.
// When BGI is positive (insulin activity is negative), we want to use that data to tune basals
// When BGI is smaller than about 1/4 of basalBGI, we want to use that data to tune basals
// When BGI is negative and more than about 1/4 of basalBGI, we can use that data to tune ISF,
// unless avgDelta is positive: then that's some sort of unexplained rise we don't want to use for ISF, so that means basals
if (basalBGI > -4 * BGI) {
type = "basal"
basalGlucoseData.add(glucoseDatum)
} else {
if (avgDelta > 0 && avgDelta > -2 * BGI) {
//type="unknown"
type = "basal"
basalGlucoseData.add(glucoseDatum)
} else {
type = "ISF"
isfGlucoseData.add(glucoseDatum)
}
}
}
}
// debug line to print out all the things
//aapsLogger.debug(LTag.AUTOTUNE, "${(if (absorbing) 1 else 0)} mealCOB: ${Round.roundTo(mealCOB, 0.1)} mealCarbs: ${Math.round(mealCarbs)} basalBGI: ${Round.roundTo(basalBGI, 0.1)} BGI: ${Round.roundTo(BGI, 0.1)} IOB: ${iob.iob} Activity: ${iob.activity} at ${dateUtil.timeStringWithSeconds(BGTime)} dev: $deviation avgDelta: $avgDelta $type")
if (verbose)
log("${(if (absorbing) 1 else 0)} mealCOB: ${Round.roundTo(mealCOB, 0.1)} mealCarbs: ${Math.round(mealCarbs)} basalBGI: ${Round.roundTo(basalBGI, 0.1)} BGI: ${Round
.roundTo(BGI, 0.1)} IOB: ${iob.iob} Activity: ${iob.activity} at ${dateUtil.timeStringWithSeconds(BGTime)} dev: $deviation avgDelta: $avgDelta $type")
}
//****************************************************************************************************************************************
// categorize.js Lines 372-383
for (crDatum in crData) {
crDatum.crInsulin = dosed(crDatum.crInitialCarbTime, crDatum.crEndTime, boluses)
}
// categorize.js Lines 384-436
val CSFLength = csfGlucoseData.size
var ISFLength = isfGlucoseData.size
val UAMLength = uamGlucoseData.size
var basalLength = basalGlucoseData.size
if (sp.getBoolean(R.string.key_autotune_categorize_uam_as_basal, false)) {
//aapsLogger.debug(LTag.AUTOTUNE, "Categorizing all UAM data as basal.")
if (verbose)
log("Categorizing all UAM data as basal.")
basalGlucoseData.addAll(uamGlucoseData)
} else if (CSFLength > 12) {
//aapsLogger.debug(LTag.AUTOTUNE, "Found at least 1h of carb: assuming meals were announced, and categorizing UAM data as basal.")
if (verbose)
log("Found at least 1h of carb: assuming meals were announced, and categorizing UAM data as basal.")
basalGlucoseData.addAll(uamGlucoseData)
} else {
if (2 * basalLength < UAMLength) {
//log.debug(basalGlucoseData, UAMGlucoseData);
//aapsLogger.debug(LTag.AUTOTUNE, "Warning: too many deviations categorized as UnAnnounced Meals")
//aapsLogger.debug(LTag.AUTOTUNE, "Adding $UAMLength UAM deviations to $basalLength basal ones")
if (verbose) {
log("Warning: too many deviations categorized as UnAnnounced Meals")
log("Adding $UAMLength UAM deviations to $basalLength basal ones")
}
basalGlucoseData.addAll(uamGlucoseData)
//log.debug(basalGlucoseData);
// if too much data is excluded as UAM, add in the UAM deviations, but then discard the highest 50%
basalGlucoseData.sortWith(object: Comparator<BGDatum>{ override fun compare(o1: BGDatum, o2: BGDatum): Int = (100 * o1.deviation - 100 * o2.deviation).toInt() }) //deviation rouded to 0.01, so *100 to avoid crash during sort
val newBasalGlucose: MutableList<BGDatum> = ArrayList()
for (i in 0 until basalGlucoseData.size / 2) {
newBasalGlucose.add(basalGlucoseData[i])
}
//log.debug(newBasalGlucose);
basalGlucoseData = newBasalGlucose
//aapsLogger.debug(LTag.AUTOTUNE, "and selecting the lowest 50%, leaving ${basalGlucoseData.size} basal+UAM ones")
if (verbose)
log("and selecting the lowest 50%, leaving ${basalGlucoseData.size} basal+UAM ones")
}
if (2 * ISFLength < UAMLength) {
//aapsLogger.debug(LTag.AUTOTUNE, "Adding $UAMLength UAM deviations to $ISFLength ISF ones")
if (verbose)
log("Adding $UAMLength UAM deviations to $ISFLength ISF ones")
isfGlucoseData.addAll(uamGlucoseData)
// if too much data is excluded as UAM, add in the UAM deviations to ISF, but then discard the highest 50%
isfGlucoseData.sortWith(object: Comparator<BGDatum>{ override fun compare(o1: BGDatum, o2: BGDatum): Int = (100 * o1.deviation - 100 * o2.deviation).toInt() }) //deviation rouded to 0.01, so *100 to avoid crash during sort
val newISFGlucose: MutableList<BGDatum> = ArrayList()
for (i in 0 until isfGlucoseData.size / 2) {
newISFGlucose.add(isfGlucoseData[i])
}
//console.error(newISFGlucose);
isfGlucoseData = newISFGlucose
//aapsLogger.debug(LTag.AUTOTUNE, "and selecting the lowest 50%, leaving ${isfGlucoseData.size} ISF+UAM ones")
if (verbose)
log("and selecting the lowest 50%, leaving ${isfGlucoseData.size} ISF+UAM ones")
//log.error(ISFGlucoseData.length, UAMLength);
}
}
basalLength = basalGlucoseData.size
ISFLength = isfGlucoseData.size
if (4 * basalLength + ISFLength < CSFLength && ISFLength < 10) {
//aapsLogger.debug(LTag.AUTOTUNE, "Warning: too many deviations categorized as meals")
//aapsLogger.debug(LTag.AUTOTUNE, "Adding $CSFLength CSF deviations to $ISFLength ISF ones")
if (verbose) {
log("Warning: too many deviations categorized as meals")
//log.debug("Adding",CSFLength,"CSF deviations to",basalLength,"basal ones");
//var basalGlucoseData = basalGlucoseData.concat(CSFGlucoseData);
log("Adding $CSFLength CSF deviations to $ISFLength ISF ones")
}
isfGlucoseData.addAll(csfGlucoseData)
csfGlucoseData = ArrayList()
}
// categorize.js Lines 437-444
//aapsLogger.debug(LTag.AUTOTUNE, "CRData: ${crData.size} CSFGlucoseData: ${csfGlucoseData.size} ISFGlucoseData: ${isfGlucoseData.size} BasalGlucoseData: ${basalGlucoseData.size}")
if (verbose)
log("CRData: ${crData.size} CSFGlucoseData: ${csfGlucoseData.size} ISFGlucoseData: ${isfGlucoseData.size} BasalGlucoseData: ${basalGlucoseData.size}")
return PreppedGlucose(autotuneIob.startBG, crData, csfGlucoseData, isfGlucoseData, basalGlucoseData, dateUtil)
}
//dosed.js full
private fun dosed(start: Long, end: Long, treatments: List<Bolus>): Double {
var insulinDosed = 0.0
//aapsLogger.debug(LTag.AUTOTUNE, "No treatments to process.")
if (treatments.size == 0) {
log("No treatments to process.")
return 0.0
}
for (treatment in treatments) {
if (treatment.amount != 0.0 && treatment.timestamp > start && treatment.timestamp <= end) {
insulinDosed += treatment.amount
//log("CRDATA;${dateUtil.toISOString(start)};${dateUtil.toISOString(end)};${treatment.timestamp};${treatment.amount};$insulinDosed")
}
}
//log("insulin dosed: " + insulinDosed);
return Round.roundTo(insulinDosed, 0.001)
}
private fun log(message: String) {
autotuneFS.atLog("[Prep] $message")
}
}

View file

@ -0,0 +1,254 @@
package info.nightscout.androidaps.plugins.general.autotune.data
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.core.R
import info.nightscout.androidaps.data.LocalInsulin
import info.nightscout.androidaps.data.ProfileSealed
import info.nightscout.androidaps.data.PureProfile
import info.nightscout.androidaps.database.data.Block
import info.nightscout.androidaps.extensions.blockValueBySeconds
import info.nightscout.androidaps.extensions.pureProfileFromJson
import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.Round
import info.nightscout.androidaps.utils.T
import info.nightscout.shared.SafeParse
import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import java.text.DecimalFormat
import java.util.*
import javax.inject.Inject
class ATProfile(profile: Profile, var localInsulin: LocalInsulin, val injector: HasAndroidInjector) {
@Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var sp: SP
@Inject lateinit var profileFunction: ProfileFunction
@Inject lateinit var dateUtil: DateUtil
@Inject lateinit var config: Config
@Inject lateinit var rxBus: RxBus
@Inject lateinit var rh: ResourceHelper
var profile: ProfileSealed
var circadianProfile: ProfileSealed
lateinit var pumpProfile: ProfileSealed
var profilename: String = ""
var basal = DoubleArray(24)
var basalUntuned = IntArray(24)
var ic = 0.0
var isf = 0.0
var dia = 0.0
var peak = 0
var isValid: Boolean = false
var from: Long = 0
var pumpProfileAvgISF = 0.0
var pumpProfileAvgIC = 0.0
val icSize: Int
get() = profile.getIcsValues().size
val isfSize: Int
get() = profile.getIsfsMgdlValues().size
val avgISF: Double
get() = if (profile.getIsfsMgdlValues().size == 1) profile.getIsfsMgdlValues().get(0).value else Round.roundTo(averageProfileValue(profile.getIsfsMgdlValues()), 0.01)
val avgIC: Double
get() = if (profile.getIcsValues().size == 1) profile.getIcsValues().get(0).value else Round.roundTo(averageProfileValue(profile.getIcsValues()), 0.01)
fun getBasal(timestamp: Long): Double = basal[Profile.secondsFromMidnight(timestamp)/3600]
// for localProfilePlugin Synchronisation
fun basal() = jsonArray(basal)
fun ic(circadian: Boolean = false): JSONArray {
if(circadian)
return jsonArray(pumpProfile.icBlocks, avgIC/pumpProfileAvgIC)
return jsonArray(ic)
}
fun isf(circadian: Boolean = false): JSONArray {
if(circadian)
return jsonArray(pumpProfile.isfBlocks, avgISF/pumpProfileAvgISF)
return jsonArray(Profile.fromMgdlToUnits(isf, profile.units))
}
fun getProfile(circadian: Boolean = false): PureProfile {
return if (circadian)
circadianProfile.convertToNonCustomizedProfile(dateUtil)
else
profile.convertToNonCustomizedProfile(dateUtil)
}
fun updateProfile() {
data()?.let { profile = ProfileSealed.Pure(it) }
data(true)?.let { circadianProfile = ProfileSealed.Pure(it) }
}
//Export json string with oref0 format used for autotune
// Include min_5m_carbimpact, insulin type, single value for carb_ratio and isf
fun profiletoOrefJSON(): String {
var jsonString = ""
val json = JSONObject()
val insulinInterface: Insulin = activePlugin.activeInsulin
try {
json.put("name", profilename)
json.put("min_5m_carbimpact", sp.getDouble("openapsama_min_5m_carbimpact", 3.0))
json.put("dia", dia)
if (insulinInterface.id === Insulin.InsulinType.OREF_ULTRA_RAPID_ACTING) json.put(
"curve",
"ultra-rapid"
) else if (insulinInterface.id === Insulin.InsulinType.OREF_RAPID_ACTING) json.put("curve", "rapid-acting") else if (insulinInterface.id === Insulin.InsulinType.OREF_LYUMJEV) {
json.put("curve", "ultra-rapid")
json.put("useCustomPeakTime", true)
json.put("insulinPeakTime", 45)
} else if (insulinInterface.id === Insulin.InsulinType.OREF_FREE_PEAK) {
val peaktime: Int = sp.getInt(rh.gs(R.string.key_insulin_oref_peak), 75)
json.put("curve", if (peaktime > 50) "rapid-acting" else "ultra-rapid")
json.put("useCustomPeakTime", true)
json.put("insulinPeakTime", peaktime)
}
val basals = JSONArray()
for (h in 0..23) {
val secondfrommidnight = h * 60 * 60
var time: String
time = DecimalFormat("00").format(h) + ":00:00"
basals.put(
JSONObject()
.put("start", time)
.put("minutes", h * 60)
.put("rate", profile.getBasalTimeFromMidnight(secondfrommidnight)
)
)
}
json.put("basalprofile", basals)
val isfvalue = Round.roundTo(avgISF, 0.001)
json.put(
"isfProfile",
JSONObject().put(
"sensitivities",
JSONArray().put(JSONObject().put("i", 0).put("start", "00:00:00").put("sensitivity", isfvalue).put("offset", 0).put("x", 0).put("endoffset", 1440))
)
)
json.put("carb_ratio", avgIC)
json.put("autosens_max", SafeParse.stringToDouble(sp.getString(R.string.key_openapsama_autosens_max, "1.2")))
json.put("autosens_min", SafeParse.stringToDouble(sp.getString(R.string.key_openapsama_autosens_min, "0.7")))
json.put("units", GlucoseUnit.MGDL.asText)
json.put("timezone", TimeZone.getDefault().id)
jsonString = json.toString(2).replace("\\/", "/")
} catch (e: JSONException) {}
return jsonString
}
fun data(circadian: Boolean = false): PureProfile? {
val json: JSONObject = profile.toPureNsJson(dateUtil)
try {
json.put("dia", dia)
if (circadian) {
json.put("sens", jsonArray(pumpProfile.isfBlocks, avgISF/pumpProfileAvgISF))
json.put("carbratio", jsonArray(pumpProfile.icBlocks, avgIC/pumpProfileAvgIC))
} else {
json.put("sens", jsonArray(Profile.fromMgdlToUnits(isf, profile.units)))
json.put("carbratio", jsonArray(ic))
}
json.put("basal", jsonArray(basal))
} catch (e: JSONException) {
}
return pureProfileFromJson(json, dateUtil, profile.units.asText)
}
fun profileStore(circadian: Boolean = false): ProfileStore?
{
var profileStore: ProfileStore? = null
val json = JSONObject()
val store = JSONObject()
val tunedProfile = if (circadian) circadianProfile else profile
if (profilename.isEmpty())
profilename = rh.gs(R.string.autotune_tunedprofile_name)
try {
store.put(profilename, tunedProfile.toPureNsJson(dateUtil))
json.put("defaultProfile", profilename)
json.put("store", store)
json.put("startDate", dateUtil.toISOAsUTC(dateUtil.now()))
profileStore = ProfileStore(injector, json, dateUtil)
} catch (e: JSONException) { }
return profileStore
}
fun jsonArray(values: DoubleArray): JSONArray {
val json = JSONArray()
for (h in 0..23) {
val secondfrommidnight = h * 60 * 60
val df = DecimalFormat("00")
val time = df.format(h.toLong()) + ":00"
json.put(
JSONObject()
.put("time", time)
.put("timeAsSeconds", secondfrommidnight)
.put("value", values[h])
)
}
return json
}
fun jsonArray(value: Double) =
JSONArray().put(
JSONObject()
.put("time", "00:00")
.put("timeAsSeconds", 0)
.put("value", value)
)
fun jsonArray(values: List<Block>, multiplier: Double = 1.0): JSONArray {
val json = JSONArray()
var elapsedHours = 0L
values.forEach {
val value = values.blockValueBySeconds(T.hours(elapsedHours).secs().toInt(), multiplier, 0)
json.put(
JSONObject()
.put("time", DecimalFormat("00").format(elapsedHours) + ":00")
.put("timeAsSeconds", T.hours(elapsedHours).secs())
.put("value", value)
)
elapsedHours += T.msecs(it.duration).hours()
}
return json
}
companion object {
fun averageProfileValue(pf: Array<Profile.ProfileValue>?): Double {
var avgValue = 0.0
val secondPerDay = 24 * 60 * 60
if (pf == null) return avgValue
for (i in pf.indices) {
avgValue += pf[i].value * ((if (i == pf.size - 1) secondPerDay else pf[i + 1].timeAsSeconds) - pf[i].timeAsSeconds)
}
avgValue /= secondPerDay.toDouble()
return avgValue
}
}
init {
injector.androidInjector().inject(this)
this.profile = profile as ProfileSealed
circadianProfile = profile
isValid = profile.isValid
if (isValid) {
//initialize tuned value with current profile values
var minBasal = 1.0
for (h in 0..23) {
basal[h] = Round.roundTo(profile.basalBlocks.blockValueBySeconds(T.hours(h.toLong()).secs().toInt(), 1.0, 0), 0.001)
minBasal = Math.min(minBasal, basal[h])
}
ic = avgIC
isf = avgISF
if (ic * isf * minBasal == 0.0) // Additional validity check to avoid error later in AutotunePrep
isValid = false
pumpProfile = profile
pumpProfileAvgIC = avgIC
pumpProfileAvgISF = avgISF
}
dia = localInsulin.dia
peak = localInsulin.peak
}
}

View file

@ -0,0 +1,91 @@
package info.nightscout.androidaps.plugins.general.autotune.data
import info.nightscout.androidaps.database.entities.GlucoseValue.TrendArrow
import info.nightscout.androidaps.database.entities.GlucoseValue
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.T
import org.json.JSONException
import org.json.JSONObject
import java.util.*
/**
* Created by Rumen Georgiev on 2/24/2018.
*/
class BGDatum {
//Added by Rumen for autotune
var id: Long = 0
var date = 0L
var value = 0.0
var direction: TrendArrow? = null
var deviation = 0.0
var bgi = 0.0
var mealAbsorption = ""
var mealCarbs = 0
var uamAbsorption = ""
var avgDelta = 0.0
var bgReading: GlucoseValue? = null
private set
var dateUtil: DateUtil
constructor(dateUtil: DateUtil) { this.dateUtil = dateUtil}
constructor(json: JSONObject, dateUtil: DateUtil) {
this.dateUtil = dateUtil
try {
//if (json.has("_id")) id = json.getLong("_id")
if (json.has("date")) date = json.getLong("date")
if (json.has("sgv")) value = json.getDouble("sgv")
if (json.has("direction")) direction = TrendArrow.fromString(json.getString("direction"))
if (json.has("deviation")) deviation = json.getDouble("deviation")
if (json.has("BGI")) bgi = json.getDouble("BGI")
if (json.has("avgDelta")) avgDelta = json.getDouble("avgDelta")
if (json.has("mealAbsorption")) mealAbsorption = json.getString("mealAbsorption")
if (json.has("mealCarbs")) mealCarbs = json.getInt("mealCarbs")
} catch (e: JSONException) {
}
}
constructor(glucoseValue: GlucoseValue, dateUtil: DateUtil) {
this.dateUtil = dateUtil
date = glucoseValue.timestamp
value = glucoseValue.value
direction = glucoseValue.trendArrow
id = glucoseValue.id
this.bgReading = glucoseValue
}
fun toJSON(mealData: Boolean): JSONObject {
val bgjson = JSONObject()
val utcOffset = T.msecs(TimeZone.getDefault().getOffset(dateUtil.now()).toLong()).hours()
try {
bgjson.put("_id", id)
bgjson.put("date", date)
bgjson.put("dateString", dateUtil.toISOAsUTC(date))
bgjson.put("sgv", value)
bgjson.put("direction", direction)
bgjson.put("type", "sgv")
bgjson.put("sysTime", dateUtil.toISOAsUTC(date))
bgjson.put("utcOffset", utcOffset)
bgjson.put("glucose", value)
bgjson.put("avgDelta", avgDelta)
bgjson.put("BGI", bgi)
bgjson.put("deviation", deviation)
if (mealData) {
bgjson.put("mealAbsorption", mealAbsorption)
bgjson.put("mealCarbs", mealCarbs)
}
} catch (e: JSONException) {
}
return bgjson
}
fun equals(obj: BGDatum): Boolean {
var isEqual = true
if (date / 1000 != obj.date / 1000) isEqual = false
if (deviation != obj.deviation) isEqual = false
if (avgDelta != obj.avgDelta) isEqual = false
if (bgi != obj.bgi) isEqual = false
if (mealAbsorption != obj.mealAbsorption) isEqual = false
if (mealCarbs != obj.mealCarbs) isEqual = false
return isEqual
}
}

View file

@ -0,0 +1,67 @@
package info.nightscout.androidaps.plugins.general.autotune.data
import info.nightscout.androidaps.utils.DateUtil
import org.json.JSONException
import org.json.JSONObject
/**
* Created by Rumen Georgiev on 2/26/2018.
*/
class CRDatum {
var crInitialIOB = 0.0
var crInitialBG = 0.0
var crInitialCarbTime = 0L
var crEndIOB = 0.0
var crEndBG = 0.0
var crEndTime = 0L
var crCarbs = 0.0
var crInsulin = 0.0
var crInsulinTotal = 0.0
var dateUtil: DateUtil
constructor(dateUtil: DateUtil) { this.dateUtil = dateUtil}
constructor(json: JSONObject, dateUtil: DateUtil) {
this.dateUtil = dateUtil
try {
if (json.has("CRInitialIOB")) crInitialIOB = json.getDouble("CRInitialIOB")
if (json.has("CRInitialBG")) crInitialBG = json.getDouble("CRInitialBG")
if (json.has("CRInitialCarbTime")) crInitialCarbTime = dateUtil.fromISODateString(json.getString("CRInitialCarbTime"))
if (json.has("CREndIOB")) crEndIOB = json.getDouble("CREndIOB")
if (json.has("CREndBG")) crEndBG = json.getDouble("CREndBG")
if (json.has("CREndTime")) crEndTime = dateUtil.fromISODateString(json.getString("CREndTime"))
if (json.has("CRCarbs")) crCarbs = json.getDouble("CRCarbs")
if (json.has("CRInsulin")) crInsulin = json.getDouble("CRInsulin")
} catch (e: JSONException) {
}
}
fun toJSON(): JSONObject {
val crjson = JSONObject()
try {
crjson.put("CRInitialIOB", crInitialIOB)
crjson.put("CRInitialBG", crInitialBG.toInt())
crjson.put("CRInitialCarbTime", dateUtil.toISOString(crInitialCarbTime))
crjson.put("CREndIOB", crEndIOB)
crjson.put("CREndBG", crEndBG.toInt())
crjson.put("CREndTime", dateUtil.toISOString(crEndTime))
crjson.put("CRCarbs", crCarbs.toInt())
crjson.put("CRInsulin", crInsulin)
} catch (e: JSONException) {
}
return crjson
}
fun equals(obj: CRDatum): Boolean {
var isEqual = true
if (crInitialIOB != obj.crInitialIOB) isEqual = false
if (crInitialBG != obj.crInitialBG) isEqual = false
if (crInitialCarbTime / 1000 != obj.crInitialCarbTime / 1000) isEqual = false
if (crEndIOB != obj.crEndIOB) isEqual = false
if (crEndBG != obj.crEndBG) isEqual = false
if (crEndTime / 1000 != obj.crEndTime / 1000) isEqual = false
if (crCarbs != obj.crCarbs) isEqual = false
if (crInsulin != obj.crInsulin) isEqual = false
return isEqual
}
}

View file

@ -0,0 +1,29 @@
package info.nightscout.androidaps.plugins.general.autotune.data
import org.json.JSONException
import org.json.JSONObject
class DiaDeviation(var dia: Double = 0.0, var meanDeviation: Double = 0.0, var smrDeviation: Double = 0.0, var rmsDeviation: Double = 0.0) {
constructor(json: JSONObject) : this() {
try {
if (json.has("dia")) dia = json.getDouble("dia")
if (json.has("meanDeviation")) meanDeviation = json.getDouble("meanDeviation")
if (json.has("SMRDeviation")) smrDeviation = json.getDouble("SMRDeviation")
if (json.has("RMSDeviation")) rmsDeviation = json.getDouble("RMSDeviation")
} catch (e: JSONException) {
}
}
fun toJSON(): JSONObject {
val crjson = JSONObject()
try {
crjson.put("dia", dia)
crjson.put("meanDeviation", meanDeviation.toInt())
crjson.put("SMRDeviation", smrDeviation)
crjson.put("RMSDeviation", rmsDeviation.toInt())
} catch (e: JSONException) {
}
return crjson
}
}

View file

@ -0,0 +1,29 @@
package info.nightscout.androidaps.plugins.general.autotune.data
import org.json.JSONException
import org.json.JSONObject
class PeakDeviation(var peak: Int = 0, var meanDeviation: Double = 0.0, var smrDeviation: Double = 0.0, var rmsDeviation: Double = 0.0) {
constructor(json: JSONObject) : this() {
try {
if (json.has("peak")) peak = json.getInt("peak")
if (json.has("meanDeviation")) meanDeviation = json.getDouble("meanDeviation")
if (json.has("SMRDeviation")) smrDeviation = json.getDouble("SMRDeviation")
if (json.has("RMSDeviation")) rmsDeviation = json.getDouble("RMSDeviation")
} catch (e: JSONException) {
}
}
fun toJSON(): JSONObject {
val crjson = JSONObject()
try {
crjson.put("peak", peak)
crjson.put("meanDeviation", meanDeviation.toInt())
crjson.put("SMRDeviation", smrDeviation)
crjson.put("RMSDeviation", rmsDeviation.toInt())
} catch (e: JSONException) {
}
return crjson
}
}

View file

@ -0,0 +1,117 @@
package info.nightscout.androidaps.plugins.general.autotune.data
import info.nightscout.androidaps.utils.DateUtil
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import java.util.*
class PreppedGlucose {
var crData: List<CRDatum> = ArrayList()
var csfGlucoseData: List<BGDatum> = ArrayList()
var isfGlucoseData: List<BGDatum> = ArrayList()
var basalGlucoseData: List<BGDatum> = ArrayList()
var diaDeviations: List<DiaDeviation> = ArrayList()
var peakDeviations: List<PeakDeviation> = ArrayList()
var from: Long = 0
lateinit var dateUtil: DateUtil
// to generate same king of json string than oref0-autotune-prep
override fun toString(): String {
return toString(0)
}
constructor(from: Long, crData: List<CRDatum>, csfGlucoseData: List<BGDatum>, isfGlucoseData: List<BGDatum>, basalGlucoseData: List<BGDatum>, dateUtil: DateUtil) {
this.from = from
this.crData = crData
this.csfGlucoseData = csfGlucoseData
this.isfGlucoseData = isfGlucoseData
this.basalGlucoseData = basalGlucoseData
this.dateUtil = dateUtil
}
constructor(json: JSONObject?, dateUtil: DateUtil) {
if (json == null) return
this.dateUtil = dateUtil
crData = ArrayList()
csfGlucoseData = ArrayList()
isfGlucoseData = ArrayList()
basalGlucoseData = ArrayList()
try {
crData = JsonCRDataToList(json.getJSONArray("CRData"))
csfGlucoseData = JsonGlucoseDataToList(json.getJSONArray("CSFGlucoseData"))
isfGlucoseData = JsonGlucoseDataToList(json.getJSONArray("ISFGlucoseData"))
basalGlucoseData = JsonGlucoseDataToList(json.getJSONArray("basalGlucoseData"))
} catch (e: JSONException) {
}
}
private fun JsonGlucoseDataToList(array: JSONArray): List<BGDatum> {
val bgData: MutableList<BGDatum> = ArrayList()
for (index in 0 until array.length()) {
try {
val o = array.getJSONObject(index)
bgData.add(BGDatum(o, dateUtil))
} catch (e: Exception) {
}
}
return bgData
}
private fun JsonCRDataToList(array: JSONArray): List<CRDatum> {
val crData: MutableList<CRDatum> = ArrayList()
for (index in 0 until array.length()) {
try {
val o = array.getJSONObject(index)
crData.add(CRDatum(o, dateUtil))
} catch (e: Exception) {
}
}
return crData
}
fun toString(indent: Int): String {
var jsonString = ""
val json = JSONObject()
try {
val crjson = JSONArray()
for (crd in crData) {
crjson.put(crd.toJSON())
}
val csfjson = JSONArray()
for (bgd in csfGlucoseData) {
csfjson.put(bgd.toJSON(true))
}
val isfjson = JSONArray()
for (bgd in isfGlucoseData) {
isfjson.put(bgd.toJSON(false))
}
val basaljson = JSONArray()
for (bgd in basalGlucoseData) {
basaljson.put(bgd.toJSON(false))
}
val diajson = JSONArray()
val peakjson = JSONArray()
if (diaDeviations.size > 0 || peakDeviations.size > 0) {
for (diad in diaDeviations) {
diajson.put(diad.toJSON())
}
for (peakd in peakDeviations) {
peakjson.put(peakd.toJSON())
}
}
json.put("CRData", crjson)
json.put("CSFGlucoseData", csfjson)
json.put("ISFGlucoseData", isfjson)
json.put("basalGlucoseData", basaljson)
if (diaDeviations.size > 0 || peakDeviations.size > 0) {
json.put("diaDeviations", diajson)
json.put("peakDeviations", peakjson)
}
jsonString = if (indent != 0) json.toString(indent) else json.toString()
} catch (e: JSONException) {
}
return jsonString
}
}

View file

@ -0,0 +1,5 @@
package info.nightscout.androidaps.plugins.general.autotune.events
import info.nightscout.androidaps.events.Event
class EventAutotuneUpdateGui : Event()

View file

@ -6,12 +6,11 @@ import android.content.pm.ResolveInfo
import android.os.Bundle
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R
import info.nightscout.androidaps.events.*
import info.nightscout.androidaps.events.Event
import info.nightscout.androidaps.events.EventAutosensCalculationFinished
import info.nightscout.androidaps.extensions.durationInMinutes
import info.nightscout.androidaps.extensions.toStringFull
import info.nightscout.androidaps.interfaces.*
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.plugins.aps.events.EventOpenAPSUpdateGui
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.nsclient.data.DeviceStatusData
@ -19,13 +18,15 @@ import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus
import info.nightscout.androidaps.plugins.general.overview.events.EventOverviewBolusProgress
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProvider
import info.nightscout.androidaps.receivers.ReceiverStatusStore
import info.nightscout.androidaps.services.Intents
import info.nightscout.androidaps.receivers.Intents
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.DefaultValueHelper
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import io.reactivex.disposables.CompositeDisposable
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import io.reactivex.rxjava3.disposables.CompositeDisposable
import javax.inject.Inject
import javax.inject.Singleton
@ -68,26 +69,6 @@ class DataBroadcastPlugin @Inject constructor(
.observeOn(aapsSchedulers.io)
.subscribe({ sendData(it) }, fabricPrivacy::logException)
)
disposable.add(rxBus
.toObservable(EventExtendedBolusChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ sendData(it) }, fabricPrivacy::logException)
)
disposable.add(rxBus
.toObservable(EventTempBasalChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ sendData(it) }, fabricPrivacy::logException)
)
disposable.add(rxBus
.toObservable(EventTreatmentChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ sendData(it) }, fabricPrivacy::logException)
)
disposable.add(rxBus
.toObservable(EventEffectiveProfileSwitchChanged::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ sendData(it) }, fabricPrivacy::logException)
)
disposable.add(rxBus
.toObservable(EventAutosensCalculationFinished::class.java)
.observeOn(aapsSchedulers.io)

View file

@ -1,7 +1,6 @@
package info.nightscout.androidaps.plugins.general.food
import android.annotation.SuppressLint
import android.graphics.Paint
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
@ -18,32 +17,27 @@ import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.entities.Food
import info.nightscout.androidaps.database.entities.UserEntry.Action
import info.nightscout.androidaps.database.entities.UserEntry.Sources
import info.nightscout.androidaps.database.entities.ValueWithUnit
import info.nightscout.androidaps.database.transactions.InvalidateFoodTransaction
import info.nightscout.androidaps.databinding.FoodFragmentBinding
import info.nightscout.androidaps.databinding.FoodItemBinding
import info.nightscout.androidaps.dialogs.WizardDialog
import info.nightscout.androidaps.events.EventFoodDatabaseChanged
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.extensions.toVisibility
import info.nightscout.androidaps.utils.protection.ProtectionCheck
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.androidaps.utils.ui.UIRunnable
import io.reactivex.Completable
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign
import io.reactivex.rxkotlin.subscribeBy
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import java.util.*
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlin.collections.ArrayList
class FoodFragment : DaggerFragment() {
@ -66,10 +60,8 @@ class FoodFragment : DaggerFragment() {
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = FoodFragmentBinding.inflate(inflater, container, false)
return binding.root
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
FoodFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -77,50 +69,14 @@ class FoodFragment : DaggerFragment() {
binding.recyclerview.setHasFixedSize(true)
binding.recyclerview.layoutManager = LinearLayoutManager(view.context)
binding.refreshFromNightscout.setOnClickListener {
context?.let { context ->
OKDialog.showConfirmation(context, rh.gs(R.string.refresheventsfromnightscout) + " ?", {
uel.log(Action.FOOD, Sources.Food, rh.gs(R.string.refresheventsfromnightscout),
ValueWithUnit.SimpleString(rh.gsNotLocalised(R.string.refresheventsfromnightscout)))
disposable += Completable.fromAction { repository.deleteAllFoods() }
.subscribeOn(aapsSchedulers.io)
.observeOn(aapsSchedulers.main)
.subscribeBy(
onError = { aapsLogger.error("Error removing foods", it) },
onComplete = { rxBus.send(EventFoodDatabaseChanged()) }
)
rxBus.send(EventNSClientRestart())
})
}
}
binding.clearfilter.setOnClickListener {
binding.filterInputLayout.setEndIconOnClickListener {
binding.filter.setText("")
binding.category.setSelection(0)
binding.subcategory.setSelection(0)
binding.categoryList.setText(rh.gs(R.string.none), false)
binding.subcategoryList.setText(rh.gs(R.string.none), false)
filterData()
}
binding.category.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
fillSubcategories()
filterData()
}
override fun onNothingSelected(parent: AdapterView<*>?) {
fillSubcategories()
filterData()
}
}
binding.subcategory.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
filterData()
}
override fun onNothingSelected(parent: AdapterView<*>?) {
filterData()
}
}
binding.categoryList.onItemClickListener = AdapterView.OnItemClickListener { _, _, _, _ -> fillSubcategories(); filterData() }
binding.subcategoryList.onItemClickListener = AdapterView.OnItemClickListener { _, _, _, _ -> filterData() }
binding.filter.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
@ -134,12 +90,11 @@ class FoodFragment : DaggerFragment() {
@Synchronized
override fun onResume() {
super.onResume()
disposable.add(rxBus
disposable += rxBus
.toObservable(EventFoodDatabaseChanged::class.java)
.observeOn(aapsSchedulers.main)
.debounce(1L, TimeUnit.SECONDS)
.subscribe({ swapAdapter() }, fabricPrivacy::logException)
)
swapAdapter()
}
@ -178,13 +133,13 @@ class FoodFragment : DaggerFragment() {
val categories = ArrayList(catSet)
categories.add(0, rh.gs(R.string.none))
context?.let { context ->
val adapterCategories = ArrayAdapter(context, R.layout.spinner_centered, categories)
binding.category.adapter = adapterCategories
binding.categoryList.setAdapter(ArrayAdapter(context, R.layout.spinner_centered, categories))
binding.categoryList.setText(rh.gs(R.string.none), false)
}
}
private fun fillSubcategories() {
val categoryFilter = binding.category.selectedItem.toString()
val categoryFilter = binding.categoryList.text.toString()
val subCatSet: MutableSet<CharSequence> = HashSet()
if (categoryFilter != rh.gs(R.string.none)) {
for (f in unfiltered) {
@ -198,17 +153,15 @@ class FoodFragment : DaggerFragment() {
val subcategories = ArrayList(subCatSet)
subcategories.add(0, rh.gs(R.string.none))
context?.let { context ->
val adapterSubcategories = ArrayAdapter(context, R.layout.spinner_centered, subcategories)
binding.subcategory.adapter = adapterSubcategories
binding.subcategoryList.setAdapter(ArrayAdapter(context, R.layout.spinner_centered, subcategories))
binding.subcategoryList.setText(rh.gs(R.string.none), false)
}
}
private fun filterData() {
val textFilter = binding.filter.text.toString()
val categoryFilter = binding.category.selectedItem?.toString()
?: rh.gs(R.string.none)
val subcategoryFilter = binding.subcategory.selectedItem?.toString()
?: rh.gs(R.string.none)
val categoryFilter = binding.categoryList.text.toString()
val subcategoryFilter = binding.subcategoryList.text.toString()
val newFiltered = ArrayList<Food>()
for (f in unfiltered) {
if (f.category == null || f.subCategory == null) continue
@ -243,7 +196,6 @@ class FoodFragment : DaggerFragment() {
holder.binding.energy.text = rh.gs(R.string.shortenergy) + ": " + food.energy + rh.gs(R.string.shortkilojoul)
holder.binding.energy.visibility = food.energy.isNotZero().toVisibility()
holder.binding.icRemove.tag = food
holder.binding.foodItem.tag = food
holder.binding.icCalculator.tag = food
}
@ -267,18 +219,17 @@ class FoodFragment : DaggerFragment() {
}, null)
}
}
binding.icCalculator.setOnClickListener { v:View ->
binding.icCalculator.setOnClickListener { v: View ->
val food = v.tag as Food
activity?.let { activity ->
protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, UIRunnable {
if (isAdded) {
val wizardDialog = WizardDialog()
val bundle = Bundle()
bundle.putInt("carbs_input", food.carbs)
bundle.putString("notes_input", " ${food.name} - ${food.carbs}g")
wizardDialog.setArguments(bundle)
wizardDialog.show(childFragmentManager, "Food Item")
}
if (isAdded)
WizardDialog().also { dialog ->
dialog.arguments = Bundle().also { bundle ->
bundle.putDouble("carbs_input", food.carbs.toDouble())
bundle.putString("notes_input", " ${food.name} - ${food.carbs}g")
}
}.show(childFragmentManager, "Food Item")
})
}
}

View file

@ -17,7 +17,7 @@ import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.receivers.DataWorker
import info.nightscout.androidaps.utils.JsonHelper
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONObject
import javax.inject.Inject

Some files were not shown because too many files have changed in this diff Show more