Merge branch 'dev' into eopatch2

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

View file

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

View file

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

View file

@ -44,5 +44,5 @@ Hints
* Start small, it is easier to review smaller changes that affect fewer parts of code * 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 * 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 * 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 * Speak with other developers to minimise merge conflicts. Find out who worked, working or plan to work on speciffic issue or part of app

View file

@ -6,8 +6,8 @@ apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.hiya.jacoco-android' apply plugin: 'com.hiya.jacoco-android'
apply plugin: 'com.google.firebase.crashlytics' apply plugin: 'com.google.firebase.crashlytics'
apply from: "${project.rootDir}/gradle/android_dependencies.gradle" apply from: "${project.rootDir}/core/android_dependencies.gradle"
apply from: "${project.rootDir}/gradle/jacoco_global.gradle" apply from: "${project.rootDir}/core/jacoco_global.gradle"
repositories { repositories {
mavenCentral() mavenCentral()
@ -97,19 +97,15 @@ def allCommitted = { ->
return stringBuilder.toString().isEmpty() 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 { android {
namespace 'info.nightscout.androidaps'
ndkVersion "21.1.6352462" ndkVersion "21.1.6352462"
defaultConfig { defaultConfig {
multiDexEnabled true multiDexEnabled true
versionCode 1500 versionCode 1500
version "3.0.0.1-dev" version "3.1.0"
buildConfigField "String", "VERSION", '"' + version + '"' buildConfigField "String", "VERSION", '"' + version + '"'
buildConfigField "String", "BUILDVERSION", '"' + generateGitBuild() + '-' + generateDate() + '"' buildConfigField "String", "BUILDVERSION", '"' + generateGitBuild() + '-' + generateDate() + '"'
buildConfigField "String", "REMOTE", '"' + generateGitRemote() + '"' buildConfigField "String", "REMOTE", '"' + generateGitRemote() + '"'
@ -171,9 +167,6 @@ android {
allprojects { allprojects {
repositories { repositories {
flatDir {
dirs 'libs'
}
} }
} }
@ -212,7 +205,7 @@ dependencies {
kapt "com.google.dagger:dagger-compiler:$dagger_version" kapt "com.google.dagger:dagger-compiler:$dagger_version"
} }
apply from: "${project.rootDir}/gradle/test_dependencies.gradle" apply from: "${project.rootDir}/core/test_dependencies.gradle"
/* /*

View file

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

View file

@ -324,7 +324,7 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
rT.reason += ", but Min. Delta " + minDelta.toFixed(2) + " > Exp. Delta " + expectedDelta; rT.reason += ", but Min. Delta " + minDelta.toFixed(2) + " > Exp. Delta " + expectedDelta;
} }
if (currenttemp.duration > 15 && (round_basal(basal, profile) === round_basal(currenttemp.rate, profile))) { 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; return rT;
} else { } else {
rT.reason += "; setting current basal of " + round(basal, 2) + " as temp"; 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); return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp);
} }
if (typeof currenttemp.rate !== 'undefined' && (currenttemp.duration > 5 && rate >= currenttemp.rate * 0.8)) { 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; return rT;
} else { } else {
rT.reason += ", setting " + rate + "U/hr"; rT.reason += ", setting " + round(rate, 2) + "U/hr";
return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp); 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; 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 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); return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp);
} }
if (typeof currenttemp.duration == 'undefined' || currenttemp.duration == 0) { // no temp is set 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); 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 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; return rT;
} }
// required temp > existing temp basal // 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); return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp);
} }

View file

@ -287,7 +287,7 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
} }
//console.log(" (autosens ratio "+sensitivityRatio+")"); //console.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 // compare currenttemp to iob_data.lastTemp and cancel temp if they don't match
var lastTempAge; 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"); //console.error("currenttemp:",currenttemp,"lastTemp:",JSON.stringify(iob_data.lastTemp),"lastTempAge:",lastTempAge,"m");
var tempModulus = (lastTempAge + currenttemp.duration) % 30; 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.temp = 'absolute';
rT.deliverAt = deliverAt; rT.deliverAt = deliverAt;
if ( microBolusAllowed && currenttemp && iob_data.lastTemp && currenttemp.rate !== iob_data.lastTemp.rate && lastTempAge > 10 && currenttemp.duration ) { 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); return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp);
} }
if (typeof currenttemp.rate !== 'undefined' && (currenttemp.duration > 5 && rate >= currenttemp.rate * 0.8)) { 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; return rT;
} else { } else {
// calculate a long enough zero temp to eventually correct back up to target // 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); return tempBasalFunctions.setTempBasal(rate, durationReq, profile, rT, currenttemp);
} }
} else { } else {
rT.reason += ", setting " + rate + "U/hr. "; rT.reason += ", setting " + round(rate, 2) + "U/hr. ";
} }
return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp); 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; 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 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); return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp);
} }
if (typeof currenttemp.duration === 'undefined' || currenttemp.duration === 0) { // no temp is set 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); 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 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; return rT;
} }
// required temp > existing temp basal // 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); return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp);
} }

File diff suppressed because it is too large Load diff

View file

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

View file

@ -6,7 +6,13 @@ import android.content.IntentFilter
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.net.wifi.WifiManager import android.net.wifi.WifiManager
import android.os.Build 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.AndroidInjector
import dagger.android.DaggerApplication import dagger.android.DaggerApplication
import info.nightscout.androidaps.database.AppRepository 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.Config
import info.nightscout.androidaps.interfaces.ConfigBuilder import info.nightscout.androidaps.interfaces.ConfigBuilder
import info.nightscout.androidaps.interfaces.PluginBase import info.nightscout.androidaps.interfaces.PluginBase
import info.nightscout.shared.logging.AAPSLogger import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.configBuilder.PluginStore import info.nightscout.androidaps.plugins.configBuilder.PluginStore
import info.nightscout.androidaps.plugins.constraints.versionChecker.VersionCheckerUtils 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.Notification
import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationStore 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.BTReceiver
import info.nightscout.androidaps.receivers.ChargingStateReceiver 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.NetworkChangeReceiver
import info.nightscout.androidaps.receivers.TimeDateOrTZChangeReceiver import info.nightscout.androidaps.receivers.TimeDateOrTZChangeReceiver
import info.nightscout.androidaps.services.AlarmSoundServiceHelper import info.nightscout.androidaps.services.AlarmSoundServiceHelper
import info.nightscout.androidaps.utils.ActivityMonitor import info.nightscout.androidaps.utils.ActivityMonitor
import info.nightscout.androidaps.utils.DateUtil 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.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 info.nightscout.shared.sharedPreferences.SP
import io.reactivex.disposables.CompositeDisposable import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.exceptions.UndeliverableException import io.reactivex.rxjava3.exceptions.UndeliverableException
import io.reactivex.plugins.RxJavaPlugins import io.reactivex.rxjava3.kotlin.plusAssign
import io.reactivex.rxkotlin.plusAssign import io.reactivex.rxjava3.plugins.RxJavaPlugins
import net.danlew.android.joda.JodaTimeAndroid import rxdogtag2.RxDogTag
import java.io.IOException import java.io.IOException
import java.net.SocketException import java.net.SocketException
import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Provider
class MainApp : DaggerApplication() { class MainApp : DaggerApplication() {
@ -60,16 +72,21 @@ class MainApp : DaggerApplication() {
@Inject lateinit var config: Config @Inject lateinit var config: Config
@Inject lateinit var buildHelper: BuildHelper @Inject lateinit var buildHelper: BuildHelper
@Inject lateinit var configBuilder: ConfigBuilder @Inject lateinit var configBuilder: ConfigBuilder
@Inject lateinit var keepAliveManager: KeepAliveManager
@Inject lateinit var plugins: List<@JvmSuppressWildcards PluginBase> @Inject lateinit var plugins: List<@JvmSuppressWildcards PluginBase>
@Inject lateinit var compatDBHelper: CompatDBHelper @Inject lateinit var compatDBHelper: CompatDBHelper
@Inject lateinit var repository: AppRepository @Inject lateinit var repository: AppRepository
@Inject lateinit var dateUtil: DateUtil @Inject lateinit var dateUtil: DateUtil
@Inject lateinit var staticInjector: StaticInjector// TODO avoid , here fake only to initialize @Inject lateinit var staticInjector: StaticInjector// TODO avoid , here fake only to initialize
@Inject lateinit var uel: UserEntryLogger @Inject lateinit var uel: UserEntryLogger
@Inject lateinit var passwordCheck: PasswordCheck
@Inject lateinit var alarmSoundServiceHelper: AlarmSoundServiceHelper @Inject lateinit var alarmSoundServiceHelper: AlarmSoundServiceHelper
@Inject lateinit var notificationStore: NotificationStore @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() { override fun onCreate() {
super.onCreate() super.onCreate()
@ -77,6 +94,7 @@ class MainApp : DaggerApplication() {
RxDogTag.install() RxDogTag.install()
setRxErrorHandler() setRxErrorHandler()
LocaleHelper.update(this) LocaleHelper.update(this)
ProcessLifecycleOwner.get().lifecycle.addObserver(processLifecycleListener)
var gitRemote: String? = BuildConfig.REMOTE var gitRemote: String? = BuildConfig.REMOTE
var commitHash: String? = BuildConfig.HEAD var commitHash: String? = BuildConfig.HEAD
@ -84,21 +102,9 @@ class MainApp : DaggerApplication() {
gitRemote = null gitRemote = null
commitHash = 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() disposable += compatDBHelper.dbChangeDisposable()
registerActivityLifecycleCallbacks(activityMonitor) registerActivityLifecycleCallbacks(activityMonitor)
JodaTimeAndroid.init(this) profileSwitchPlugin.setThemeMode()
aapsLogger.debug("Version: " + BuildConfig.VERSION_NAME) aapsLogger.debug("Version: " + BuildConfig.VERSION_NAME)
aapsLogger.debug("BuildVersion: " + BuildConfig.BUILDVERSION) aapsLogger.debug("BuildVersion: " + BuildConfig.BUILDVERSION)
aapsLogger.debug("Remote: " + BuildConfig.REMOTE) 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 // trigger here to see the new version on app start after an update
versionCheckersUtils.triggerCheckVersion() 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 // Register all tabs in app here
pluginStore.plugins = plugins pluginStore.plugins = plugins
configBuilder.initialize() 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() doMigrations()
uel.log(UserEntry.Action.START_AAPS, UserEntry.Sources.Aaps) 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() { private fun setRxErrorHandler() {
@ -147,11 +188,33 @@ class MainApp : DaggerApplication() {
} }
} }
@Suppress("SpellCheckingInspection")
private fun doMigrations() { private fun doMigrations() {
// set values for different builds // 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_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_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") 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> { override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
@ -186,8 +249,7 @@ class MainApp : DaggerApplication() {
override fun onTerminate() { override fun onTerminate() {
aapsLogger.debug(LTag.CORE, "onTerminate") aapsLogger.debug(LTag.CORE, "onTerminate")
unregisterActivityLifecycleCallbacks(activityMonitor) unregisterActivityLifecycleCallbacks(activityMonitor)
keepAliveManager.cancelAlarm(this) alarmSoundServiceHelper.stopService(this, "onTerminate")
alarmSoundServiceHelper.stopService(this)
super.onTerminate() super.onTerminate()
} }
} }

View file

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

View file

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

View file

@ -2,8 +2,9 @@ package info.nightscout.androidaps.activities
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.text.Editable import android.view.Menu
import android.text.TextWatcher import android.view.MenuItem
import androidx.appcompat.widget.SearchView
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
@ -14,23 +15,16 @@ class PreferencesActivity : NoSplashAppCompatActivity(), PreferenceFragmentCompa
private var preferenceId = 0 private var preferenceId = 0
private var myPreferenceFragment: MyPreferenceFragment? = null private var myPreferenceFragment: MyPreferenceFragment? = null
private var searchView: SearchView? = null
private lateinit var binding: ActivityPreferencesBinding private lateinit var binding: ActivityPreferencesBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setTheme(R.style.AppTheme)
binding = ActivityPreferencesBinding.inflate(layoutInflater) binding = ActivityPreferencesBinding.inflate(layoutInflater)
setContentView(binding.root) 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) title = rh.gs(R.string.nav_preferences)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true) supportActionBar?.setDisplayShowHomeEnabled(true)
@ -38,12 +32,29 @@ class PreferencesActivity : NoSplashAppCompatActivity(), PreferenceFragmentCompa
preferenceId = intent.getIntExtra("id", -1) preferenceId = intent.getIntExtra("id", -1)
myPreferenceFragment?.arguments = Bundle().also { myPreferenceFragment?.arguments = Bundle().also {
it.putInt("id", preferenceId) it.putInt("id", preferenceId)
it.putString("filter", binding.prefFilter.text.toString())
} }
if (savedInstanceState == null) if (savedInstanceState == null)
supportFragmentManager.beginTransaction().replace(R.id.frame_layout, myPreferenceFragment!!).commit() 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 { override fun onPreferenceStartScreen(caller: PreferenceFragmentCompat, pref: PreferenceScreen): Boolean {
val fragment = MyPreferenceFragment() val fragment = MyPreferenceFragment()
fragment.arguments = Bundle().also { fragment.arguments = Bundle().also {
@ -58,7 +69,13 @@ class PreferencesActivity : NoSplashAppCompatActivity(), PreferenceFragmentCompa
super.attachBaseContext(LocaleHelper.wrap(newBase)) super.attachBaseContext(LocaleHelper.wrap(newBase))
} }
private fun filterPreferences() { override fun onOptionsItemSelected(item: MenuItem): Boolean {
myPreferenceFragment?.setFilter(binding.prefFilter.text.toString()) when (item.itemId) {
android.R.id.home -> {
onBackPressed()
return true
}
}
return super.onOptionsItemSelected(item)
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -19,7 +19,7 @@ import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProv
import info.nightscout.androidaps.utils.HtmlHelper import info.nightscout.androidaps.utils.HtmlHelper
import info.nightscout.androidaps.utils.XDripBroadcast import info.nightscout.androidaps.utils.XDripBroadcast
import info.nightscout.androidaps.utils.alertDialogs.OKDialog 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.text.DecimalFormat
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
@ -64,6 +64,7 @@ class CalibrationDialog : DialogFragmentWithDate() {
binding.bg.setParams(savedInstanceState?.getDouble("bg") binding.bg.setParams(savedInstanceState?.getDouble("bg")
?: bg, 36.0, 500.0, 1.0, DecimalFormat("0"), false, binding.okcancel.ok) ?: 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.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() { override fun onDestroyView() {

View file

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

View file

@ -30,9 +30,9 @@ import info.nightscout.androidaps.utils.Translator
import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.extensions.fromConstant import info.nightscout.androidaps.extensions.fromConstant
import info.nightscout.androidaps.interfaces.GlucoseUnit import info.nightscout.androidaps.interfaces.GlucoseUnit
import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.interfaces.ResourceHelper
import io.reactivex.disposables.CompositeDisposable import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign import io.reactivex.rxjava3.kotlin.plusAssign
import java.text.DecimalFormat import java.text.DecimalFormat
import java.util.* import java.util.*
import javax.inject.Inject 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) ?: 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) if (options == EventType.NOTE || options == EventType.QUESTION || options == EventType.ANNOUNCEMENT || options == EventType.EXERCISE)
binding.notesLayout.root.visibility = View.VISIBLE // independent to preferences binding.notesLayout.root.visibility = View.VISIBLE // independent to preferences
binding.bgLabel.labelFor = binding.bg.editTextId
binding.durationLabel.labelFor = binding.duration.editTextId
} }
override fun onDestroyView() { override fun onDestroyView() {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -30,9 +30,11 @@ import info.nightscout.shared.SafeParse
import info.nightscout.androidaps.utils.ToastUtils import info.nightscout.androidaps.utils.ToastUtils
import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.extensions.formatColor import info.nightscout.androidaps.extensions.formatColor
import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.protection.ProtectionCheck
import io.reactivex.disposables.CompositeDisposable import info.nightscout.androidaps.utils.protection.ProtectionCheck.Protection.BOLUS
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.text.DecimalFormat
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
@ -48,8 +50,14 @@ class TreatmentDialog : DialogFragmentWithDate() {
@Inject lateinit var config: Config @Inject lateinit var config: Config
@Inject lateinit var uel: UserEntryLogger @Inject lateinit var uel: UserEntryLogger
@Inject lateinit var repository: AppRepository @Inject lateinit var repository: AppRepository
@Inject lateinit var protectionCheck: ProtectionCheck
private var queryingProtection = false
private val disposable = CompositeDisposable() 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 { private val textWatcher: TextWatcher = object : TextWatcher {
override fun afterTextChanged(s: Editable) {} 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) { override fun onSaveInstanceState(savedInstanceState: Bundle) {
super.onSaveInstanceState(savedInstanceState) super.onSaveInstanceState(savedInstanceState)
savedInstanceState.putDouble("carbs", binding.carbs.value) savedInstanceState.putDouble("carbs", binding.carbs.value)
@ -106,6 +108,8 @@ class TreatmentDialog : DialogFragmentWithDate() {
binding.insulin.setParams(savedInstanceState?.getDouble("insulin") binding.insulin.setParams(savedInstanceState?.getDouble("insulin")
?: 0.0, 0.0, maxInsulin, pumpDescription.bolusStep, DecimalFormatter.pumpSupportedBolusFormat(activePlugin.activePump), false, binding.okcancel.ok, textWatcher) ?: 0.0, 0.0, maxInsulin, pumpDescription.bolusStep, DecimalFormatter.pumpSupportedBolusFormat(activePlugin.activePump), false, binding.okcancel.ok, textWatcher)
binding.recordOnlyLayout.visibility = View.GONE binding.recordOnlyLayout.visibility = View.GONE
binding.insulinLabel.labelFor = binding.insulin.editTextId
binding.carbsLabel.labelFor = binding.carbs.editTextId
} }
override fun onDestroyView() { override fun onDestroyView() {
@ -124,16 +128,16 @@ class TreatmentDialog : DialogFragmentWithDate() {
val carbsAfterConstraints = constraintChecker.applyCarbsConstraints(Constraint(carbs)).value() val carbsAfterConstraints = constraintChecker.applyCarbsConstraints(Constraint(carbs)).value()
if (insulinAfterConstraints > 0) { 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) 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)) 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) { 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) 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) { if (insulinAfterConstraints > 0 || carbsAfterConstraints > 0) {
activity?.let { activity -> activity?.let { activity ->
@ -199,4 +203,20 @@ class TreatmentDialog : DialogFragmentWithDate() {
} }
return true return true
} }
override fun onResume() {
super.onResume()
if(!queryingProtection) {
queryingProtection = true
activity?.let { activity ->
val cancelFail = {
queryingProtection = false
aapsLogger.debug(LTag.APS, "Dialog canceled on resume protection: ${this.javaClass.name}")
ToastUtils.showToastInUiThread(ctx, R.string.dialog_canceled)
dismiss()
}
protectionCheck.queryProtection(activity, BOLUS, { queryingProtection = false }, cancelFail, cancelFail)
}
}
}
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,24 +2,22 @@ package info.nightscout.androidaps.plugins.aps.openAPSAMA
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils import android.text.TextUtils
import android.view.LayoutInflater import android.view.*
import android.view.View
import android.view.ViewGroup
import dagger.android.support.DaggerFragment import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.databinding.OpenapsamaFragmentBinding 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.EventOpenAPSUpdateGui
import info.nightscout.androidaps.plugins.aps.events.EventOpenAPSUpdateResultGui import info.nightscout.androidaps.plugins.aps.events.EventOpenAPSUpdateResultGui
import info.nightscout.androidaps.plugins.bus.RxBus import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.JSONFormatter 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 info.nightscout.androidaps.utils.rx.AapsSchedulers
import io.reactivex.disposables.CompositeDisposable import info.nightscout.shared.logging.AAPSLogger
import io.reactivex.rxkotlin.plusAssign 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.JSONArray
import org.json.JSONException import org.json.JSONException
import javax.inject.Inject import javax.inject.Inject
@ -37,26 +35,53 @@ class OpenAPSAMAFragment : DaggerFragment() {
@Inject lateinit var dateUtil: DateUtil @Inject lateinit var dateUtil: DateUtil
@Inject lateinit var jsonFormatter: JSONFormatter @Inject lateinit var jsonFormatter: JSONFormatter
private val ID_MENU_RUN = 1
private var _binding: OpenapsamaFragmentBinding? = null private var _binding: OpenapsamaFragmentBinding? = null
// This property is only valid between onCreateView and // This property is only valid between onCreateView and
// onDestroyView. // onDestroyView.
private val binding get() = _binding!! private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
savedInstanceState: Bundle?): View { OpenapsamaFragmentBinding.inflate(inflater, container, false).also {
_binding = OpenapsamaFragmentBinding.inflate(inflater, container, false) _binding = it
return binding.root setHasOptionsMenu(true)
} }.root
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding.run.setOnClickListener { with(binding.swipeRefresh) {
openAPSAMAPlugin.invoke("OpenAPSAMA button", false) 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 @Synchronized
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
@ -65,14 +90,14 @@ class OpenAPSAMAFragment : DaggerFragment() {
.toObservable(EventOpenAPSUpdateGui::class.java) .toObservable(EventOpenAPSUpdateGui::class.java)
.observeOn(aapsSchedulers.main) .observeOn(aapsSchedulers.main)
.subscribe({ .subscribe({
updateGUI() updateGUI()
}, fabricPrivacy::logException) }, fabricPrivacy::logException)
disposable += rxBus disposable += rxBus
.toObservable(EventOpenAPSUpdateResultGui::class.java) .toObservable(EventOpenAPSUpdateResultGui::class.java)
.observeOn(aapsSchedulers.main) .observeOn(aapsSchedulers.main)
.subscribe({ .subscribe({
updateResultGUI(it.text) updateResultGUI(it.text)
}, fabricPrivacy::logException) }, fabricPrivacy::logException)
updateGUI() updateGUI()
} }
@ -96,7 +121,7 @@ class OpenAPSAMAFragment : DaggerFragment() {
binding.result.text = jsonFormatter.format(lastAPSResult.json) binding.result.text = jsonFormatter.format(lastAPSResult.json)
binding.request.text = lastAPSResult.toSpanned() binding.request.text = lastAPSResult.toSpanned()
} }
openAPSAMAPlugin.lastDetermineBasalAdapterAMAJS?.let { determineBasalAdapterAMAJS -> openAPSAMAPlugin.lastDetermineBasalAdapter?.let { determineBasalAdapterAMAJS ->
binding.glucosestatus.text = jsonFormatter.format(determineBasalAdapterAMAJS.glucoseStatusParam) binding.glucosestatus.text = jsonFormatter.format(determineBasalAdapterAMAJS.glucoseStatusParam)
binding.currenttemp.text = jsonFormatter.format(determineBasalAdapterAMAJS.currentTempParam) binding.currenttemp.text = jsonFormatter.format(determineBasalAdapterAMAJS.currentTempParam)
try { try {
@ -118,6 +143,7 @@ class OpenAPSAMAFragment : DaggerFragment() {
openAPSAMAPlugin.lastAutosensResult.let { openAPSAMAPlugin.lastAutosensResult.let {
binding.autosensdata.text = jsonFormatter.format(it.json()) binding.autosensdata.text = jsonFormatter.format(it.json())
} }
binding.swipeRefresh.isRefreshing = false
} }
private fun updateResultGUI(text: String) { private fun updateResultGUI(text: String) {
@ -131,5 +157,6 @@ class OpenAPSAMAFragment : DaggerFragment() {
binding.scriptdebugdata.text = "" binding.scriptdebugdata.text = ""
binding.request.text = "" binding.request.text = ""
binding.lastrun.text = "" binding.lastrun.text = ""
binding.swipeRefresh.isRefreshing = false
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -16,7 +16,7 @@ import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBus import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.configBuilder.events.EventConfigBuilderUpdateGui import info.nightscout.androidaps.plugins.configBuilder.events.EventConfigBuilderUpdateGui
import info.nightscout.androidaps.utils.alertDialogs.OKDialog 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 info.nightscout.shared.sharedPreferences.SP
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject

View file

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

View file

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

View file

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

View file

@ -10,7 +10,7 @@ import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotifi
import info.nightscout.androidaps.plugins.general.overview.notifications.Notification import info.nightscout.androidaps.plugins.general.overview.notifications.Notification
import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationWithAction import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationWithAction
import info.nightscout.androidaps.utils.T 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 info.nightscout.shared.sharedPreferences.SP
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject

View file

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

View file

@ -14,7 +14,7 @@ import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.constraints.objectives.objectives.* import info.nightscout.androidaps.plugins.constraints.objectives.objectives.*
import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.alertDialogs.OKDialog 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 info.nightscout.shared.sharedPreferences.SP
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject

View file

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

View file

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

View file

@ -126,6 +126,7 @@ class Objective2(injector: HasAndroidInjector) : Objective(injector, "exam", R.s
.option(Option(R.string.sensitivity_cannula, true)) .option(Option(R.string.sensitivity_cannula, true))
.option(Option(R.string.sensitivity_time, true)) .option(Option(R.string.sensitivity_time, true))
.hint(Hint(R.string.sensitivity_hint1)) .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") tasks.add(ExamTask(this, R.string.objectives_label, R.string.objectives_howtosave, "objectives")
.option(Option(R.string.objectives_notesettings, false)) .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_negiob, true))
.option(Option(R.string.iob_posiob, 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") tasks.add(ExamTask(this, R.string.breadgrams_label, R.string.blank, "breadgrams")
.option(Option(R.string.breadgrams_grams, true)) .option(Option(R.string.breadgrams_grams, true))
.option(Option(R.string.breadgrams_exchange, false)) .option(Option(R.string.breadgrams_exchange, false))

View file

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

View file

@ -10,7 +10,7 @@ import info.nightscout.androidaps.interfaces.PluginBase
import info.nightscout.androidaps.interfaces.PluginDescription import info.nightscout.androidaps.interfaces.PluginDescription
import info.nightscout.androidaps.interfaces.PluginType import info.nightscout.androidaps.interfaces.PluginType
import info.nightscout.shared.logging.AAPSLogger 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.Inject
import javax.inject.Singleton import javax.inject.Singleton

View file

@ -9,6 +9,7 @@ import info.nightscout.androidaps.interfaces.*
import info.nightscout.shared.logging.AAPSLogger import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.androidaps.plugins.aps.openAPSAMA.OpenAPSAMAPlugin import info.nightscout.androidaps.plugins.aps.openAPSAMA.OpenAPSAMAPlugin
import info.nightscout.androidaps.plugins.aps.openAPSSMB.OpenAPSSMBPlugin 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.bus.RxBus
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification 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.DecimalFormatter
import info.nightscout.androidaps.utils.HardLimits import info.nightscout.androidaps.utils.HardLimits
import info.nightscout.androidaps.utils.Round import info.nightscout.androidaps.utils.Round
import info.nightscout.androidaps.utils.buildHelper.BuildHelper import info.nightscout.androidaps.interfaces.BuildHelper
import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP import info.nightscout.shared.sharedPreferences.SP
import org.json.JSONObject import org.json.JSONObject
import javax.inject.Inject import javax.inject.Inject
@ -36,6 +37,7 @@ class SafetyPlugin @Inject constructor(
private val constraintChecker: ConstraintChecker, private val constraintChecker: ConstraintChecker,
private val openAPSAMAPlugin: OpenAPSAMAPlugin, private val openAPSAMAPlugin: OpenAPSAMAPlugin,
private val openAPSSMBPlugin: OpenAPSSMBPlugin, private val openAPSSMBPlugin: OpenAPSSMBPlugin,
private val openAPSSMBDynamicISFPlugin: OpenAPSSMBDynamicISFPlugin,
private val sensitivityOref1Plugin: SensitivityOref1Plugin, private val sensitivityOref1Plugin: SensitivityOref1Plugin,
private val activePlugin: ActivePlugin, private val activePlugin: ActivePlugin,
private val hardLimits: HardLimits, private val hardLimits: HardLimits,
@ -107,29 +109,29 @@ class SafetyPlugin @Inject constructor(
} }
override fun applyBasalConstraints(absoluteRate: Constraint<Double>, profile: Profile): Constraint<Double> { 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) { if (config.APS) {
var maxBasal = sp.getDouble(R.string.key_openapsma_max_basal, 1.0) var maxBasal = sp.getDouble(R.string.key_openapsma_max_basal, 1.0)
if (maxBasal < profile.getMaxDailyBasal()) { if (maxBasal < profile.getMaxDailyBasal()) {
maxBasal = profile.getMaxDailyBasal() maxBasal = profile.getMaxDailyBasal()
absoluteRate.addReason(rh.gs(R.string.increasingmaxbasal), this) 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 // 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 maxBasalMultiplier = sp.getDouble(R.string.key_openapsama_current_basal_safety_multiplier, 4.0)
val maxFromBasalMultiplier = floor(maxBasalMultiplier * profile.getBasal() * 100) / 100 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 maxBasalFromDaily = sp.getDouble(R.string.key_openapsama_max_daily_safety_multiplier, 3.0)
val maxFromDaily = floor(profile.getMaxDailyBasal() * maxBasalFromDaily * 100) / 100 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 val pump = activePlugin.activePump
// check for pump max // check for pump max
if (pump.pumpDescription.tempBasalStyle == PumpDescription.ABSOLUTE) { if (pump.pumpDescription.tempBasalStyle == PumpDescription.ABSOLUTE) {
val pumpLimit = pump.pumpDescription.pumpType.tbrSettings?.maxDose ?: 0.0 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 // do rounding
@ -149,19 +151,19 @@ class SafetyPlugin @Inject constructor(
val pump = activePlugin.activePump val pump = activePlugin.activePump
var percentRateAfterConst = java.lang.Double.valueOf(absoluteConstraint.value() / currentBasal * 100).toInt() 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() 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) { if (pump.pumpDescription.tempBasalStyle == PumpDescription.PERCENT) {
val pumpLimit = pump.pumpDescription.pumpType.tbrSettings?.maxDose ?: 0.0 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 return percentRate
} }
override fun applyBolusConstraints(insulin: Constraint<Double>): Constraint<Double> { 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) 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, maxBolus, 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, hardLimits.maxBolus(), rh.gs(R.string.limitingbolus, hardLimits.maxBolus(), rh.gs(R.string.hardlimit)), this)
val pump = activePlugin.activePump val pump = activePlugin.activePump
val rounded = pump.pumpDescription.pumpType.determineCorrectBolusSize(insulin.value()) val rounded = pump.pumpDescription.pumpType.determineCorrectBolusSize(insulin.value())
insulin.setIfDifferent(aapsLogger, rounded, rh.gs(R.string.pumplimit), this) 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> { 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) 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, maxBolus, 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, hardLimits.maxBolus(), rh.gs(R.string.limitingextendedbolus, hardLimits.maxBolus(), rh.gs(R.string.hardlimit)), this)
val pump = activePlugin.activePump val pump = activePlugin.activePump
val rounded = pump.pumpDescription.pumpType.determineCorrectExtendedBolusSize(insulin.value()) val rounded = pump.pumpDescription.pumpType.determineCorrectExtendedBolusSize(insulin.value())
insulin.setIfDifferent(aapsLogger, rounded, rh.gs(R.string.pumplimit), this) 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> { 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) 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 return carbs
} }
override fun applyMaxIOBConstraints(maxIob: Constraint<Double>): Constraint<Double> { override fun applyMaxIOBConstraints(maxIob: Constraint<Double>): Constraint<Double> {
val apsMode = sp.getString(R.string.key_aps_mode, "open") 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) val maxIobPref: Double = if (openAPSSMBPlugin.isEnabled() || openAPSSMBDynamicISFPlugin.isEnabled()) sp.getDouble(R.string.key_openapssmb_max_iob, 3.0) else sp.getDouble(R.string
maxIob.setIfSmaller(aapsLogger, maxIobPref, String.format(rh.gs(R.string.limitingiob), maxIobPref, rh.gs(R.string.maxvalueinpreferences)), this) .key_openapsma_max_iob, 1.5)
if (openAPSAMAPlugin.isEnabled()) maxIob.setIfSmaller(aapsLogger, hardLimits.maxIobAMA(), String.format(rh.gs(R.string.limitingiob), hardLimits.maxIobAMA(), rh.gs(R.string.hardlimit)), this) maxIob.setIfSmaller(aapsLogger, maxIobPref, rh.gs(R.string.limitingiob, maxIobPref, rh.gs(R.string.maxvalueinpreferences)), 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 (openAPSAMAPlugin.isEnabled()) maxIob.setIfSmaller(aapsLogger, hardLimits.maxIobAMA(), rh.gs(R.string.limitingiob, hardLimits.maxIobAMA(), 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) 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 return maxIob
} }

View file

@ -13,7 +13,7 @@ import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.androidaps.plugins.bus.RxBus import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification
import info.nightscout.androidaps.plugins.general.overview.notifications.Notification 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 info.nightscout.shared.sharedPreferences.SP
import org.spongycastle.util.encoders.Hex import org.spongycastle.util.encoders.Hex
import java.io.* import java.io.*

View file

@ -16,7 +16,7 @@ import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.plugins.bus.RxBus import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification
import info.nightscout.androidaps.plugins.general.overview.notifications.Notification 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.Inject
import javax.inject.Singleton import javax.inject.Singleton

View file

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

View file

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

View file

@ -7,7 +7,7 @@ import info.nightscout.androidaps.interfaces.PluginBase
import info.nightscout.androidaps.interfaces.PluginDescription import info.nightscout.androidaps.interfaces.PluginDescription
import info.nightscout.androidaps.interfaces.PluginType import info.nightscout.androidaps.interfaces.PluginType
import info.nightscout.shared.logging.AAPSLogger 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.Inject
import javax.inject.Singleton import javax.inject.Singleton

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -6,12 +6,11 @@ import android.content.pm.ResolveInfo
import android.os.Bundle import android.os.Bundle
import dagger.android.HasAndroidInjector import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R 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.durationInMinutes
import info.nightscout.androidaps.extensions.toStringFull import info.nightscout.androidaps.extensions.toStringFull
import info.nightscout.androidaps.interfaces.* 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.EventOpenAPSUpdateGui
import info.nightscout.androidaps.plugins.bus.RxBus import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.nsclient.data.DeviceStatusData 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.general.overview.events.EventOverviewBolusProgress
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProvider import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProvider
import info.nightscout.androidaps.receivers.ReceiverStatusStore 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.DateUtil
import info.nightscout.androidaps.utils.DefaultValueHelper import info.nightscout.androidaps.utils.DefaultValueHelper
import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers import info.nightscout.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.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -68,26 +69,6 @@ class DataBroadcastPlugin @Inject constructor(
.observeOn(aapsSchedulers.io) .observeOn(aapsSchedulers.io)
.subscribe({ sendData(it) }, fabricPrivacy::logException) .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 disposable.add(rxBus
.toObservable(EventAutosensCalculationFinished::class.java) .toObservable(EventAutosensCalculationFinished::class.java)
.observeOn(aapsSchedulers.io) .observeOn(aapsSchedulers.io)

View file

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

View file

@ -17,7 +17,7 @@ import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.receivers.DataWorker import info.nightscout.androidaps.receivers.DataWorker
import info.nightscout.androidaps.utils.JsonHelper 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 info.nightscout.shared.sharedPreferences.SP
import org.json.JSONObject import org.json.JSONObject
import javax.inject.Inject import javax.inject.Inject

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