Merge branch 'dev' into eopatch2
This commit is contained in:
commit
763f54ec6f
|
@ -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>
|
||||
|
|
|
@ -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,6 +107,7 @@
|
|||
<w>timeshift</w>
|
||||
<w>tirs</w>
|
||||
<w>totp</w>
|
||||
<w>tunedays</w>
|
||||
<w>uart</w>
|
||||
<w>wizzardpage</w>
|
||||
<w>xdrip</w>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
||||
/*
|
||||
|
|
|
@ -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,38 +81,54 @@
|
|||
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
|
||||
|
@ -94,17 +137,19 @@
|
|||
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>
|
||||
|
||||
|
@ -115,13 +160,10 @@
|
|||
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>
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
1321
app/src/main/assets/OpenAPSSMBDynamicISF/determine-basal.js
Normal file
1321
app/src/main/assets/OpenAPSSMBDynamicISF/determine-basal.js
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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,11 +177,14 @@ 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() {
|
||||
val keepScreenOn = sp.getBoolean(R.string.key_keep_screen_on, false)
|
||||
|
@ -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])
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.setOnClickListener {
|
||||
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")
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
.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()
|
||||
}
|
||||
|
||||
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])
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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,10 +125,12 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
|
|||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
context?.let { context ->
|
||||
PreferenceManager
|
||||
.getDefaultSharedPreferences(context)
|
||||
.unregisterOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addPreferencesFromResourceIfEnabled(p: PluginBase?, rootKey: String?, enabled: Boolean) {
|
||||
if (enabled) addPreferencesFromResourceIfEnabled(p, rootKey)
|
||||
|
@ -141,10 +143,12 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
|
|||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
context?.let { context ->
|
||||
PreferenceManager
|
||||
.getDefaultSharedPreferences(context)
|
||||
.registerOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
(savedInstanceState ?: arguments)?.let { bundle ->
|
||||
|
@ -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,21 +273,21 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
|
|||
|
||||
@SuppressLint("RestrictedApi")
|
||||
private fun addPreferencesFromResource(@XmlRes preferencesResId: Int, key: String?) {
|
||||
val xmlRoot = preferenceManager.inflateFromResource(context,
|
||||
preferencesResId, null)
|
||||
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")
|
||||
("Preference object with key $key is not a PreferenceScreen")
|
||||
}
|
||||
preferenceScreen = root
|
||||
} else {
|
||||
addPreferencesFromResource(preferencesResId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun adjustUnitDependentPrefs(pref: Preference) { // convert preferences values to current units
|
||||
val unitDependent = arrayOf(
|
||||
|
@ -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,10 +383,14 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
|
|||
if (sp.getString(pref.key, "").startsWith("hmac:")) {
|
||||
pref.summary = "******"
|
||||
} else {
|
||||
if (pref.key.contains("pin")) {
|
||||
pref.summary = rh.gs(R.string.pin_not_set)
|
||||
}else {
|
||||
pref.summary = rh.gs(R.string.password_not_set)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pref?.let { adjustUnitDependentPrefs(it) }
|
||||
}
|
||||
|
||||
|
@ -396,9 +413,8 @@ 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)
|
||||
|
@ -417,13 +433,24 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
|
|||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() }
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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() {
|
||||
|
@ -24,39 +26,44 @@ class TreatmentsActivity : NoSplashAppCompatActivity() {
|
|||
binding = TreatmentsFragmentBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
//binding.tempBasals.visibility = buildHelper.isEngineeringMode().toVisibility()
|
||||
//binding.extendedBoluses.visibility = (buildHelper.isEngineeringMode() && !activePlugin.activePump.isFakingTempsByExtendedBoluses).toVisibility()
|
||||
// Use index, TabItems crashes with an id
|
||||
val useFakeTempBasal = activePlugin.activePump.isFakingTempsByExtendedBoluses
|
||||
binding.treatmentsTabs.getTabAt(1)?.view?.visibility = useFakeTempBasal.toVisibility()
|
||||
|
||||
binding.treatments.setOnClickListener {
|
||||
setFragment(TreatmentsBolusCarbsFragment())
|
||||
setBackgroundColorOnSelected(it)
|
||||
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
|
||||
}
|
||||
binding.extendedBoluses.setOnClickListener {
|
||||
setFragment(TreatmentsExtendedBolusesFragment())
|
||||
setBackgroundColorOnSelected(it)
|
||||
setFragment(fragment.newInstance())
|
||||
supportActionBar?.title = tab.contentDescription
|
||||
}
|
||||
binding.tempBasals.setOnClickListener {
|
||||
setFragment(TreatmentsTemporaryBasalsFragment())
|
||||
setBackgroundColorOnSelected(it)
|
||||
|
||||
override fun onTabUnselected(tab: TabLayout.Tab) {}
|
||||
override fun onTabReselected(tab: TabLayout.Tab) {}
|
||||
})
|
||||
}
|
||||
binding.tempTargets.setOnClickListener {
|
||||
setFragment(TreatmentsTempTargetFragment())
|
||||
setBackgroundColorOnSelected(it)
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
android.R.id.home -> {
|
||||
finish()
|
||||
true
|
||||
}
|
||||
binding.profileSwitches.setOnClickListener {
|
||||
setFragment(TreatmentsProfileSwitchFragment())
|
||||
setBackgroundColorOnSelected(it)
|
||||
|
||||
else -> false
|
||||
}
|
||||
binding.careportal.setOnClickListener {
|
||||
setFragment(TreatmentsCareportalFragment())
|
||||
setBackgroundColorOnSelected(it)
|
||||
}
|
||||
binding.userentry.setOnClickListener {
|
||||
setFragment(TreatmentsUserEntryFragment())
|
||||
setBackgroundColorOnSelected(it)
|
||||
}
|
||||
setFragment(TreatmentsBolusCarbsFragment())
|
||||
setBackgroundColorOnSelected(binding.treatments)
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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,9 +126,10 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() {
|
|||
|
||||
fun swapAdapter() {
|
||||
val now = System.currentTimeMillis()
|
||||
|
||||
if (binding.showInvalidated.isChecked)
|
||||
disposable += carbsMealLinksWithInvalid(now)
|
||||
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 ->
|
||||
|
@ -217,10 +141,9 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() {
|
|||
.observeOn(aapsSchedulers.main)
|
||||
.subscribe { list ->
|
||||
binding.recyclerview.swapAdapter(RecyclerViewAdapter(list), true)
|
||||
binding.deleteFutureTreatments.visibility = list.isNotEmpty().toVisibility()
|
||||
}
|
||||
else
|
||||
disposable += carbsMealLinks(now)
|
||||
carbsMealLinks(now)
|
||||
.zipWith(bolusMealLinks(now)) { first, second -> first + second }
|
||||
.zipWith(calcResultMealLinks(now)) { first, second -> first + second }
|
||||
.map { ml ->
|
||||
|
@ -232,7 +155,6 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() {
|
|||
.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,35 +287,175 @@ 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 ->
|
||||
val text = rh.gs(R.string.configbuilder_insulin) + ": " +
|
||||
rh.gs(R.string.formatinsulinunits, bolus.amount) + "\n" +
|
||||
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) }
|
||||
)
|
||||
}
|
||||
}
|
||||
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 bolusCalculatorResult $it") } },
|
||||
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating bolusCalculatorResult", it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), text, Runnable {
|
||||
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)
|
||||
//ValueWithUnit.Gram(mealLinkLoaded.carbs.toInt())
|
||||
)
|
||||
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 {
|
||||
ml.carbs?.let { carb ->
|
||||
uel.log(
|
||||
Action.CARBS_REMOVED, Sources.Treatments,
|
||||
ValueWithUnit.Timestamp(carb.timestamp),
|
||||
|
@ -398,11 +466,11 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() {
|
|||
{ result -> result.invalidated.forEach { aapsLogger.debug(LTag.DATABASE, "Invalidated carbs $it") } },
|
||||
{ aapsLogger.error(LTag.DATABASE, "Error while invalidating carbs", it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
actionHelper.finish()
|
||||
})
|
||||
}
|
||||
}
|
||||
binding.carbsRemove.paintFlags = binding.carbsRemove.paintFlags or Paint.UNDERLINE_TEXT_FLAG
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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,24 +52,32 @@ 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 {
|
||||
binding.recyclerview.emptyView = binding.noRecordsText
|
||||
binding.recyclerview.loadingView = binding.progressBar
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -88,30 +91,25 @@ class TreatmentsCareportalFragment : DaggerFragment() {
|
|||
})
|
||||
}
|
||||
}
|
||||
binding.removeAndroidapsStartedEvents.setOnClickListener {
|
||||
|
||||
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)
|
||||
repository.runTransactionForResult(InvalidateAAPSStartedTherapyEventTransaction(rh.gs(R.string.androidaps_start)))
|
||||
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) }
|
||||
)
|
||||
}, null)
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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" +
|
||||
}
|
||||
|
||||
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)
|
||||
OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), text, Runnable {
|
||||
uel.log(Action.CAREPORTAL_REMOVED, Sources.Treatments, therapyEvent.note ,
|
||||
}
|
||||
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))
|
||||
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
|
||||
actionHelper.finish()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
|
||||
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()))
|
||||
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
|
||||
actionHelper.finish()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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,23 +64,31 @@ 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 {
|
||||
private fun refreshFromNightscout() {
|
||||
activity?.let { activity ->
|
||||
OKDialog.showConfirmation(activity, rh.gs(R.string.refresheventsfromnightscout) + "?") {
|
||||
uel.log(Action.TREATMENTS_NS_REFRESH, Sources.Treatments)
|
||||
|
@ -101,11 +111,6 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() {
|
|||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
.getProfileSwitchDataIncludingInvalidFromTime(now - millsToThePast, false)
|
||||
|
@ -125,9 +130,10 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() {
|
|||
|
||||
fun swapAdapter() {
|
||||
val now = System.currentTimeMillis()
|
||||
|
||||
if (binding.showInvalidated.isChecked)
|
||||
disposable += profileSwitchWithInvalid(now)
|
||||
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)
|
||||
|
@ -135,14 +141,13 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() {
|
|||
binding.recyclerview.swapAdapter(RecyclerProfileViewAdapter(list), true)
|
||||
}
|
||||
else
|
||||
disposable += profileSwitches(now)
|
||||
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,
|
||||
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))
|
||||
ValueWithUnit.SimpleString(profileSwitch.profileName)
|
||||
)
|
||||
val nonCustomized = profileSealed.convertToNonCustomizedProfile(dateUtil)
|
||||
localProfilePlugin.addProfile(localProfilePlugin.copyFrom(nonCustomized, profileSwitch.getCustomizedName() + " " + dateUtil.dateAndTimeString(profileSwitch.timestamp).replace(".", "_")))
|
||||
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()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,48 +62,60 @@ 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())
|
||||
})
|
||||
}
|
||||
}
|
||||
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)
|
||||
binding.recyclerview.isLoading = true
|
||||
disposable +=
|
||||
if (showInvalidated)
|
||||
repository
|
||||
.getTemporaryTargetDataIncludingInvalidFromTime(now - millsToThePast, false)
|
||||
.observeOn(aapsSchedulers.main)
|
||||
|
@ -122,23 +131,17 @@ class TreatmentsTempTargetFragment : DaggerFragment() {
|
|||
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,
|
||||
}
|
||||
}
|
||||
|
||||
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()))
|
||||
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
|
||||
}
|
||||
}
|
||||
actionHelper.finish()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
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,
|
||||
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()))
|
||||
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,
|
||||
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()))
|
||||
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)
|
||||
}
|
||||
actionHelper.finish()
|
||||
})
|
||||
}
|
||||
binding.remove.paintFlags = binding.remove.paintFlags or Paint.UNDERLINE_TEXT_FLAG
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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,9 +60,14 @@ 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 {
|
||||
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)
|
||||
|
@ -73,41 +75,31 @@ class TreatmentsUserEntryFragment : DaggerFragment() {
|
|||
}
|
||||
}
|
||||
}
|
||||
binding.showLoop.setOnCheckedChangeListener { _, _ ->
|
||||
rxBus.send(EventTreatmentUpdateGui())
|
||||
}
|
||||
}
|
||||
|
||||
fun swapAdapter() {
|
||||
val now = System.currentTimeMillis()
|
||||
if (binding.showLoop.isChecked)
|
||||
disposable.add( repository
|
||||
binding.recyclerview.isLoading = true
|
||||
disposable +=
|
||||
if (showLoop)
|
||||
repository
|
||||
.getUserEntryDataFromTime(now - millsToThePastUnFiltered)
|
||||
.observeOn(aapsSchedulers.main)
|
||||
.subscribe { list -> binding.recyclerview.swapAdapter(UserEntryAdapter(list), true) }
|
||||
)
|
||||
else
|
||||
disposable.add( repository
|
||||
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
|
||||
}
|
||||
|
||||
}
|
|
@ -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())
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,12 +340,25 @@ class WizardDialog : DaggerDialogFragment() {
|
|||
return
|
||||
}
|
||||
|
||||
// IOB calculation
|
||||
val bolusIob = iobCobCalculator.calculateIobFromBolus().round()
|
||||
val basalIob = iobCobCalculator.calculateIobFromTempBasalsIncludingConvertedExtended().round()
|
||||
|
||||
runOnUiThread {
|
||||
_binding ?: return@runOnUiThread
|
||||
if (carbsPassedIntoWizard != 0.0) {
|
||||
binding.carbsInput.value = carbsPassedIntoWizard
|
||||
}
|
||||
if (notesPassedIntoWizard.isNotBlank()) {
|
||||
binding.notesLayout.notes.setText(notesPassedIntoWizard)
|
||||
}
|
||||
|
||||
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
|
||||
binding.profileList.setAdapter(ArrayAdapter(context, R.layout.spinner_centered, profileList))
|
||||
binding.profileList.setText(profileList[0], false)
|
||||
}
|
||||
|
||||
val units = profileFunction.getUnits()
|
||||
binding.bgUnits.text = units.asText
|
||||
|
@ -337,24 +366,19 @@ class WizardDialog : DaggerDialogFragment() {
|
|||
|
||||
// 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.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()
|
||||
|
||||
binding.percentUsed.visibility = (sp.getInt(R.string.key_boluswizard_percentage, 100) != 100 || correctionPercent).toVisibility()
|
||||
}
|
||||
}
|
||||
|
||||
@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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
package info.nightscout.androidaps.events
|
||||
|
||||
class EventBolusRequested(var amount: Double) : Event()
|
|
@ -1,3 +0,0 @@
|
|||
package info.nightscout.androidaps.events
|
||||
|
||||
class EventReloadProfileSwitchData : Event()
|
|
@ -0,0 +1,3 @@
|
|||
package info.nightscout.androidaps.events
|
||||
|
||||
class EventScale(val hours: Int) : Event()
|
|
@ -1,3 +0,0 @@
|
|||
package info.nightscout.androidaps.events
|
||||
|
||||
class EventTreatmentUpdateGui : EventUpdateGui()
|
|
@ -1,5 +0,0 @@
|
|||
package info.nightscout.androidaps.plugins.aps.events
|
||||
|
||||
import info.nightscout.androidaps.events.Event
|
||||
|
||||
class EventLoopInvoked : Event()
|
|
@ -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,20 +42,43 @@ 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() {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,7 +87,8 @@ class LoopPlugin @Inject constructor(
|
|||
private val uel: UserEntryLogger,
|
||||
private val repository: AppRepository,
|
||||
private val runningConfiguration: RunningConfiguration
|
||||
) : PluginBase(PluginDescription()
|
||||
) : PluginBase(
|
||||
PluginDescription()
|
||||
.mainType(PluginType.LOOP)
|
||||
.fragmentClass(LoopFragment::class.java.name)
|
||||
.pluginIcon(R.drawable.ic_loop_closed_white)
|
||||
|
@ -101,7 +101,7 @@ class LoopPlugin @Inject constructor(
|
|||
), 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),
|
||||
uel.log(
|
||||
Action.CAREPORTAL, Sources.Loop, rh.gs(R.string.carbsreq, resultAfterConstraints.carbsReq, resultAfterConstraints.carbsReqWithin),
|
||||
ValueWithUnit.Gram(resultAfterConstraints.carbsReq),
|
||||
ValueWithUnit.Minute(resultAfterConstraints.carbsReqWithin))
|
||||
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)
|
||||
callback?.result(
|
||||
PumpEnactResult(injector).percent(request.percent)
|
||||
.enacted(false).success(true).duration(activeTemp.plannedRemainingMinutes)
|
||||
.comment(R.string.let_temp_basal_run))?.run()
|
||||
.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))
|
||||
callback?.result(
|
||||
PumpEnactResult(injector).absolute(activeTemp.convertedToAbsolute(now, profile))
|
||||
.enacted(false).success(true).duration(activeTemp.plannedRemainingMinutes)
|
||||
.comment(R.string.let_temp_basal_run))?.run()
|
||||
.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)
|
||||
callback?.result(
|
||||
PumpEnactResult(injector)
|
||||
.comment(R.string.smb_frequency_exceeded)
|
||||
.enacted(false).success(false))?.run()
|
||||
.enacted(false).success(false)
|
||||
)?.run()
|
||||
return
|
||||
}
|
||||
if (!pump.isInitialized()) {
|
||||
|
|
|
@ -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,7 +139,9 @@ class DetermineBasalAdapterAMAJS internal constructor(scriptReader: ScriptReader
|
|||
}
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@Throws(JSONException::class) fun setData(profile: Profile,
|
||||
@Throws(JSONException::class)
|
||||
override fun setData(
|
||||
profile: Profile,
|
||||
maxIob: Double,
|
||||
maxBasal: Double,
|
||||
minBg: Double,
|
||||
|
@ -154,7 +152,12 @@ class DetermineBasalAdapterAMAJS internal constructor(scriptReader: ScriptReader
|
|||
glucoseStatus: GlucoseStatus,
|
||||
mealData: MealData,
|
||||
autosensDataRatio: Double,
|
||||
tempTargetSet: Boolean) {
|
||||
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))
|
||||
|
|
|
@ -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()
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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,7 +147,9 @@ class DetermineBasalAdapterSMBJS internal constructor(private val scriptReader:
|
|||
return determineBasalResultSMB
|
||||
}
|
||||
|
||||
@Suppress("SpellCheckingInspection") fun setData(profile: Profile,
|
||||
@Suppress("SpellCheckingInspection")
|
||||
override fun setData(
|
||||
profile: Profile,
|
||||
maxIob: Double,
|
||||
maxBasal: Double,
|
||||
minBg: Double,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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,19 +46,43 @@ 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() {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,7 +46,8 @@ class OpenAPSSMBPlugin @Inject constructor(
|
|||
private val dateUtil: DateUtil,
|
||||
private val repository: AppRepository,
|
||||
private val glucoseStatusProvider: GlucoseStatusProvider
|
||||
) : PluginBase(PluginDescription()
|
||||
) : PluginBase(
|
||||
PluginDescription()
|
||||
.mainType(PluginType.APS)
|
||||
.fragmentClass(OpenAPSSMBFragment::class.java.name)
|
||||
.pluginIcon(R.drawable.ic_generic_icon)
|
||||
|
@ -61,8 +62,8 @@ class OpenAPSSMBPlugin @Inject constructor(
|
|||
// 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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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,42 +47,30 @@ 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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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 -> ""
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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,10 +12,17 @@ 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) {
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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.*
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
extendedBolus?.setOnClickListener {
|
||||
binding.tempTarget.setOnClickListener {
|
||||
activity?.let { activity ->
|
||||
protectionCheck.queryProtection(
|
||||
activity,
|
||||
ProtectionCheck.Protection.BOLUS,
|
||||
UIRunnable { TempTargetDialog().show(childFragmentManager, "Actions") })
|
||||
}
|
||||
}
|
||||
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()
|
||||
|
||||
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)
|
||||
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()
|
||||
|
||||
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)
|
||||
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 = ""
|
||||
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()
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package info.nightscout.androidaps.plugins.general.autotune.events
|
||||
|
||||
import info.nightscout.androidaps.events.Event
|
||||
|
||||
class EventAutotuneUpdateGui : Event()
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
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")
|
||||
wizardDialog.setArguments(bundle)
|
||||
wizardDialog.show(childFragmentManager, "Food Item")
|
||||
}
|
||||
}.show(childFragmentManager, "Food Item")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue