diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 381dffa690..ca2f66f536 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -44,5 +44,5 @@ Hints * Start small, it is easier to review smaller changes that affect fewer parts of code * Take a look into Issues list (https://github.com/nightscout/AndroidAPS/issues) - maybe there is something you can fix or implement * For new features, make sure there is Issue to track progress and have on-topic discussion -* Reach out to community, discuss idea on Gitter (https://gitter.im/MilosKozak/AndroidAPS) +* Reach out to community, discuss idea on Discord (https://discord.gg/4fQUWHZ4Mw) * Speak with other developers to minimise merge conflicts. Find out who worked, working or plan to work on speciffic issue or part of app diff --git a/app/build.gradle b/app/build.gradle index 9d7b945424..5b6c4ed3d2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -109,7 +109,7 @@ android { defaultConfig { multiDexEnabled true versionCode 1500 - version "3.0.0.1-dev-aidex" + version "3.0.0.1-dev-i" buildConfigField "String", "VERSION", '"' + version + '"' buildConfigField "String", "BUILDVERSION", '"' + generateGitBuild() + '-' + generateDate() + '"' buildConfigField "String", "REMOTE", '"' + generateGitRemote() + '"' @@ -167,9 +167,6 @@ android { allprojects { repositories { - flatDir { - dirs 'libs' - } } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3badcdbfaf..a4c3f20972 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -41,14 +41,35 @@ + android:theme="@style/AppTheme.Launcher" > + + + + + + + + + + + + + + @@ -76,12 +98,18 @@ - - - - - - + + + + + + - - - @@ -235,6 +260,7 @@ normalTarget - || profile.low_temptarget_lowers_sensitivity && profile.temptargetSet && target_bg < normalTarget ) { - // w/ target 100, temp target 110 = .89, 120 = 0.8, 140 = 0.67, 160 = .57, and 200 = .44 - // e.g.: Sensitivity ratio set to 0.8 based on temp target of 120; Adjusting basal from 1.65 to 1.35; ISF from 58.9 to 73.6 - //sensitivityRatio = 2/(2+(target_bg-normalTarget)/40); - var c = halfBasalTarget - normalTarget; - sensitivityRatio = c/(c+target_bg-normalTarget); - // limit sensitivityRatio to profile.autosens_max (1.2x by default) - sensitivityRatio = Math.min(sensitivityRatio, profile.autosens_max); - sensitivityRatio = round(sensitivityRatio,2); - console.log("Sensitivity ratio set to "+sensitivityRatio+" based on temp target of "+target_bg+"; "); - } else if (typeof autosens_data !== 'undefined' && autosens_data) { - sensitivityRatio = autosens_data.ratio; - console.log("Autosens ratio: "+sensitivityRatio+"; "); - } - if (sensitivityRatio) { - basal = profile.current_basal * sensitivityRatio; - basal = round_basal(basal, profile); - if (basal !== profile_current_basal) { - console.log("Adjusting basal from "+profile_current_basal+" to "+basal+"; "); - } else { - console.log("Basal unchanged: "+basal+"; "); - } - } - // adjust min, max, and target BG for sensitivity, such that 50% increase in ISF raises target from 100 to 120 - if (profile.temptargetSet) { - //console.log("Temp Target set, not adjusting with autosens; "); - } else if (typeof autosens_data !== 'undefined' && autosens_data) { - if ( profile.sensitivity_raises_target && autosens_data.ratio < 1 || profile.resistance_lowers_target && autosens_data.ratio > 1 ) { - // with a target of 100, default 0.7-1.2 autosens min/max range would allow a 93-117 target range - min_bg = round((min_bg - 60) / autosens_data.ratio) + 60; - max_bg = round((max_bg - 60) / autosens_data.ratio) + 60; - var new_target_bg = round((target_bg - 60) / autosens_data.ratio) + 60; - // don't allow target_bg below 80 - new_target_bg = Math.max(80, new_target_bg); - if (target_bg === new_target_bg) { - console.log("target_bg unchanged: "+new_target_bg+"; "); - } else { - console.log("target_bg from "+target_bg+" to "+new_target_bg+"; "); - } - target_bg = new_target_bg; - } - } - - if (typeof iob_data === 'undefined' ) { - rT.error ='Error: iob_data undefined. '; - return rT; - } - - var iobArray = iob_data; - if (typeof(iob_data.length) && iob_data.length > 1) { - iob_data = iobArray[0]; - //console.error(JSON.stringify(iob_data[0])); - } - - if (typeof iob_data.activity === 'undefined' || typeof iob_data.iob === 'undefined' ) { - rT.error ='Error: iob_data missing some property. '; - return rT; - } - - var tick; - - if (glucose_status.delta > -0.5) { - tick = "+" + round(glucose_status.delta,0); - } else { - tick = round(glucose_status.delta,0); - } - //var minDelta = Math.min(glucose_status.delta, glucose_status.short_avgdelta, glucose_status.long_avgdelta); - var minDelta = Math.min(glucose_status.delta, glucose_status.short_avgdelta); - var minAvgDelta = Math.min(glucose_status.short_avgdelta, glucose_status.long_avgdelta); - var maxDelta = Math.max(glucose_status.delta, glucose_status.short_avgdelta, glucose_status.long_avgdelta); var profile_sens = round(profile.sens,1) var sens = profile.sens; var now = new Date().getHours(); - if (now < 1){ - now = 1;} - else { - console.error("Time now is "+now+"; "); - } - console.error(" "); - console.error("++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - console.error("++ Dynamic ISF Beta 1.4 - Linear Extrapolation/TDD7 ++"); - console.error("++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - console.error(" "); + if (now < 1){ + now = 1;} + else { + console.error("Time now is "+now+"; "); + } + //********************************************************************************* + //** Start of Dynamic ISF code for predictions ** + //********************************************************************************* - if (meal_data.TDDAIMI7){ - var tdd7 = meal_data.TDDAIMI7; + console.error("---------------------------------------------------------"); + console.error( " Dynamic ISF version Beta 1.5 "); + console.error("---------------------------------------------------------"); + + + if (meal_data.TDDAIMI7){ + var tdd7 = meal_data.TDDAIMI7; } else{ var tdd7 = ((basal * 12)*100)/21; } console.error("7-day average TDD is: " +tdd7+ "; "); - console.error(" "); - if (meal_data.TDDLast24){ - var tdd_24 = meal_data.TDDLast24; + if (meal_data.TDDLast24){ + var tdd_24 = meal_data.TDDLast24; + } + else { + var tdd_24 = (( basal * 24 ) * 2.8); + } + + if (meal_data.TDDPUMP){ + var tdd_pump = ( (meal_data.TDDPUMP / now ) * 24); + } + else { + var tdd_pump = (( basal * 24 ) * 2.8); + } + console.log("Rolling TDD for last 24 hours is: "+tdd_24+"; "); + + /*var tdd_pump_now = meal_data.TDDPUMP; + var tdd_pump = ( tdd_pump_now / (now / 24));*/ + var TDD = (tdd7 * 0.4) + (tdd_pump * 0.6); + + console.error("Pump extrapolated TDD = "+tdd_pump+"; "); + //if (tdd7 > 0){ + if ( tdd_pump > tdd7 && now < 5 || now < 7 && TDD < ( 0.8 * tdd7 ) ){ + TDD = ( 0.8 * tdd7 ); + console.log("Excess or too low insulin from pump so TDD set to "+TDD+" based on 75% of TDD7; "); + rT.reason += "TDD: " +TDD+ " due to low or high tdd from pump; "; + } + + else if (tdd_pump > (1.75 * tdd7)){ + TDD = tdd7; + console.error("TDD set to TDD7 due to high pump usage reported. TDD = "+TDD+"; "); + rT.reason += "TDD set to TDD7 due to high pump usage reported. TDD = "+TDD+"; "; + } + + + else if (tdd_pump < (0.33 * tdd7)){ + TDD = (tdd7 * 0.25) + (tdd_pump * 0.75); + console.error("TDD weighted to pump due to low insulin usage. TDD = "+TDD+"; "); + rT.reason += "TDD weighted to pump due to low insulin usage. TDD = "+TDD+"; "; + } + + else { + console.log("TDD = " +TDD+ " based on standard pump 60/tdd7 40 split; "); + rT.reason += "TDD: " +TDD+ " based on standard pump 60/tdd7 40 split; "; + } + + var dynISFadjust = profile.DynISFAdjust; + var dynISFadjust = ( dynISFadjust / 100 ); + var TDD = (dynISFadjust * TDD); + var variable_sens = (277700 / ( TDD * bg)); + variable_sens = round(variable_sens,1); + if (dynISFadjust > 1 ) { + console.log("TDD adjustment factor is: " +dynISFadjust+"; "); + console.log("TDD adjusted to "+TDD+" using adjustment factor of "+dynISFadjust+"; "); + console.log("Current sensitivity for predictions is " +variable_sens+" based on current bg"); } - else { - var tdd_24 = (( basal * 24 ) * 2.8); + else if (dynISFadjust < 1 ){ + console.log("TDD adjustment factor is: " +dynISFadjust+"; "); + console.log("TDD adjusted to "+TDD+" using adjustment factor of "+dynISFadjust+"; "); + console.log("Current sensitivity for predictions is " +variable_sens+" based on current bg"); + } else { + console.log("Current sensitivity for predictions is " +variable_sens+" based on current bg"); + } + sens = variable_sens; + + //********************************************************************************* + //** End of Dynamic ISF code for predictions ** + //********************************************************************************* + + + if ( high_temptarget_raises_sensitivity && profile.temptargetSet && target_bg > normalTarget || profile.low_temptarget_lowers_sensitivity && profile.temptargetSet && target_bg < normalTarget ) { + // w/ target 100, temp target 110 = .89, 120 = 0.8, 140 = 0.67, 160 = .57, and 200 = .44 + // e.g.: Sensitivity ratio set to 0.8 based on temp target of 120; Adjusting basal from 1.65 to 1.35; ISF from 58.9 to 73.6 + //sensitivityRatio = 2/(2+(target_bg-normalTarget)/40); + var c = halfBasalTarget - normalTarget; + sensitivityRatio = c/(c+target_bg-normalTarget); + // limit sensitivityRatio to profile.autosens_max (1.2x by default) + sensitivityRatio = Math.min(sensitivityRatio, profile.autosens_max); + sensitivityRatio = round(sensitivityRatio,2); + console.log("Sensitivity ratio set to "+sensitivityRatio+" based on temp target of "+target_bg+"; "); + sens = sens / sensitivityRatio ; + sens = round(sens, 1); + console.log("ISF from "+variable_sens+" to "+sens+ "due to temp target; "); + } + else { + sensitivityRatio = ( tdd_24 / tdd7 ); + } + if (sensitivityRatio > 1) { + sensitivityRatio = Math.min(sensitivityRatio, profile.autosens_max); + sensitivityRatio = round(sensitivityRatio,2); + console.log("Sensitivity ratio: "+sensitivityRatio+"; "); + } + else if( sensitivityRatio < 1) { + sensitivityRatio = Math.max(sensitivityRatio, profile.autosens_min); + sensitivityRatio = round(sensitivityRatio,2); + console.log("Sensitivity ratio: "+sensitivityRatio+"; "); + } + else { + console.log("Sensitivity ratio: "+sensitivityRatio+"; "); + } + + + if (sensitivityRatio && profile.openapsama_useautosens === true) { + basal = profile.current_basal * sensitivityRatio; + basal = round_basal(basal, profile); + if (basal !== profile_current_basal) { + console.log("Adjusting basal from "+profile_current_basal+" to "+basal+"; "); + } else { + console.log("Autosens disabled. Basal unchanged: "+basal+"; "); + } } - if (meal_data.TDDPUMP){ - var tdd_pump = ( (meal_data.TDDPUMP / now ) * 24); + // adjust min, max, and target BG for sensitivity, such that 50% increase in ISF raises target from 100 to 120 + if (profile.temptargetSet) { + //console.log("Temp Target set, not adjusting with autosens; "); + } else { + if ( profile.sensitivity_raises_target && sensitivityRatio < 1 && profile.openapsama_useautosens === true || profile.resistance_lowers_target && sensitivityRatio > 1 && profile.openapsama_useautosens === true) { + // with a target of 100, default 0.7-1.2 autosens min/max range would allow a 93-117 target range + min_bg = round((min_bg - 60) / sensitivityRatio) + 60; + max_bg = round((max_bg - 60) / sensitivityRatio) + 60; + var new_target_bg = round((target_bg - 60) / sensitivityRatio) + 60; + // don't allow target_bg below 80 + new_target_bg = Math.max(80, new_target_bg); + if (target_bg === new_target_bg) { + console.log("target_bg unchanged: "+new_target_bg+"; "); + } else { + console.log("target_bg from "+target_bg+" to "+new_target_bg+"; "); + } + target_bg = new_target_bg; + } } - else { - var tdd_pump = (( basal * 24 ) * 2.8); + + + if (typeof iob_data === 'undefined' ) { + rT.error ='Error: iob_data undefined. '; + return rT; } - var TDD = (tdd7 * 0.4) + (tdd_pump * 0.6); - console.error("Pump extrapolated TDD = "+tdd_pump+"; "); - //if (tdd7 > 0){ - if ( tdd_pump > tdd7 && now < 5 || now < 7 && TDD < ( 0.8 * tdd7 ) ){ - TDD = ( 0.8 * tdd7 ); - console.log("Excess or too low insulin from pump so TDD set to "+TDD+" based on 75% of TDD7; "); - rT.reason += "TDD: " +TDD+ " due to low or high tdd from pump; "; - } + var iobArray = iob_data; + if (typeof(iob_data.length) && iob_data.length > 1) { + iob_data = iobArray[0]; + //console.error(JSON.stringify(iob_data[0])); + } - else if (tdd_pump > (1.75 * tdd7)){ - TDD = tdd7; - console.error("TDD set to TDD7 due to high pump usage reported. TDD = "+TDD+"; "); - rT.reason += "TDD set to TDD7 due to high pump usage reported. TDD = "+TDD+"; "; - } + if (typeof iob_data.activity === 'undefined' || typeof iob_data.iob === 'undefined' ) { + rT.error ='Error: iob_data missing some property. '; + return rT; + } + var tick; - else if (tdd_pump < (0.33 * tdd7)){ - TDD = (tdd7 * 0.25) + (tdd_pump * 0.75); - console.error("TDD weighted to pump due to low insulin usage. TDD = "+TDD+"; "); - rT.reason += "TDD weighted to pump due to low insulin usage. TDD = "+TDD+"; "; - } + if (glucose_status.delta > -0.5) { + tick = "+" + round(glucose_status.delta,0); + } else { + tick = round(glucose_status.delta,0); + } + //var minDelta = Math.min(glucose_status.delta, glucose_status.short_avgdelta, glucose_status.long_avgdelta); + var minDelta = Math.min(glucose_status.delta, glucose_status.short_avgdelta); + var minAvgDelta = Math.min(glucose_status.short_avgdelta, glucose_status.long_avgdelta); + var maxDelta = Math.max(glucose_status.delta, glucose_status.short_avgdelta, glucose_status.long_avgdelta); - else { - console.log("TDD = " +TDD+ " based on standard pump 60/tdd7 40 split; "); - rT.reason += "TDD: " +TDD+ " based on standard pump 60/tdd7 40 split; "; - } - - console.error(" "); - - - var variable_sens = (277700 / (TDD * bg)); - variable_sens = round(variable_sens,1); - console.log("Current sensitivity for predictions is " +variable_sens+" based on current bg"); - console.error(" "); - - sens = variable_sens; - if ( high_temptarget_raises_sensitivity && profile.temptargetSet && target_bg > normalTarget || profile.low_temptarget_lowers_sensitivity && profile.temptargetSet && target_bg < normalTarget ) { - sens = sens / sensitivityRatio ; - sens = round(sens, 1); - console.log("ISF from "+variable_sens+" to "+sens+ "due to temp target; "); - } else { - sens = sens; - sens = round(sens, 1); - } console.error("; CR:",profile.carb_ratio); @@ -772,19 +805,27 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ console.error("UAM Impact:",uci,"mg/dL per 5m; UAM Duration:",UAMduration,"hours"); - console.log("EventualBG is" +eventualBG+" ;"); + console.log("EventualBG is" +eventualBG+" ;"); + + if (bg > target_bg && glucose_status.delta < 3 && glucose_status.delta > -3 && glucose_status.short_avgdelta > -3 && glucose_status.short_avgdelta < 3 && eventualBG > target_bg && eventualBG < bg ) { + var future_sens = ( 277700 / (TDD * ((eventualBG * 0.5) + (bg * 0.5) ) ) ); + console.log("Future state sensitivity is " +future_sens+" based on eventual and current bg due to flat glucose level above target"); + rT.reason += "Dosing sensitivity: " +future_sens+" using eventual BG;"; + } + + else if( glucose_status.delta > 0 && eventualBG > target_bg ) { + var future_sens = ( 277700 / (TDD * bg) ); + console.log("Future state sensitivity is " +future_sens+" using current bg due to small delta or variation"); + rT.reason += "Dosing sensitivity: " +future_sens+" using current BG;"; + } + + else { + var future_sens = ( 277700 / (TDD * eventualBG) ); + console.log("Future state sensitivity is " +future_sens+" based on eventual bg due to -ve delta"); + rT.reason += "Dosing sensitivity: " +future_sens+" using eventual BG;"; + } + var future_sens = round(future_sens,1); - if( glucose_status.delta >= 0 || bg > 60 && glucose_status.delta < 2 && glucose_status.delta > -2 && glucose_status.short_avgdelta > -2 && glucose_status.short_avgdelta < 2 || eventualBG > target_bg && glucose_status.delta < 0 ) { - var future_sens = ( 277700 / (TDD * bg) ); - console.log("Future state sensitivity is " +future_sens+" using current bg due to no COB & small delta or variation"); - rT.reason += "Dosing sensitivity: " +future_sens+" using current BG;"; - } - else { - var future_sens = ( 277700 / (TDD * eventualBG)); - console.log("Future state sensitivity is " +future_sens+" based on eventual bg due to -ve delta"); - rT.reason += "Dosing sensitivity: " +future_sens+" using eventual BG;"; - } - var future_sens = round(future_sens,1); minIOBPredBG = Math.max(39,minIOBPredBG); diff --git a/app/src/main/java/info/nightscout/androidaps/MainActivity.kt b/app/src/main/java/info/nightscout/androidaps/MainActivity.kt index bf7e2982cf..9a5d86db99 100644 --- a/app/src/main/java/info/nightscout/androidaps/MainActivity.kt +++ b/app/src/main/java/info/nightscout/androidaps/MainActivity.kt @@ -7,6 +7,7 @@ import android.os.Bundle import android.os.PersistableBundle import android.text.SpannableString import android.text.method.LinkMovementMethod +import android.text.style.ForegroundColorSpan import android.text.util.Linkify import android.util.TypedValue import android.view.Menu @@ -19,10 +20,10 @@ import android.widget.EditText import android.widget.LinearLayout import android.widget.TextView import androidx.appcompat.app.ActionBarDrawerToggle -import androidx.appcompat.app.AlertDialog import androidx.appcompat.widget.Toolbar import androidx.core.view.GravityCompat import androidx.viewpager2.widget.ViewPager2 +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.tabs.TabLayoutMediator import com.google.firebase.crashlytics.FirebaseCrashlytics import com.joanzapata.iconify.Iconify @@ -33,6 +34,7 @@ import info.nightscout.androidaps.database.entities.UserEntry.Action import info.nightscout.androidaps.database.entities.UserEntry.Sources import info.nightscout.androidaps.databinding.ActivityMainBinding import info.nightscout.androidaps.events.EventAppExit +import info.nightscout.androidaps.events.EventInitializationChanged import info.nightscout.androidaps.events.EventPreferenceChange import info.nightscout.androidaps.events.EventRebuildTabs import info.nightscout.androidaps.interfaces.* @@ -50,6 +52,7 @@ import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.utils.buildHelper.BuildHelper import info.nightscout.androidaps.utils.extensions.isRunningRealPumpTest import info.nightscout.androidaps.utils.locale.LocaleHelper +import info.nightscout.androidaps.utils.protection.PasswordCheck import info.nightscout.androidaps.utils.protection.ProtectionCheck import info.nightscout.androidaps.utils.rx.AapsSchedulers import info.nightscout.androidaps.utils.tabs.TabPageAdapter @@ -67,7 +70,6 @@ class MainActivity : NoSplashAppCompatActivity() { private val disposable = CompositeDisposable() @Inject lateinit var aapsSchedulers: AapsSchedulers - @Inject lateinit var rxBus: RxBus @Inject lateinit var androidPermission: AndroidPermission @Inject lateinit var sp: SP @Inject lateinit var versionCheckerUtils: VersionCheckerUtils @@ -84,12 +86,13 @@ class MainActivity : NoSplashAppCompatActivity() { @Inject lateinit var config: Config @Inject lateinit var uel: UserEntryLogger @Inject lateinit var profileFunction: ProfileFunction + @Inject lateinit var passwordCheck: PasswordCheck private lateinit var actionBarDrawerToggle: ActionBarDrawerToggle private var pluginPreferencesMenuItem: MenuItem? = null private var menu: Menu? = null private var menuOpen = false - + private var isProtectionCheckActive = false private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { @@ -115,6 +118,7 @@ class MainActivity : NoSplashAppCompatActivity() { override fun onPageSelected(position: Int) { setPluginPreferenceMenuName() checkPluginPreferences(binding.mainPager) + setDisabledMenuItemColorPluginPreferences() } }) @@ -134,6 +138,12 @@ class MainActivity : NoSplashAppCompatActivity() { .toObservable(EventPreferenceChange::class.java) .observeOn(aapsSchedulers.main) .subscribe({ processPreferenceChange(it) }, fabricPrivacy::logException) + disposable += rxBus + .toObservable(EventInitializationChanged::class.java) + .observeOn(aapsSchedulers.main) + .subscribe({ + passwordCheck.passwordResetCheck(this) + }, fabricPrivacy::logException) if (startWizard() && !isRunningRealPumpTest()) { protectionCheck.queryProtection(this, ProtectionCheck.Protection.PREFERENCES, { startActivity(Intent(this, SetupWizardActivity::class.java)) @@ -168,10 +178,13 @@ class MainActivity : NoSplashAppCompatActivity() { override fun onResume() { super.onResume() - protectionCheck.queryProtection(this, ProtectionCheck.Protection.APPLICATION, null, - UIRunnable { OKDialog.show(this, "", rh.gs(R.string.authorizationfailed)) { finish() } }, - UIRunnable { OKDialog.show(this, "", rh.gs(R.string.authorizationfailed)) { finish() } } - ) + if (!isProtectionCheckActive) { + isProtectionCheckActive = true + protectionCheck.queryProtection(this, ProtectionCheck.Protection.APPLICATION, UIRunnable { isProtectionCheckActive = false }, + UIRunnable { OKDialog.show(this, "", rh.gs(R.string.authorizationfailed)) { isProtectionCheckActive = false; finish() } }, + UIRunnable { OKDialog.show(this, "", rh.gs(R.string.authorizationfailed)) { isProtectionCheckActive = false; finish() } } + ) + } } private fun setWakeLock() { @@ -252,6 +265,14 @@ class MainActivity : NoSplashAppCompatActivity() { return super.dispatchTouchEvent(event) } + private fun setDisabledMenuItemColorPluginPreferences() { + if (pluginPreferencesMenuItem?.isEnabled == false) { + val spanString = SpannableString(this.menu?.findItem(R.id.nav_plugin_preferences)?.title.toString()) + spanString.setSpan(ForegroundColorSpan(rh.gac(R.attr.disabledTextColor)), 0, spanString.length, 0) + this.menu?.findItem(R.id.nav_plugin_preferences)?.title = spanString + } + } + private fun setPluginPreferenceMenuName() { if (binding.mainPager.currentItem >= 0) { val plugin = (binding.mainPager.adapter as TabPageAdapter).getPluginAt(binding.mainPager.currentItem) @@ -270,7 +291,7 @@ class MainActivity : NoSplashAppCompatActivity() { } override fun onPanelClosed(featureId: Int, menu: Menu) { - menuOpen = false; + menuOpen = false super.onPanelClosed(featureId, menu) } @@ -280,6 +301,7 @@ class MainActivity : NoSplashAppCompatActivity() { pluginPreferencesMenuItem = menu.findItem(R.id.nav_plugin_preferences) setPluginPreferenceMenuName() checkPluginPreferences(binding.mainPager) + setDisabledMenuItemColorPluginPreferences() return true } @@ -320,7 +342,7 @@ class MainActivity : NoSplashAppCompatActivity() { message += rh.gs(R.string.about_link_urls) val messageSpanned = SpannableString(message) Linkify.addLinks(messageSpanned, Linkify.WEB_URLS) - AlertDialog.Builder(this, R.style.DialogTheme) + MaterialAlertDialogBuilder(this, R.style.DialogTheme) .setTitle(rh.gs(R.string.app_name) + " " + BuildConfig.VERSION) .setIcon(iconsProvider.getIcon()) .setMessage(messageSpanned) diff --git a/app/src/main/java/info/nightscout/androidaps/MainApp.kt b/app/src/main/java/info/nightscout/androidaps/MainApp.kt index 781ab707c3..cbecb3cc31 100644 --- a/app/src/main/java/info/nightscout/androidaps/MainApp.kt +++ b/app/src/main/java/info/nightscout/androidaps/MainApp.kt @@ -6,6 +6,13 @@ import android.content.IntentFilter import android.net.ConnectivityManager import android.net.wifi.WifiManager import android.os.Build +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 com.uber.rxdogtag.RxDogTag import dagger.android.AndroidInjector import dagger.android.DaggerApplication @@ -20,24 +27,23 @@ import info.nightscout.androidaps.di.StaticInjector import info.nightscout.androidaps.interfaces.Config import info.nightscout.androidaps.interfaces.ConfigBuilder import info.nightscout.androidaps.interfaces.PluginBase -import info.nightscout.shared.logging.AAPSLogger -import info.nightscout.shared.logging.LTag import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.plugins.configBuilder.PluginStore import info.nightscout.androidaps.plugins.constraints.versionChecker.VersionCheckerUtils import info.nightscout.androidaps.plugins.general.overview.notifications.Notification import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationStore -import info.nightscout.androidaps.receivers.BTReceiver -import info.nightscout.androidaps.receivers.ChargingStateReceiver -import info.nightscout.androidaps.receivers.KeepAliveReceiver.KeepAliveManager -import info.nightscout.androidaps.receivers.NetworkChangeReceiver -import info.nightscout.androidaps.receivers.TimeDateOrTZChangeReceiver +import info.nightscout.androidaps.plugins.general.themes.ThemeSwitcherPlugin +import info.nightscout.androidaps.receivers.* import info.nightscout.androidaps.services.AlarmSoundServiceHelper import info.nightscout.androidaps.utils.ActivityMonitor import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.LocalAlertUtils +import info.nightscout.androidaps.utils.ProcessLifecycleListener import info.nightscout.androidaps.utils.buildHelper.BuildHelper import info.nightscout.androidaps.utils.locale.LocaleHelper -import info.nightscout.androidaps.utils.protection.PasswordCheck +import info.nightscout.androidaps.widget.updateWidget +import info.nightscout.shared.logging.AAPSLogger +import info.nightscout.shared.logging.LTag import info.nightscout.shared.sharedPreferences.SP import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.exceptions.UndeliverableException @@ -46,6 +52,7 @@ import io.reactivex.rxjava3.plugins.RxJavaPlugins import net.danlew.android.joda.JodaTimeAndroid import java.io.IOException import java.net.SocketException +import java.util.concurrent.TimeUnit import javax.inject.Inject class MainApp : DaggerApplication() { @@ -60,16 +67,20 @@ class MainApp : DaggerApplication() { @Inject lateinit var config: Config @Inject lateinit var buildHelper: BuildHelper @Inject lateinit var configBuilder: ConfigBuilder - @Inject lateinit var keepAliveManager: KeepAliveManager @Inject lateinit var plugins: List<@JvmSuppressWildcards PluginBase> @Inject lateinit var compatDBHelper: CompatDBHelper @Inject lateinit var repository: AppRepository @Inject lateinit var dateUtil: DateUtil @Inject lateinit var staticInjector: StaticInjector// TODO avoid , here fake only to initialize @Inject lateinit var uel: UserEntryLogger - @Inject lateinit var passwordCheck: PasswordCheck @Inject lateinit var alarmSoundServiceHelper: AlarmSoundServiceHelper @Inject lateinit var notificationStore: NotificationStore + @Inject lateinit var processLifecycleListener: ProcessLifecycleListener + @Inject lateinit var profileSwitchPlugin: ThemeSwitcherPlugin + @Inject lateinit var localAlertUtils: LocalAlertUtils + + private var handler = Handler(HandlerThread(this::class.simpleName + "Handler").also { it.start() }.looper) + private lateinit var refreshWidget: Runnable override fun onCreate() { super.onCreate() @@ -77,6 +88,7 @@ class MainApp : DaggerApplication() { RxDogTag.install() setRxErrorHandler() LocaleHelper.update(this) + ProcessLifecycleOwner.get().lifecycle.addObserver(processLifecycleListener) var gitRemote: String? = BuildConfig.REMOTE var commitHash: String? = BuildConfig.HEAD @@ -84,21 +96,10 @@ class MainApp : DaggerApplication() { gitRemote = null commitHash = null } - disposable += repository.runTransaction(VersionChangeTransaction(BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE, gitRemote, commitHash)).subscribe() - if (sp.getBoolean(R.string.key_ns_logappstartedevent, config.APS)) - disposable += repository - .runTransaction( - InsertIfNewByTimestampTherapyEventTransaction( - timestamp = dateUtil.now(), - type = TherapyEvent.Type.NOTE, - note = getString(info.nightscout.androidaps.core.R.string.androidaps_start) + " - " + Build.MANUFACTURER + " " + Build.MODEL, - glucoseUnit = TherapyEvent.GlucoseUnit.MGDL - ) - ) - .subscribe() disposable += compatDBHelper.dbChangeDisposable() registerActivityLifecycleCallbacks(activityMonitor) JodaTimeAndroid.init(this) + profileSwitchPlugin.setThemeMode() aapsLogger.debug("Version: " + BuildConfig.VERSION_NAME) aapsLogger.debug("BuildVersion: " + BuildConfig.BUILDVERSION) aapsLogger.debug("Remote: " + BuildConfig.REMOTE) @@ -113,10 +114,38 @@ class MainApp : DaggerApplication() { // Register all tabs in app here pluginStore.plugins = plugins configBuilder.initialize() - keepAliveManager.setAlarm(this) + + 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() + WorkManager.getInstance(this).enqueueUniquePeriodicWork( + "KeepAlive", + ExistingPeriodicWorkPolicy.REPLACE, + PeriodicWorkRequest.Builder(KeepAliveWorker::class.java, 15, TimeUnit.MINUTES) + .setInputData(Data.Builder().putString("schedule", "KeepAlive").build()) + .setInitialDelay(5, TimeUnit.SECONDS) + .build() + ) + localAlertUtils.shortenSnoozeInterval() + localAlertUtils.preSnoozeAlarms() doMigrations() uel.log(UserEntry.Action.START_AAPS, UserEntry.Sources.Aaps) - passwordCheck.passwordResetCheck(this) + + // schedule widget update + refreshWidget = Runnable { + handler.postDelayed(refreshWidget, 60000) + updateWidget(this) + } + handler.postDelayed(refreshWidget, 60000) } private fun setRxErrorHandler() { @@ -186,7 +215,6 @@ class MainApp : DaggerApplication() { override fun onTerminate() { aapsLogger.debug(LTag.CORE, "onTerminate") unregisterActivityLifecycleCallbacks(activityMonitor) - keepAliveManager.cancelAlarm(this) alarmSoundServiceHelper.stopService(this) super.onTerminate() } diff --git a/app/src/main/java/info/nightscout/androidaps/activities/HistoryBrowseActivity.kt b/app/src/main/java/info/nightscout/androidaps/activities/HistoryBrowseActivity.kt index 3cf77eaf74..d3736c6869 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/HistoryBrowseActivity.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/HistoryBrowseActivity.kt @@ -2,7 +2,7 @@ package info.nightscout.androidaps.activities import android.annotation.SuppressLint import android.app.DatePickerDialog -import android.graphics.Color +import android.content.Context import android.os.Bundle import android.util.DisplayMetrics import android.view.ViewGroup @@ -18,21 +18,19 @@ import info.nightscout.androidaps.events.EventAutosensCalculationFinished import info.nightscout.androidaps.events.EventCustomCalculationFinished import info.nightscout.androidaps.events.EventRefreshOverview import info.nightscout.androidaps.extensions.toVisibility +import info.nightscout.androidaps.extensions.toVisibilityKeepSpace import info.nightscout.androidaps.interfaces.ActivePlugin import info.nightscout.androidaps.interfaces.Config import info.nightscout.androidaps.interfaces.Loop import info.nightscout.androidaps.interfaces.ProfileFunction -import info.nightscout.androidaps.plugins.bus.RxBus import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus import info.nightscout.androidaps.plugins.general.overview.OverviewData import info.nightscout.androidaps.plugins.general.overview.OverviewMenus +import info.nightscout.androidaps.plugins.general.overview.events.EventUpdateOverviewGraph import info.nightscout.androidaps.plugins.general.overview.graphData.GraphData import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventBucketedDataCreated import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress -import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin -import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref1Plugin -import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin +import info.nightscout.androidaps.receivers.DataWorker import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.DefaultValueHelper import info.nightscout.androidaps.utils.FabricPrivacy @@ -40,6 +38,7 @@ import info.nightscout.androidaps.utils.T import info.nightscout.androidaps.utils.Translator import info.nightscout.androidaps.utils.buildHelper.BuildHelper import info.nightscout.androidaps.utils.rx.AapsSchedulers +import info.nightscout.androidaps.workflow.CalculationWorkflow import info.nightscout.shared.logging.LTag import info.nightscout.shared.sharedPreferences.SP import io.reactivex.rxjava3.disposables.CompositeDisposable @@ -52,15 +51,11 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() { @Inject lateinit var injector: HasAndroidInjector @Inject lateinit var aapsSchedulers: AapsSchedulers - @Inject lateinit var rxBus: RxBus @Inject lateinit var sp: SP @Inject lateinit var profileFunction: ProfileFunction @Inject lateinit var defaultValueHelper: DefaultValueHelper @Inject lateinit var activePlugin: ActivePlugin @Inject lateinit var buildHelper: BuildHelper - @Inject lateinit var sensitivityOref1Plugin: SensitivityOref1Plugin - @Inject lateinit var sensitivityAAPSPlugin: SensitivityAAPSPlugin - @Inject lateinit var sensitivityWeightedAveragePlugin: SensitivityWeightedAveragePlugin @Inject lateinit var repository: AppRepository @Inject lateinit var fabricPrivacy: FabricPrivacy @Inject lateinit var overviewMenus: OverviewMenus @@ -69,6 +64,9 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() { @Inject lateinit var loop: Loop @Inject lateinit var nsDeviceStatus: NSDeviceStatus @Inject lateinit var translator: Translator + @Inject lateinit var context: Context + @Inject lateinit var dataWorker: DataWorker + @Inject lateinit var calculationWorkflow: CalculationWorkflow private val disposable = CompositeDisposable() @@ -91,6 +89,17 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() { setContentView(binding.root) // We don't want to use injected singletons but own instance working on top of different data + overviewData = + OverviewData( + aapsLogger, + rh, + dateUtil, + sp, + activePlugin, + defaultValueHelper, + profileFunction, + repository + ) iobCobCalculator = IobCobCalculatorPlugin( injector, @@ -101,30 +110,11 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() { rh, profileFunction, activePlugin, - sensitivityOref1Plugin, - sensitivityAAPSPlugin, - sensitivityWeightedAveragePlugin, fabricPrivacy, dateUtil, - repository - ) - overviewData = - OverviewData( - injector, - aapsLogger, - rh, - dateUtil, - sp, - activePlugin, - defaultValueHelper, - profileFunction, - config, - loop, - nsDeviceStatus, repository, - overviewMenus, - iobCobCalculator, - translator + overviewData, + calculationWorkflow ) binding.left.setOnClickListener { @@ -179,7 +169,7 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() { val cal = Calendar.getInstance() cal.timeInMillis = overviewData.fromTime DatePickerDialog( - this, R.style.MaterialPickerTheme, + this, dateSetListener, cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), @@ -188,18 +178,19 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() { } val dm = DisplayMetrics() + @Suppress("DEPRECATION") if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) display?.getRealMetrics(dm) else - @Suppress("DEPRECATION") windowManager.defaultDisplay.getMetrics(dm) + windowManager.defaultDisplay.getMetrics(dm) axisWidth = if (dm.densityDpi <= 120) 3 else if (dm.densityDpi <= 160) 10 else if (dm.densityDpi <= 320) 35 else if (dm.densityDpi <= 420) 50 else if (dm.densityDpi <= 560) 70 else 80 - binding.bgGraph.gridLabelRenderer?.gridColor = rh.gc(R.color.graphgrid) + binding.bgGraph.gridLabelRenderer?.gridColor = rh.gac(this, R.attr.graphgrid) binding.bgGraph.gridLabelRenderer?.reloadStyles() binding.bgGraph.gridLabelRenderer?.labelVerticalWidth = axisWidth - overviewMenus.setupChartMenu(binding.chartMenuButton) + overviewMenus.setupChartMenu(context, binding.chartMenuButton) prepareGraphsIfNeeded(overviewMenus.setting.size) savedInstanceState?.let { bundle -> rangeToDisplay = bundle.getInt("rangeToDisplay", 0) @@ -211,7 +202,7 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() { override fun onPause() { super.onPause() disposable.clear() - iobCobCalculator.stopCalculation("onPause") + calculationWorkflow.stopCalculation(CalculationWorkflow.HISTORY_CALCULATION, "onPause") } @Synchronized @@ -233,22 +224,11 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() { disposable += rxBus .toObservable(EventIobCalculationProgress::class.java) .observeOn(aapsSchedulers.main) - .subscribe({ - if (it.cause is EventCustomCalculationFinished) - binding.overviewIobcalculationprogess.text = it.progress - }, fabricPrivacy::logException) + .subscribe({ updateCalcProgress(it.pass.finalPercent(it.progressPct)) }, fabricPrivacy::logException) disposable += rxBus - .toObservable(EventRefreshOverview::class.java) + .toObservable(EventUpdateOverviewGraph::class.java) .observeOn(aapsSchedulers.main) .subscribe({ updateGUI("EventRefreshOverview") }, fabricPrivacy::logException) - disposable += rxBus - .toObservable(EventBucketedDataCreated::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ - overviewData.prepareBucketedData("EventBucketedDataCreated") - overviewData.prepareBgData("EventBucketedDataCreated") - rxBus.send(EventRefreshOverview("EventBucketedDataCreated")) - }, fabricPrivacy::logException) if (overviewData.fromTime == 0L) { // set start of current day @@ -279,12 +259,12 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() { val graph = GraphView(this) graph.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, rh.dpToPx(100)).also { it.setMargins(0, rh.dpToPx(15), 0, rh.dpToPx(10)) } - graph.gridLabelRenderer?.gridColor = rh.gc(R.color.graphgrid) + graph.gridLabelRenderer?.gridColor = rh.gac(R.attr.graphgrid) graph.gridLabelRenderer?.reloadStyles() graph.gridLabelRenderer?.isHorizontalLabelsVisible = false graph.gridLabelRenderer?.labelVerticalWidth = axisWidth graph.gridLabelRenderer?.numVerticalLabels = 3 - graph.viewport.backgroundColor = Color.argb(20, 255, 255, 255) // 8% of gray + graph.viewport.backgroundColor = rh.gac(this, R.attr.viewPortbackgroundColor) relativeLayout.addView(graph) val label = TextView(this) @@ -304,17 +284,7 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() { @Suppress("SameParameterValue") private fun loadAll(from: String) { updateDate() - Thread { - overviewData.prepareBgData("$from") - overviewData.prepareTreatmentsData(from) - rxBus.send(EventRefreshOverview("loadAll_$from")) - overviewData.prepareTemporaryTargetData(from) - rxBus.send(EventRefreshOverview("loadAll_$from")) - overviewData.prepareBasalData(from) - rxBus.send(EventRefreshOverview(from)) - aapsLogger.debug(LTag.UI, "loadAll $from finished") - runCalculation(from) - }.start() + runCalculation(from) } private fun setTime(start: Long) { @@ -335,25 +305,32 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() { } private fun runCalculation(from: String) { - Thread { - iobCobCalculator.stopCalculation(from) - iobCobCalculator.stopCalculationTrigger = false - iobCobCalculator.runCalculation(from, overviewData.toTime, bgDataReload = true, limitDataToOldestAvailable = false, cause = EventCustomCalculationFinished()) - }.start() + calculationWorkflow.runCalculation( + CalculationWorkflow.HISTORY_CALCULATION, + iobCobCalculator, + overviewData, + from, + overviewData.toTime, + bgDataReload = true, + limitDataToOldestAvailable = false, + cause = EventCustomCalculationFinished(), + runLoop = false + ) } @Volatile var runningRefresh = false + + @Suppress("SameParameterValue") private fun refreshLoop(from: String) { if (runningRefresh) return runningRefresh = true - overviewData.prepareIobAutosensData(from) rxBus.send(EventRefreshOverview(from)) aapsLogger.debug(LTag.UI, "refreshLoop finished") runningRefresh = false } - fun updateDate() { + private fun updateDate() { binding.date.text = dateUtil.dateAndTimeString(overviewData.fromTime) binding.zoom.text = rangeToDisplay.toString() } @@ -372,6 +349,8 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() { graphData.addBgReadings(menuChartSettings[0][OverviewMenus.CharType.PRE.ordinal]) if (buildHelper.isDev()) graphData.addBucketedData() graphData.addTreatments() + if (menuChartSettings[0][OverviewMenus.CharType.TREAT.ordinal]) + graphData.addTherapyEvents() if (menuChartSettings[0][OverviewMenus.CharType.ACT.ordinal]) graphData.addActivity(0.8) if (pump.pumpDescription.isTempBasalCapable && menuChartSettings[0][OverviewMenus.CharType.BAS.ordinal]) @@ -400,12 +379,12 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() { var useDSForScale = false var useBGIForScale = false when { - menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] -> useABSForScale = true - menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal] -> useIobForScale = true - menuChartSettings[g + 1][OverviewMenus.CharType.COB.ordinal] -> useCobForScale = true - menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] -> useDevForScale = true - menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] -> useBGIForScale = true - menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal] -> useRatioForScale = true + menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] -> useABSForScale = true + menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal] -> useIobForScale = true + menuChartSettings[g + 1][OverviewMenus.CharType.COB.ordinal] -> useCobForScale = true + menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] -> useDevForScale = true + menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] -> useBGIForScale = true + menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal] -> useRatioForScale = true menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] -> useDSForScale = true } val alignDevBgiScale = menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] @@ -437,4 +416,9 @@ class HistoryBrowseActivity : NoSplashAppCompatActivity() { secondaryGraphsData[g].performUpdate() } } + + private fun updateCalcProgress(percent: Int) { + binding.progressBar.progress = percent + binding.progressBar.visibility = (percent != 100).toVisibilityKeepSpace() + } } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/activities/MyPreferenceFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/MyPreferenceFragment.kt index df6b007f5c..f4a679a9b0 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/MyPreferenceFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/MyPreferenceFragment.kt @@ -8,17 +8,17 @@ import android.os.Bundle import androidx.annotation.XmlRes import androidx.preference.* import dagger.android.support.AndroidSupportInjection -import info.nightscout.androidaps.interfaces.Config import info.nightscout.androidaps.R import info.nightscout.androidaps.danaRKorean.DanaRKoreanPlugin import info.nightscout.androidaps.danaRv2.DanaRv2Plugin import info.nightscout.androidaps.danar.DanaRPlugin import info.nightscout.androidaps.danars.DanaRSPlugin import info.nightscout.androidaps.diaconn.DiaconnG8Plugin -import info.nightscout.androidaps.interfaces.Profile import info.nightscout.androidaps.events.EventPreferenceChange import info.nightscout.androidaps.events.EventRebuildTabs +import info.nightscout.androidaps.interfaces.Config import info.nightscout.androidaps.interfaces.PluginBase +import info.nightscout.androidaps.interfaces.Profile import info.nightscout.androidaps.interfaces.ProfileFunction import info.nightscout.androidaps.plugin.general.openhumans.OpenHumansUploader import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin @@ -44,11 +44,11 @@ import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref1Plugin import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin import info.nightscout.androidaps.plugins.source.* -import info.nightscout.shared.SafeParse import info.nightscout.androidaps.utils.alertDialogs.OKDialog.show import info.nightscout.androidaps.utils.protection.PasswordCheck -import info.nightscout.androidaps.utils.protection.ProtectionCheck +import info.nightscout.androidaps.utils.protection.ProtectionCheck.ProtectionType.* import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.shared.SafeParse import info.nightscout.shared.sharedPreferences.SP import javax.inject.Inject @@ -236,7 +236,7 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang rh.gs(R.string.key_application_protection) == key || rh.gs(R.string.key_bolus_protection) == key) && sp.getString(R.string.key_master_password, "") == "" && - sp.getInt(key, ProtectionCheck.ProtectionType.NONE.ordinal) == ProtectionCheck.ProtectionType.BIOMETRIC.ordinal + sp.getInt(key, NONE.ordinal) == BIOMETRIC.ordinal ) { activity?.let { val title = rh.gs(R.string.unsecure_fallback_biometric) @@ -246,9 +246,9 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang } // Master password erased with activated Biometric protection - val isBiometricActivated = sp.getInt(R.string.key_settings_protection, ProtectionCheck.ProtectionType.NONE.ordinal) == ProtectionCheck.ProtectionType.BIOMETRIC.ordinal || - sp.getInt(R.string.key_application_protection, ProtectionCheck.ProtectionType.NONE.ordinal) == ProtectionCheck.ProtectionType.BIOMETRIC.ordinal || - sp.getInt(R.string.key_bolus_protection, ProtectionCheck.ProtectionType.NONE.ordinal) == ProtectionCheck.ProtectionType.BIOMETRIC.ordinal + val isBiometricActivated = sp.getInt(R.string.key_settings_protection, NONE.ordinal) == BIOMETRIC.ordinal || + sp.getInt(R.string.key_application_protection, NONE.ordinal) == BIOMETRIC.ordinal || + sp.getInt(R.string.key_bolus_protection, NONE.ordinal) == BIOMETRIC.ordinal if (rh.gs(R.string.key_master_password) == key && sp.getString(key, "") == "" && isBiometricActivated) { activity?.let { val title = rh.gs(R.string.unsecure_fallback_biometric) @@ -319,26 +319,35 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang if (pref is ListPreference) { pref.setSummary(pref.entry) // Preferences - // Preferences if (pref.getKey() == rh.gs(R.string.key_settings_protection)) { val pass: Preference? = findPreference(rh.gs(R.string.key_settings_password)) - if (pass != null) pass.isEnabled = pref.value == ProtectionCheck.ProtectionType.CUSTOM_PASSWORD.ordinal.toString() + val usePassword = pref.value == CUSTOM_PASSWORD.ordinal.toString() + pass?.let { it.isVisible = usePassword } + val pin: Preference? = findPreference(rh.gs(R.string.key_settings_pin)) + val usePin = pref.value == CUSTOM_PIN.ordinal.toString() + pin?.let { it.isVisible = usePin } } // Application - // Application if (pref.getKey() == rh.gs(R.string.key_application_protection)) { val pass: Preference? = findPreference(rh.gs(R.string.key_application_password)) - if (pass != null) pass.isEnabled = pref.value == ProtectionCheck.ProtectionType.CUSTOM_PASSWORD.ordinal.toString() + val usePassword = pref.value == CUSTOM_PASSWORD.ordinal.toString() + pass?.let { it.isVisible = usePassword } + val pin: Preference? = findPreference(rh.gs(R.string.key_application_pin)) + val usePin = pref.value == CUSTOM_PIN.ordinal.toString() + pin?.let { it.isVisible = usePin } } // Bolus - // Bolus if (pref.getKey() == rh.gs(R.string.key_bolus_protection)) { val pass: Preference? = findPreference(rh.gs(R.string.key_bolus_password)) - if (pass != null) pass.isEnabled = pref.value == ProtectionCheck.ProtectionType.CUSTOM_PASSWORD.ordinal.toString() + val usePassword = pref.value == CUSTOM_PASSWORD.ordinal.toString() + pass?.let { it.isVisible = usePassword } + val pin: Preference? = findPreference(rh.gs(R.string.key_bolus_pin)) + val usePin = pref.value == CUSTOM_PIN.ordinal.toString() + pin?.let { it.isVisible = usePin } } } if (pref is EditTextPreference) { - if (pref.getKey().contains("password") || pref.getKey().contains("secret")) { + if (pref.getKey().contains("password") || pref.getKey().contains("pin") || pref.getKey().contains("secret")) { pref.setSummary("******") } else if (pref.text != null) { pref.dialogMessage = pref.dialogMessage @@ -354,7 +363,10 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang rh.gs(R.string.key_bolus_password), rh.gs(R.string.key_master_password), rh.gs(R.string.key_application_password), - rh.gs(R.string.key_settings_password) + rh.gs(R.string.key_settings_password), + rh.gs(R.string.key_bolus_pin), + rh.gs(R.string.key_application_pin), + rh.gs(R.string.key_settings_pin) ) if (pref is Preference) { @@ -362,7 +374,11 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang if (sp.getString(pref.key, "").startsWith("hmac:")) { pref.summary = "******" } else { - pref.summary = rh.gs(R.string.password_not_set) + if (pref.key.contains("pin")) { + pref.summary = rh.gs(R.string.pin_not_set) + }else { + pref.summary = rh.gs(R.string.password_not_set) + } } } } @@ -408,6 +424,18 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang passwordCheck.setPassword(context, R.string.application_password, R.string.key_application_password) return true } + if (preference.key == rh.gs(R.string.key_settings_pin)) { + passwordCheck.setPassword(context, R.string.settings_pin, R.string.key_settings_pin, pinInput = true) + return true + } + if (preference.key == rh.gs(R.string.key_bolus_pin)) { + passwordCheck.setPassword(context, R.string.bolus_pin, R.string.key_bolus_pin, pinInput = true) + return true + } + if (preference.key == rh.gs(R.string.key_application_pin)) { + passwordCheck.setPassword(context, R.string.application_pin, R.string.key_application_pin, pinInput = true) + return true + } // NSClient copy settings if (preference.key == rh.gs(R.string.key_statuslights_copy_ns)) { nsSettingStatus.copyStatusLightsNsSettings(context) @@ -421,4 +449,4 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang this.filter = filter preferenceManager?.preferenceScreen?.let { updateFilterVisibility(filter, it) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/info/nightscout/androidaps/activities/PreferencesActivity.kt b/app/src/main/java/info/nightscout/androidaps/activities/PreferencesActivity.kt index edff599122..60f0f5006a 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/PreferencesActivity.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/PreferencesActivity.kt @@ -4,6 +4,7 @@ import android.content.Context import android.os.Bundle import android.text.Editable import android.text.TextWatcher +import android.view.MenuItem import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceScreen import info.nightscout.androidaps.R @@ -19,6 +20,7 @@ class PreferencesActivity : NoSplashAppCompatActivity(), PreferenceFragmentCompa override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + setTheme(R.style.AppTheme) binding = ActivityPreferencesBinding.inflate(layoutInflater) setContentView(binding.root) @@ -61,4 +63,14 @@ class PreferencesActivity : NoSplashAppCompatActivity(), PreferenceFragmentCompa private fun filterPreferences() { myPreferenceFragment?.setFilter(binding.prefFilter.text.toString()) } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + android.R.id.home -> { + onBackPressed() + return true + } + } + return super.onOptionsItemSelected(item) + } } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/activities/ProfileHelperActivity.kt b/app/src/main/java/info/nightscout/androidaps/activities/ProfileHelperActivity.kt index 8de1ff39d1..36a548d0f2 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/ProfileHelperActivity.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/ProfileHelperActivity.kt @@ -7,6 +7,7 @@ import android.text.Editable import android.text.TextWatcher import android.view.Menu import android.widget.PopupMenu +import android.widget.TextView import info.nightscout.androidaps.R import info.nightscout.androidaps.data.ProfileSealed import info.nightscout.androidaps.data.PureProfile @@ -37,7 +38,6 @@ class ProfileHelperActivity : NoSplashAppCompatActivity() { @Inject lateinit var defaultProfile: DefaultProfile @Inject lateinit var defaultProfileDPV: DefaultProfileDPV @Inject lateinit var localProfilePlugin: LocalProfilePlugin - @Inject lateinit var rxBus: RxBus @Inject lateinit var dateUtil: DateUtil @Inject lateinit var activePlugin: ActivePlugin @Inject lateinit var repository: AppRepository @@ -66,9 +66,9 @@ class ProfileHelperActivity : NoSplashAppCompatActivity() { private lateinit var binding: ActivityProfilehelperBinding + @SuppressLint("SetTextI18n") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - binding = ActivityProfilehelperBinding.inflate(layoutInflater) setContentView(binding.root) @@ -79,11 +79,11 @@ class ProfileHelperActivity : NoSplashAppCompatActivity() { switchTab(1, typeSelected[1]) } - binding.profiletype.setOnClickListener { - PopupMenu(this, binding.profiletype).apply { + binding.profileType.setOnClickListener { + PopupMenu(this, binding.profileType).apply { menuInflater.inflate(R.menu.menu_profilehelper, menu) setOnMenuItemClickListener { item -> - binding.profiletype.setText(item.title) + binding.profileType.setText(item.title) when (item.itemId) { R.id.menu_default -> switchTab(tabSelected, ProfileType.MOTOL_DEFAULT) R.id.menu_default_dpv -> switchTab(tabSelected, ProfileType.DPV_DEFAULT) @@ -130,7 +130,7 @@ class ProfileHelperActivity : NoSplashAppCompatActivity() { } // Default profile - binding.copytolocalprofile.setOnClickListener { + binding.copyToLocalProfile.setOnClickListener { storeValues() val age = ageUsed[tabSelected] val weight = weightUsed[tabSelected] @@ -168,20 +168,22 @@ class ProfileHelperActivity : NoSplashAppCompatActivity() { } }) - binding.basalpctfromtdd.setParams(32.0, 32.0, 37.0, 1.0, DecimalFormat("0"), false, null) + binding.basalPctFromTdd.setParams(32.0, 32.0, 37.0, 1.0, DecimalFormat("0"), false, null) - @SuppressLint("SetTextI18n") - binding.tdds.text = getString(R.string.tdd) + ": " + rh.gs(R.string.calculation_in_progress) + binding.tdds.addView(TextView(this).apply { text = rh.gs(R.string.tdd) + ": " + rh.gs(R.string.calculation_in_progress) }) Thread { - val tdds = tddCalculator.stats() - runOnUiThread { binding.tdds.text = tdds } + val tdds = tddCalculator.stats(this) + runOnUiThread { + binding.tdds.removeAllViews() + binding.tdds.addView(tdds) + } }.start() // Current profile binding.currentProfileText.text = profileFunction.getProfileName() // General - binding.compareprofile.setOnClickListener { + binding.compareProfiles.setOnClickListener { storeValues() for (i in 0..1) { if (typeSelected[i] == ProfileType.MOTOL_DEFAULT) { @@ -239,10 +241,10 @@ class ProfileHelperActivity : NoSplashAppCompatActivity() { } ToastUtils.showToastInUiThread(this, R.string.invalidinput) } - binding.age.editText?.id?.let { binding.ageLabel.labelFor = it } - binding.tdd.editText?.id?.let { binding.tddLabel.labelFor = it } - binding.weight.editText?.id?.let { binding.weightLabel.labelFor = it } - binding.basalpctfromtdd.editText?.id?.let { binding.basalpctfromtddLabel.labelFor = it } + 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) } @@ -273,7 +275,7 @@ class ProfileHelperActivity : NoSplashAppCompatActivity() { ageUsed[tabSelected] = binding.age.value weightUsed[tabSelected] = binding.weight.value tddUsed[tabSelected] = binding.tdd.value - pctUsed[tabSelected] = binding.basalpctfromtdd.value + pctUsed[tabSelected] = binding.basalPctFromTdd.value } private fun switchTab(tab: Int, newContent: ProfileType, storeOld: Boolean = true) { @@ -283,10 +285,10 @@ class ProfileHelperActivity : NoSplashAppCompatActivity() { tabSelected = tab typeSelected[tabSelected] = newContent - binding.profiletypeTitle.defaultHintTextColor = ColorStateList.valueOf(rh.gc(if (tab == 0) R.color.helperProfile else R.color.examinedProfile)) + binding.profileTypeTitle.defaultHintTextColor = ColorStateList.valueOf(rh.gac( this, if (tab == 0) R.attr.helperProfileColor else R.attr.examinedProfileColor)) // show new content - binding.profiletype.setText( + binding.profileType.setText( when (typeSelected[tabSelected]) { ProfileType.MOTOL_DEFAULT -> rh.gs(R.string.motoldefaultprofile) ProfileType.DPV_DEFAULT -> rh.gs(R.string.dpvdefaultprofile) @@ -304,9 +306,9 @@ class ProfileHelperActivity : NoSplashAppCompatActivity() { binding.age.value = ageUsed[tabSelected] binding.weight.value = weightUsed[tabSelected] binding.tdd.value = tddUsed[tabSelected] - binding.basalpctfromtdd.value = pctUsed[tabSelected] + binding.basalPctFromTdd.value = pctUsed[tabSelected] - binding.basalpctfromtddRow.visibility = (newContent == ProfileType.DPV_DEFAULT).toVisibility() + binding.basalPctFromTddRow.visibility = (newContent == ProfileType.DPV_DEFAULT).toVisibility() if (profileList.isNotEmpty()) binding.availableProfileList.setText(profileList[profileUsed[tabSelected]].toString()) if (profileSwitch.isNotEmpty()) @@ -314,7 +316,7 @@ class ProfileHelperActivity : NoSplashAppCompatActivity() { } private fun setBackgroundColorOnSelected(tab: Int) { - binding.menu1.setBackgroundColor(rh.gc(if (tab == 1) R.color.defaultbackground else R.color.helperProfile)) - binding.menu2.setBackgroundColor(rh.gc(if (tab == 0) R.color.defaultbackground else R.color.examinedProfile)) + binding.menu1.setBackgroundColor(rh.gac(this, if (tab == 1) R.attr.defaultbackground else R.attr.helperProfileColor)) + binding.menu2.setBackgroundColor(rh.gac(this, if (tab == 0) R.attr.defaultbackground else R.attr.examinedProfileColor)) } } diff --git a/app/src/main/java/info/nightscout/androidaps/activities/StatsActivity.kt b/app/src/main/java/info/nightscout/androidaps/activities/StatsActivity.kt index 1ad4bb826f..39842724cb 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/StatsActivity.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/StatsActivity.kt @@ -2,6 +2,7 @@ package info.nightscout.androidaps.activities import android.annotation.SuppressLint import android.os.Bundle +import android.widget.TextView import info.nightscout.androidaps.R import info.nightscout.androidaps.database.entities.UserEntry.Action import info.nightscout.androidaps.database.entities.UserEntry.Sources @@ -9,7 +10,6 @@ import info.nightscout.androidaps.databinding.ActivityStatsBinding import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.utils.ActivityMonitor import info.nightscout.androidaps.utils.alertDialogs.OKDialog -import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.stats.TddCalculator import info.nightscout.androidaps.utils.stats.TirCalculator import javax.inject.Inject @@ -29,21 +29,30 @@ class StatsActivity : NoSplashAppCompatActivity() { binding = ActivityStatsBinding.inflate(layoutInflater) setContentView(binding.root) - binding.tdds.text = getString(R.string.tdd) + ": " + rh.gs(R.string.calculation_in_progress) - binding.tir.text = getString(R.string.tir) + ": " + rh.gs(R.string.calculation_in_progress) - binding.activity.text = rh.gs(R.string.activitymonitor) + ": " + rh.gs(R.string.calculation_in_progress) + binding.tdds.addView(TextView(this).apply { text = getString(R.string.tdd) + ": " + rh.gs(R.string.calculation_in_progress) }) + binding.tir.addView(TextView(this).apply { text = getString(R.string.tir) + ": " + rh.gs(R.string.calculation_in_progress) }) + binding.activity.addView(TextView(this).apply { text = getString(R.string.activitymonitor) + ": " + rh.gs(R.string.calculation_in_progress) }) Thread { - val tdds = tddCalculator.stats() - runOnUiThread { binding.tdds.text = tdds } + val tdds = tddCalculator.stats(this) + runOnUiThread { + binding.tdds.removeAllViews() + binding.tdds.addView(tdds) + } }.start() Thread { - val tir = tirCalculator.stats() - runOnUiThread { binding.tir.text = tir } + val tir = tirCalculator.stats(this) + runOnUiThread { + binding.tir.removeAllViews() + binding.tir.addView(tir) + } }.start() Thread { - val activity = activityMonitor.stats() - runOnUiThread { binding.activity.text = activity } + val activity = activityMonitor.stats(this) + runOnUiThread { + binding.activity.removeAllViews() + binding.activity.addView(activity) + } }.start() binding.ok.setOnClickListener { finish() } diff --git a/app/src/main/java/info/nightscout/androidaps/activities/SurveyActivity.kt b/app/src/main/java/info/nightscout/androidaps/activities/SurveyActivity.kt index a147e26ea2..bd59340c7c 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/SurveyActivity.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/SurveyActivity.kt @@ -43,9 +43,9 @@ class SurveyActivity : NoSplashAppCompatActivity() { val profileList = profileStore?.getProfileList() ?: return binding.spinner.adapter = ArrayAdapter(this, R.layout.spinner_centered, profileList) - binding.tdds.text = tddCalculator.stats() - binding.tir.text = tirCalculator.stats() - binding.activity.text = activityMonitor.stats() + //binding.tdds.text = tddCalculator.stats() + //binding.tir.text = tirCalculator.stats() + //binding.activity.text = activityMonitor.stats() binding.profile.setOnClickListener { val age = SafeParse.stringToDouble(binding.age.text.toString()) @@ -104,7 +104,7 @@ class SurveyActivity : NoSplashAppCompatActivity() { .addOnCompleteListener(this) { task -> if (task.isSuccessful) { aapsLogger.debug(LTag.CORE, "signInAnonymously:success") - //val user = auth.currentUser // TODO: do we need this, seems unused? + //val user = auth.currentUser // do we need this, seems unused? val database = FirebaseDatabase.getInstance().reference database.child("survey").child(r.id).setValue(r) diff --git a/app/src/main/java/info/nightscout/androidaps/activities/TreatmentsActivity.kt b/app/src/main/java/info/nightscout/androidaps/activities/TreatmentsActivity.kt index 5290b48711..3b88ff8770 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/TreatmentsActivity.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/TreatmentsActivity.kt @@ -2,12 +2,14 @@ package info.nightscout.androidaps.activities import android.os.Bundle import android.view.MenuItem -import android.view.View import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentTransaction +import com.google.android.material.tabs.TabLayout +import com.google.android.material.tabs.TabLayout.OnTabSelectedListener import info.nightscout.androidaps.R import info.nightscout.androidaps.activities.fragments.* import info.nightscout.androidaps.databinding.TreatmentsFragmentBinding +import info.nightscout.androidaps.extensions.toVisibility import info.nightscout.androidaps.interfaces.ActivePlugin import info.nightscout.androidaps.utils.buildHelper.BuildHelper import javax.inject.Inject @@ -23,49 +25,34 @@ class TreatmentsActivity : NoSplashAppCompatActivity() { super.onCreate(savedInstanceState) binding = TreatmentsFragmentBinding.inflate(layoutInflater) setContentView(binding.root) - //binding.tempBasals.visibility = buildHelper.isEngineeringMode().toVisibility() - //binding.extendedBoluses.visibility = (buildHelper.isEngineeringMode() && !activePlugin.activePump.isFakingTempsByExtendedBoluses).toVisibility() + + // Use index, TabItems crashes with an id + val useFakeTempBasal = activePlugin.activePump.isFakingTempsByExtendedBoluses + binding.treatmentsTabs.getTabAt(1)?.view?.visibility = useFakeTempBasal.toVisibility() - binding.treatments.setOnClickListener { - setFragment(TreatmentsBolusCarbsFragment()) - setBackgroundColorOnSelected(it) - supportActionBar?.title = rh.gs(R.string.carbs_and_bolus) - } - binding.extendedBoluses.setOnClickListener { - setFragment(TreatmentsExtendedBolusesFragment()) - setBackgroundColorOnSelected(it) - supportActionBar?.title = rh.gs(R.string.extended_bolus) - } - binding.tempBasals.setOnClickListener { - setFragment(TreatmentsTemporaryBasalsFragment()) - setBackgroundColorOnSelected(it) - supportActionBar?.title = rh.gs(R.string.tempbasal_label) - } - binding.tempTargets.setOnClickListener { - setFragment(TreatmentsTempTargetFragment()) - setBackgroundColorOnSelected(it) - supportActionBar?.title = rh.gs(R.string.tempt_targets) - } - binding.profileSwitches.setOnClickListener { - setFragment(TreatmentsProfileSwitchFragment()) - setBackgroundColorOnSelected(it) - supportActionBar?.title = rh.gs(R.string.profile_changes) - } - binding.careportal.setOnClickListener { - setFragment(TreatmentsCareportalFragment()) - setBackgroundColorOnSelected(it) - supportActionBar?.title = rh.gs(R.string.careportal) - } - binding.userentry.setOnClickListener { - setFragment(TreatmentsUserEntryFragment()) - setBackgroundColorOnSelected(it) - supportActionBar?.title = rh.gs(R.string.user_action) - } 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 { @@ -86,15 +73,4 @@ class TreatmentsActivity : NoSplashAppCompatActivity() { .commit() } - private fun setBackgroundColorOnSelected(selected: View) { - binding.treatments.setBackgroundColor(rh.gc(R.color.defaultbackground)) - binding.extendedBoluses.setBackgroundColor(rh.gc(R.color.defaultbackground)) - binding.tempBasals.setBackgroundColor(rh.gc(R.color.defaultbackground)) - binding.tempTargets.setBackgroundColor(rh.gc(R.color.defaultbackground)) - binding.profileSwitches.setBackgroundColor(rh.gc(R.color.defaultbackground)) - binding.careportal.setBackgroundColor(rh.gc(R.color.defaultbackground)) - binding.userentry.setBackgroundColor(rh.gc(R.color.defaultbackground)) - selected.setBackgroundColor(rh.gc(R.color.tabBgColorSelected)) - } - } diff --git a/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsBolusCarbsFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsBolusCarbsFragment.kt index 1c569a78a6..e70e7a9cbe 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsBolusCarbsFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsBolusCarbsFragment.kt @@ -3,12 +3,8 @@ package info.nightscout.androidaps.activities.fragments import android.annotation.SuppressLint import android.graphics.Paint import android.os.Bundle -import android.util.Log import android.util.SparseArray import android.view.* -import android.widget.CompoundButton -import android.view.ActionMode -import androidx.appcompat.widget.Toolbar import androidx.core.util.forEach import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -18,6 +14,7 @@ import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.entities.Bolus import info.nightscout.androidaps.database.entities.BolusCalculatorResult import info.nightscout.androidaps.database.entities.Carbs +import info.nightscout.androidaps.database.entities.UserEntry import info.nightscout.androidaps.database.entities.UserEntry.Action import info.nightscout.androidaps.database.entities.UserEntry.Sources import info.nightscout.androidaps.database.entities.ValueWithUnit @@ -28,9 +25,7 @@ import info.nightscout.androidaps.database.transactions.InvalidateCarbsTransacti import info.nightscout.androidaps.databinding.TreatmentsBolusCarbsFragmentBinding import info.nightscout.androidaps.databinding.TreatmentsBolusCarbsItemBinding import info.nightscout.androidaps.dialogs.WizardInfoDialog -import info.nightscout.androidaps.events.EventAutosensCalculationFinished import info.nightscout.androidaps.events.EventTreatmentChange -import info.nightscout.androidaps.events.EventTreatmentUpdateGui import info.nightscout.androidaps.extensions.iobCalc import info.nightscout.androidaps.extensions.toVisibility import info.nightscout.androidaps.interfaces.ActivePlugin @@ -39,9 +34,11 @@ import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.plugins.bus.RxBus import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventNewHistoryData +import info.nightscout.androidaps.utils.ActionModeHelper import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.T +import info.nightscout.androidaps.utils.ToastUtils import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.utils.buildHelper.BuildHelper import info.nightscout.androidaps.utils.resources.ResourceHelper @@ -72,8 +69,10 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() { @Inject lateinit var activePlugin: ActivePlugin private var _binding: TreatmentsBolusCarbsFragmentBinding? = null + // This property is only valid between onCreateView and onDestroyView. private val binding get() = _binding!! + private var menu: Menu? = null class MealLink( val bolus: Bolus? = null, @@ -82,23 +81,24 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() { ) private val disposable = CompositeDisposable() + private lateinit var actionHelper: ActionModeHelper private val millsToThePast = T.days(30).msecs() - - private var selectedItems: SparseArray = SparseArray() private var showInvalidated = false - private var removeActionMode: ActionMode? = null - private var toolbar: Toolbar? = null override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = TreatmentsBolusCarbsFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root - @SuppressLint("CheckResult") + @SuppressLint("NotifyDataSetChanged") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + actionHelper = ActionModeHelper(rh, activity) + actionHelper.setUpdateListHandler { binding.recyclerview.adapter?.notifyDataSetChanged() } + actionHelper.setOnRemoveHandler { removeSelected(it) } setHasOptionsMenu(true) - toolbar = activity?.findViewById(R.id.toolbar) binding.recyclerview.setHasFixedSize(true) binding.recyclerview.layoutManager = LinearLayoutManager(view.context) + binding.recyclerview.emptyView = binding.noRecordsText + binding.recyclerview.loadingView = binding.progressBar } private fun bolusMealLinksWithInvalid(now: Long) = repository @@ -127,8 +127,8 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() { fun swapAdapter() { val now = System.currentTimeMillis() - - disposable += + binding.recyclerview.isLoading = true + disposable += if (showInvalidated) carbsMealLinksWithInvalid(now) .zipWith(bolusMealLinksWithInvalid(now)) { first, second -> first + second } @@ -169,28 +169,18 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() { .observeOn(aapsSchedulers.main) .debounce(1L, TimeUnit.SECONDS) .subscribe({ swapAdapter() }, fabricPrivacy::logException) - disposable += rxBus - .toObservable(EventTreatmentUpdateGui::class.java) // TODO join with above - .observeOn(aapsSchedulers.io) - .debounce(1L, TimeUnit.SECONDS) - .subscribe({ swapAdapter() }, fabricPrivacy::logException) - disposable += rxBus - .toObservable(EventAutosensCalculationFinished::class.java) - .observeOn(aapsSchedulers.main) - .debounce(1L, TimeUnit.SECONDS) - .subscribe({ swapAdapter() }, fabricPrivacy::logException) } @Synchronized override fun onPause() { super.onPause() + actionHelper.finish() disposable.clear() } @Synchronized override fun onDestroyView() { super.onDestroyView() - removeActionMode?.let { it.finish() } binding.recyclerview.adapter = null // avoid leaks _binding = null } @@ -206,9 +196,9 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() { val profile = profileFunction.getProfile() ?: return val ml = mealLinks[position] - val sameDayPrevious = position > 0 && dateUtil.isSameDay(timestamp(ml), timestamp(mealLinks[position - 1])) - holder.binding.date.visibility = sameDayPrevious.not().toVisibility() - holder.binding.date.text = dateUtil.dateString(timestamp(ml)) + val newDay = position == 0 || !dateUtil.isSameDayGroup(timestamp(ml), timestamp(mealLinks[position - 1])) + holder.binding.date.visibility = newDay.toVisibility() + holder.binding.date.text = if (newDay) dateUtil.dateStringRelative(timestamp(ml), rh) else "" // Metadata holder.binding.metadataLayout.visibility = (ml.bolusCalculatorResult != null && (ml.bolusCalculatorResult.isValid || showInvalidated)).toVisibility() @@ -226,7 +216,7 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() { holder.binding.bolusInvalid.visibility = bolus.isValid.not().toVisibility() val iob = bolus.iobCalc(activePlugin, System.currentTimeMillis(), profile.dia) if (iob.iobContrib > 0.01) { - holder.binding.iob.setTextColor(rh.gc(R.color.colorActive)) + holder.binding.iob.setTextColor(rh.gac(context , R.attr.activeColor)) holder.binding.iob.text = rh.gs(R.string.formatinsulinunits, iob.iobContrib) holder.binding.iobLabel.visibility = View.VISIBLE holder.binding.iob.visibility = View.VISIBLE @@ -236,13 +226,25 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() { holder.binding.iobLabel.visibility = View.GONE holder.binding.iob.visibility = View.GONE } - if (bolus.timestamp > dateUtil.now()) holder.binding.date.setTextColor(rh.gc(R.color.colorScheduled)) else holder.binding.date.setTextColor(holder.binding.carbs.currentTextColor) + if (bolus.timestamp > dateUtil.now()) holder.binding.date.setTextColor(rh.gac(context, R.attr.scheduledColor)) else holder.binding.date.setTextColor(holder.binding.carbs + .currentTextColor) holder.binding.mealOrCorrection.text = when (ml.bolus.type) { Bolus.Type.SMB -> "SMB" Bolus.Type.NORMAL -> rh.gs(R.string.mealbolus) Bolus.Type.PRIMING -> rh.gs(R.string.prime) } + holder.binding.cbBolusRemove.visibility = (ml.bolus.isValid && actionHelper.isRemoving).toVisibility() + if (actionHelper.isRemoving) { + holder.binding.cbBolusRemove.setOnCheckedChangeListener { _, value -> + actionHelper.updateSelection(position, ml, value) + } + holder.binding.root.setOnClickListener { + holder.binding.cbBolusRemove.toggle() + actionHelper.updateSelection(position, ml, holder.binding.cbBolusRemove.isChecked) + } + holder.binding.cbBolusRemove.isChecked = actionHelper.isSelected(position) + } } // Carbs holder.binding.carbsLayout.visibility = (ml.carbs != null && (ml.carbs.isValid || showInvalidated)).toVisibility() @@ -253,26 +255,22 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() { holder.binding.carbsNs.visibility = (carbs.interfaceIDs.nightscoutId != null).toVisibility() holder.binding.carbsPump.visibility = (carbs.interfaceIDs.pumpId != null).toVisibility() holder.binding.carbsInvalid.visibility = carbs.isValid.not().toVisibility() - } - holder.binding.cbBolusRemove.visibility = (ml.bolus?.isValid == true && removeActionMode != null).toVisibility() - holder.binding.cbCarbsRemove.visibility = (ml.carbs?.isValid == true && removeActionMode != null).toVisibility() - if (removeActionMode != null) { - val onChange = CompoundButton.OnCheckedChangeListener { _, value -> - if (value) { - selectedItems.put(position, ml) - } else { - selectedItems.remove(position) + holder.binding.cbCarbsRemove.visibility = (ml.carbs.isValid && actionHelper.isRemoving).toVisibility() + if (actionHelper.isRemoving) { + holder.binding.cbCarbsRemove.setOnCheckedChangeListener { _, value -> + actionHelper.updateSelection(position, ml, value) } - removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size()) + 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.cbBolusRemove.setOnCheckedChangeListener(onChange) - holder.binding.cbBolusRemove.isChecked = selectedItems.get(position) != null - holder.binding.cbCarbsRemove.setOnCheckedChangeListener(onChange) - holder.binding.cbCarbsRemove.isChecked = selectedItems.get(position) != null } + 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() + holder.binding.delimiter.visibility = dateUtil.isSameDayGroup(timestamp(ml), nextTimestamp).toVisibility() } override fun getItemCount() = mealLinks.size @@ -297,13 +295,18 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() { } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + this.menu = menu inflater.inflate(R.menu.menu_treatments_carbs_bolus, menu) super.onCreateOptionsMenu(menu, inflater) } + private fun updateMenuVisibility() { + menu?.findItem(R.id.nav_hide_invalidated)?.isVisible = showInvalidated + menu?.findItem(R.id.nav_show_invalidated)?.isVisible = !showInvalidated + } + override fun onPrepareOptionsMenu(menu: Menu) { - menu.findItem(R.id.nav_hide_invalidated)?.isVisible = showInvalidated - menu.findItem(R.id.nav_show_invalidated)?.isVisible = !showInvalidated + 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 @@ -314,20 +317,21 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() { override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) { - R.id.nav_remove_items -> { - removeActionMode = toolbar?.startActionMode(RemoveActionModeCallback()) - true - } + R.id.nav_remove_items -> actionHelper.startRemove() R.id.nav_show_invalidated -> { showInvalidated = true - rxBus.send(EventTreatmentUpdateGui()) + updateMenuVisibility() + ToastUtils.showToastInUiThread(context, rh.gs(R.string.show_invalidated_records)) + swapAdapter() true } R.id.nav_hide_invalidated -> { showInvalidated = false - rxBus.send(EventTreatmentUpdateGui()) + updateMenuVisibility() + ToastUtils.showToastInUiThread(context, rh.gs(R.string.hide_invalidated_records)) + swapAdapter() true } @@ -423,36 +427,7 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() { } } - inner class RemoveActionModeCallback : ActionMode.Callback { - - override fun onCreateActionMode(mode: ActionMode, menu: Menu?): Boolean { - mode.menuInflater.inflate(R.menu.menu_delete_selection, menu) - selectedItems.clear() - mode.title = rh.gs(R.string.count_selected, selectedItems.size()) - binding.recyclerview.adapter?.notifyDataSetChanged() - return true - } - - override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?) = false - - override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { - return when (item.itemId) { - R.id.remove_selected -> { - removeSelected() - true - } - - else -> false - } - } - - override fun onDestroyActionMode(mode: ActionMode?) { - removeActionMode = null - binding.recyclerview.adapter?.notifyDataSetChanged() - } - } - - private fun getConfirmationText(): String { + private fun getConfirmationText(selectedItems: SparseArray): String { if (selectedItems.size() == 1) { val mealLink = selectedItems.valueAt(0) val bolus = mealLink.bolus @@ -467,40 +442,38 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() { return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size()) } - fun removeSelected() { - if (selectedItems.size() > 0) - activity?.let { activity -> - OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(), Runnable { - selectedItems.forEach { _, ml -> - ml.bolus?.let { bolus -> - uel.log( - Action.BOLUS_REMOVED, Sources.Treatments, - ValueWithUnit.Timestamp(bolus.timestamp), - ValueWithUnit.Insulin(bolus.amount) + private fun removeSelected(selectedItems: SparseArray) { + 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) } ) - 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) } - ) - } } - removeActionMode?.finish() - }) - } - else - removeActionMode?.finish() + 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() + }) + } } + } diff --git a/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsCareportalFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsCareportalFragment.kt index 6e0bfcf4a7..37713a7e30 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsCareportalFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsCareportalFragment.kt @@ -3,39 +3,33 @@ package info.nightscout.androidaps.activities.fragments import android.os.Bundle import android.util.SparseArray import android.view.* -import android.view.ActionMode -import androidx.appcompat.widget.Toolbar import androidx.core.util.forEach import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import dagger.android.support.DaggerFragment import info.nightscout.androidaps.R +import info.nightscout.androidaps.activities.fragments.TreatmentsCareportalFragment.RecyclerViewAdapter.TherapyEventsViewHolder import info.nightscout.androidaps.database.AppRepository -import info.nightscout.androidaps.database.entities.ValueWithUnit import info.nightscout.androidaps.database.entities.TherapyEvent import info.nightscout.androidaps.database.entities.UserEntry.Action import info.nightscout.androidaps.database.entities.UserEntry.Sources +import info.nightscout.androidaps.database.entities.ValueWithUnit import info.nightscout.androidaps.database.transactions.InvalidateAAPSStartedTherapyEventTransaction import info.nightscout.androidaps.database.transactions.InvalidateTherapyEventTransaction import info.nightscout.androidaps.databinding.TreatmentsCareportalFragmentBinding import info.nightscout.androidaps.databinding.TreatmentsCareportalItemBinding import info.nightscout.androidaps.events.EventTherapyEventChange -import info.nightscout.shared.logging.AAPSLogger -import info.nightscout.shared.logging.LTag +import info.nightscout.androidaps.extensions.toVisibility import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.plugins.bus.RxBus import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart -import info.nightscout.androidaps.events.EventTreatmentUpdateGui -import info.nightscout.androidaps.activities.fragments.TreatmentsCareportalFragment.RecyclerViewAdapter.TherapyEventsViewHolder -import info.nightscout.androidaps.utils.DateUtil -import info.nightscout.androidaps.utils.FabricPrivacy -import info.nightscout.androidaps.utils.T -import info.nightscout.androidaps.utils.Translator +import info.nightscout.androidaps.utils.* import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.utils.buildHelper.BuildHelper -import info.nightscout.androidaps.extensions.toVisibility import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.rx.AapsSchedulers +import info.nightscout.shared.logging.AAPSLogger +import info.nightscout.shared.logging.LTag import info.nightscout.shared.sharedPreferences.SP import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.disposables.CompositeDisposable @@ -59,26 +53,28 @@ class TreatmentsCareportalFragment : DaggerFragment() { @Inject lateinit var uel: UserEntryLogger private var _binding: TreatmentsCareportalFragmentBinding? = null + // This property is only valid between onCreateView and onDestroyView. private val binding get() = _binding!! - + private var menu: Menu? = null private val disposable = CompositeDisposable() private val millsToThePast = T.days(30).msecs() - private var selectedItems: SparseArray = SparseArray() + private lateinit var actionHelper: ActionModeHelper private var showInvalidated = false - private var toolbar: Toolbar? = null - private var removeActionMode: ActionMode? = null - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = TreatmentsCareportalFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - toolbar = activity?.findViewById(R.id.toolbar) + actionHelper = ActionModeHelper(rh, activity) + actionHelper.setUpdateListHandler { binding.recyclerview.adapter?.notifyDataSetChanged() } + actionHelper.setOnRemoveHandler { removeSelected(it) } setHasOptionsMenu(true) binding.recyclerview.setHasFixedSize(true) binding.recyclerview.layoutManager = LinearLayoutManager(view.context) + binding.recyclerview.emptyView = binding.noRecordsText + binding.recyclerview.loadingView = binding.progressBar } private fun refreshFromNightscout() { @@ -111,6 +107,7 @@ class TreatmentsCareportalFragment : DaggerFragment() { fun swapAdapter() { val now = System.currentTimeMillis() + binding.recyclerview.isLoading = true disposable += if (showInvalidated) repository @@ -133,23 +130,18 @@ class TreatmentsCareportalFragment : DaggerFragment() { .observeOn(aapsSchedulers.main) .debounce(1L, TimeUnit.SECONDS) .subscribe({ swapAdapter() }, fabricPrivacy::logException) - disposable += rxBus - .toObservable(EventTreatmentUpdateGui::class.java) // TODO join with above - .observeOn(aapsSchedulers.io) - .debounce(1L, TimeUnit.SECONDS) - .subscribe({ swapAdapter() }, fabricPrivacy::logException) } @Synchronized override fun onPause() { super.onPause() + actionHelper.finish() disposable.clear() } @Synchronized override fun onDestroyView() { super.onDestroyView() - removeActionMode?.let { it.finish() } binding.recyclerview.adapter = null // avoid leaks _binding = null } @@ -165,27 +157,24 @@ class TreatmentsCareportalFragment : DaggerFragment() { val therapyEvent = therapyList[position] holder.binding.ns.visibility = (therapyEvent.interfaceIDs.nightscoutId != null).toVisibility() holder.binding.invalid.visibility = therapyEvent.isValid.not().toVisibility() - val sameDayPrevious = position > 0 && dateUtil.isSameDay(therapyEvent.timestamp, therapyList[position - 1].timestamp) - holder.binding.date.visibility = sameDayPrevious.not().toVisibility() - holder.binding.date.text = dateUtil.dateString(therapyEvent.timestamp) + val newDay = position == 0 || !dateUtil.isSameDayGroup(therapyEvent.timestamp, therapyList[position - 1].timestamp) + holder.binding.date.visibility = newDay.toVisibility() + holder.binding.date.text = if (newDay) dateUtil.dateStringRelative(therapyEvent.timestamp, rh) else "" holder.binding.time.text = dateUtil.timeString(therapyEvent.timestamp) holder.binding.duration.text = if (therapyEvent.duration == 0L) "" else dateUtil.niceTimeScalar(therapyEvent.duration, rh) holder.binding.note.text = therapyEvent.note holder.binding.type.text = translator.translate(therapyEvent.type) - holder.binding.cbRemove.visibility = (therapyEvent.isValid && removeActionMode != null).toVisibility() - if (removeActionMode != null) { - holder.binding.cbRemove.setOnCheckedChangeListener { _, value -> - if (value) { - selectedItems.put(position, therapyEvent) - } else { - selectedItems.remove(position) - } - removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size()) - } - holder.binding.cbRemove.isChecked = selectedItems.get(position) != null + holder.binding.cbRemove.visibility = (therapyEvent.isValid && actionHelper.isRemoving).toVisibility() + holder.binding.cbRemove.setOnCheckedChangeListener { _, value -> + actionHelper.updateSelection(position, therapyEvent, value) } + holder.binding.root.setOnClickListener { + holder.binding.cbRemove.toggle() + actionHelper.updateSelection(position, therapyEvent, holder.binding.cbRemove.isChecked) + } + holder.binding.cbRemove.isChecked = actionHelper.isSelected(position) val nextTimestamp = if (therapyList.size != position + 1) therapyList[position + 1].timestamp else 0L - holder.binding.delimiter.visibility = dateUtil.isSameDay(therapyEvent.timestamp, nextTimestamp).toVisibility() + holder.binding.delimiter.visibility = dateUtil.isSameDayGroup(therapyEvent.timestamp, nextTimestamp).toVisibility() } override fun getItemCount() = therapyList.size @@ -198,35 +187,41 @@ class TreatmentsCareportalFragment : DaggerFragment() { } 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) { - menu.findItem(R.id.nav_hide_invalidated)?.isVisible = showInvalidated - menu.findItem(R.id.nav_show_invalidated)?.isVisible = !showInvalidated + 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 -> { - removeActionMode = toolbar?.startActionMode(RemoveActionModeCallback()) - true - } + R.id.nav_remove_items -> actionHelper.startRemove() R.id.nav_show_invalidated -> { showInvalidated = true - rxBus.send(EventTreatmentUpdateGui()) + updateMenuVisibility() + ToastUtils.showToastInUiThread(context, rh.gs(R.string.show_invalidated_records)) + swapAdapter() true } R.id.nav_hide_invalidated -> { showInvalidated = false - rxBus.send(EventTreatmentUpdateGui()) + updateMenuVisibility() + ToastUtils.showToastInUiThread(context, rh.gs(R.string.hide_invalidated_records)) + swapAdapter() true } @@ -243,36 +238,7 @@ class TreatmentsCareportalFragment : DaggerFragment() { else -> false } - inner class RemoveActionModeCallback : ActionMode.Callback { - - override fun onCreateActionMode(mode: ActionMode, menu: Menu?): Boolean { - mode.menuInflater.inflate(R.menu.menu_delete_selection, menu) - selectedItems.clear() - mode.title = rh.gs(R.string.count_selected, selectedItems.size()) - binding.recyclerview.adapter?.notifyDataSetChanged() - return true - } - - override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?) = false - - override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { - return when (item.itemId) { - R.id.remove_selected -> { - removeSelected() - true - } - - else -> false - } - } - - override fun onDestroyActionMode(mode: ActionMode?) { - removeActionMode = null - binding.recyclerview.adapter?.notifyDataSetChanged() - } - } - - private fun getConfirmationText(): String { + private fun getConfirmationText(selectedItems: SparseArray): String { if (selectedItems.size() == 1) { val therapyEvent = selectedItems.valueAt(0) return rh.gs(R.string.eventtype) + ": " + translator.translate(therapyEvent.type) + "\n" + @@ -282,27 +248,24 @@ class TreatmentsCareportalFragment : DaggerFragment() { return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size()) } - private fun removeSelected() { - if (selectedItems.size() > 0) - activity?.let { activity -> - OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(), Runnable { - selectedItems.forEach { _, therapyEvent -> - uel.log( - Action.CAREPORTAL_REMOVED, Sources.Treatments, therapyEvent.note, - ValueWithUnit.Timestamp(therapyEvent.timestamp), - ValueWithUnit.TherapyEventType(therapyEvent.type) + private fun removeSelected(selectedItems: SparseArray) { + 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) } ) - 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) } - ) - } - removeActionMode?.finish() - }) - } - else - removeActionMode?.finish() + } + actionHelper.finish() + }) + } } } diff --git a/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsExtendedBolusesFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsExtendedBolusesFragment.kt index e1d8d4b34d..b7ffd407c8 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsExtendedBolusesFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsExtendedBolusesFragment.kt @@ -4,13 +4,12 @@ import android.annotation.SuppressLint import android.os.Bundle import android.util.SparseArray import android.view.* -import android.view.ActionMode -import androidx.appcompat.widget.Toolbar import androidx.core.util.forEach import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import dagger.android.support.DaggerFragment import info.nightscout.androidaps.R +import info.nightscout.androidaps.activities.fragments.TreatmentsExtendedBolusesFragment.RecyclerViewAdapter.ExtendedBolusesViewHolder import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.entities.ExtendedBolus import info.nightscout.androidaps.database.entities.UserEntry.Action @@ -26,20 +25,20 @@ import info.nightscout.androidaps.extensions.isInProgress import info.nightscout.androidaps.extensions.toVisibility import info.nightscout.androidaps.interfaces.ActivePlugin import info.nightscout.androidaps.interfaces.ProfileFunction -import info.nightscout.shared.logging.AAPSLogger import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.plugins.bus.RxBus -import info.nightscout.androidaps.activities.fragments.TreatmentsExtendedBolusesFragment.RecyclerViewAdapter.ExtendedBolusesViewHolder -import info.nightscout.androidaps.events.EventTreatmentUpdateGui +import info.nightscout.androidaps.utils.ActionModeHelper import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.T +import info.nightscout.androidaps.utils.ToastUtils import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.rx.AapsSchedulers +import info.nightscout.shared.logging.AAPSLogger +import info.nightscout.shared.logging.LTag import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign -import info.nightscout.shared.logging.LTag import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -61,26 +60,32 @@ class TreatmentsExtendedBolusesFragment : DaggerFragment() { @Inject lateinit var repository: AppRepository private var _binding: TreatmentsExtendedbolusFragmentBinding? = null + // This property is only valid between onCreateView and onDestroyView. private val binding get() = _binding!! - - private var selectedItems: SparseArray = SparseArray() + private var menu: Menu? = null + private lateinit var actionHelper: ActionModeHelper private var showInvalidated = false - private var removeActionMode: ActionMode? = null - private var toolbar: Toolbar? = null override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = TreatmentsExtendedbolusFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root + @SuppressLint("NotifyDataSetChanged") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + actionHelper = ActionModeHelper(rh, activity) + actionHelper.setUpdateListHandler { binding.recyclerview.adapter?.notifyDataSetChanged() } + actionHelper.setOnRemoveHandler { removeSelected(it) } setHasOptionsMenu(true) - toolbar = activity?.findViewById(R.id.toolbar) binding.recyclerview.setHasFixedSize(true) binding.recyclerview.layoutManager = LinearLayoutManager(view.context) + binding.recyclerview.emptyView = binding.noRecordsText + binding.recyclerview.loadingView = binding.progressBar } fun swapAdapter() { val now = System.currentTimeMillis() + binding.recyclerview.isLoading = true disposable += if (showInvalidated) repository .getExtendedBolusDataIncludingInvalidFromTime(now - millsToThePast, false) @@ -97,7 +102,6 @@ class TreatmentsExtendedBolusesFragment : DaggerFragment() { override fun onResume() { super.onResume() swapAdapter() - disposable += rxBus .toObservable(EventExtendedBolusChange::class.java) .observeOn(aapsSchedulers.io) @@ -108,13 +112,13 @@ class TreatmentsExtendedBolusesFragment : DaggerFragment() { @Synchronized override fun onPause() { super.onPause() + actionHelper.finish() disposable.clear() } @Synchronized override fun onDestroyView() { super.onDestroyView() - removeActionMode?.let { it.finish() } binding.recyclerview.adapter = null // avoid leaks _binding = null } @@ -131,13 +135,12 @@ class TreatmentsExtendedBolusesFragment : DaggerFragment() { holder.binding.ns.visibility = (extendedBolus.interfaceIDs.nightscoutId != null).toVisibility() holder.binding.ph.visibility = (extendedBolus.interfaceIDs.pumpId != null).toVisibility() holder.binding.invalid.visibility = extendedBolus.isValid.not().toVisibility() - val sameDayPrevious = position > 0 && dateUtil.isSameDay(extendedBolus.timestamp, extendedBolusList[position - 1].timestamp) - holder.binding.date.visibility = sameDayPrevious.not().toVisibility() - holder.binding.date.text = dateUtil.dateString(extendedBolus.timestamp) - @SuppressLint("SetTextI18n") + val newDay = position == 0 || !dateUtil.isSameDayGroup(extendedBolus.timestamp, extendedBolusList[position - 1].timestamp) + holder.binding.date.visibility = newDay.toVisibility() + holder.binding.date.text = if (newDay) dateUtil.dateStringRelative(extendedBolus.timestamp, rh) else "" if (extendedBolus.isInProgress(dateUtil)) { holder.binding.time.text = dateUtil.timeString(extendedBolus.timestamp) - holder.binding.time.setTextColor(rh.gc(R.color.colorActive)) + holder.binding.time.setTextColor(rh.gac(context , R.attr.activeColor)) } else { holder.binding.time.text = dateUtil.timeRangeString(extendedBolus.timestamp, extendedBolus.end) holder.binding.time.setTextColor(holder.binding.insulin.currentTextColor) @@ -148,21 +151,20 @@ class TreatmentsExtendedBolusesFragment : DaggerFragment() { val iob = extendedBolus.iobCalc(System.currentTimeMillis(), profile, activePlugin.activeInsulin) holder.binding.iob.text = rh.gs(R.string.formatinsulinunits, iob.iob) holder.binding.ratio.text = rh.gs(R.string.pump_basebasalrate, extendedBolus.rate) - if (iob.iob != 0.0) holder.binding.iob.setTextColor(rh.gc(R.color.colorActive)) else holder.binding.iob.setTextColor(holder.binding.insulin.currentTextColor) - holder.binding.cbRemove.visibility = (extendedBolus.isValid && removeActionMode != null).toVisibility() - if (removeActionMode != null) { + if (iob.iob != 0.0) holder.binding.iob.setTextColor(rh.gac(context , R.attr.activeColor)) else holder.binding.iob.setTextColor(holder.binding.insulin.currentTextColor) + holder.binding.cbRemove.visibility = (extendedBolus.isValid && actionHelper.isRemoving).toVisibility() + if (actionHelper.isRemoving) { holder.binding.cbRemove.setOnCheckedChangeListener { _, value -> - if (value) { - selectedItems.put(position, extendedBolus) - } else { - selectedItems.remove(position) - } - removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size()) + actionHelper.updateSelection(position, extendedBolus, value) } - holder.binding.cbRemove.isChecked = selectedItems.get(position) != null + holder.binding.root.setOnClickListener { + holder.binding.cbRemove.toggle() + actionHelper.updateSelection(position, extendedBolus, holder.binding.cbRemove.isChecked) + } + holder.binding.cbRemove.isChecked = actionHelper.isSelected(position) } val nextTimestamp = if (extendedBolusList.size != position + 1) extendedBolusList[position + 1].timestamp else 0L - holder.binding.delimiter.visibility = dateUtil.isSameDay(extendedBolus.timestamp, nextTimestamp).toVisibility() + holder.binding.delimiter.visibility = dateUtil.isSameDayGroup(extendedBolus.timestamp, nextTimestamp).toVisibility() } override fun getItemCount() = extendedBolusList.size @@ -175,33 +177,38 @@ class TreatmentsExtendedBolusesFragment : DaggerFragment() { } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + this.menu = menu inflater.inflate(R.menu.menu_treatments_extended_bolus, menu) super.onCreateOptionsMenu(menu, inflater) } - override fun onPrepareOptionsMenu(menu: Menu) { - menu.findItem(R.id.nav_hide_invalidated)?.isVisible = showInvalidated - menu.findItem(R.id.nav_show_invalidated)?.isVisible = !showInvalidated + 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 -> { - removeActionMode = toolbar?.startActionMode(RemoveActionModeCallback()) - true - } + R.id.nav_remove_items -> actionHelper.startRemove() R.id.nav_show_invalidated -> { showInvalidated = true - rxBus.send(EventTreatmentUpdateGui()) + updateMenuVisibility() + ToastUtils.showToastInUiThread(context, rh.gs(R.string.show_invalidated_records)) + swapAdapter() true } R.id.nav_hide_invalidated -> { showInvalidated = false - rxBus.send(EventTreatmentUpdateGui()) + updateMenuVisibility() + ToastUtils.showToastInUiThread(context, rh.gs(R.string.hide_invalidated_records)) + swapAdapter() true } @@ -209,36 +216,7 @@ class TreatmentsExtendedBolusesFragment : DaggerFragment() { } } - inner class RemoveActionModeCallback : ActionMode.Callback { - - override fun onCreateActionMode(mode: ActionMode, menu: Menu?): Boolean { - mode.menuInflater.inflate(R.menu.menu_delete_selection, menu) - selectedItems.clear() - mode.title = rh.gs(R.string.count_selected, selectedItems.size()) - binding.recyclerview.adapter?.notifyDataSetChanged() - return true - } - - override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?) = false - - override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { - return when (item.itemId) { - R.id.remove_selected -> { - removeSelected() - true - } - - else -> false - } - } - - override fun onDestroyActionMode(mode: ActionMode?) { - removeActionMode = null - binding.recyclerview.adapter?.notifyDataSetChanged() - } - } - - private fun getConfirmationText(): String { + private fun getConfirmationText(selectedItems: SparseArray): String { if (selectedItems.size() == 1) { val bolus = selectedItems.valueAt(0) return rh.gs(R.string.extended_bolus) + "\n" + @@ -247,27 +225,25 @@ class TreatmentsExtendedBolusesFragment : DaggerFragment() { return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size()) } - private fun removeSelected() { - if (selectedItems.size() > 0) - activity?.let { activity -> - OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(), 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) }) - } - removeActionMode?.finish() - }) - } - else - removeActionMode?.finish() + private fun removeSelected(selectedItems: SparseArray) { + 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() + }) + } } + } diff --git a/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsProfileSwitchFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsProfileSwitchFragment.kt index 08d4e20240..92f9d5f38b 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsProfileSwitchFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsProfileSwitchFragment.kt @@ -1,11 +1,10 @@ package info.nightscout.androidaps.activities.fragments +import android.annotation.SuppressLint import android.graphics.Paint import android.os.Bundle import android.util.SparseArray import android.view.* -import android.view.ActionMode -import androidx.appcompat.widget.Toolbar import androidx.core.util.forEach import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -23,7 +22,6 @@ import info.nightscout.androidaps.databinding.TreatmentsProfileswitchItemBinding import info.nightscout.androidaps.dialogs.ProfileViewerDialog import info.nightscout.androidaps.events.EventEffectiveProfileSwitchChanged import info.nightscout.androidaps.events.EventProfileSwitchChanged -import info.nightscout.androidaps.events.EventTreatmentUpdateGui import info.nightscout.androidaps.extensions.getCustomizedName import info.nightscout.androidaps.extensions.toVisibility import info.nightscout.androidaps.logging.UserEntryLogger @@ -32,9 +30,11 @@ import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientR import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventNewHistoryData import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin import info.nightscout.androidaps.plugins.profile.local.events.EventLocalProfileChanged +import info.nightscout.androidaps.utils.ActionModeHelper import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.T +import info.nightscout.androidaps.utils.ToastUtils import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.utils.buildHelper.BuildHelper import info.nightscout.androidaps.utils.resources.ResourceHelper @@ -63,26 +63,29 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() { @Inject lateinit var uel: UserEntryLogger private var _binding: TreatmentsProfileswitchFragmentBinding? = null + // This property is only valid between onCreateView and onDestroyView. private val binding get() = _binding!! - + private var menu: Menu? = null + private lateinit var actionHelper: ActionModeHelper private val disposable = CompositeDisposable() private val millsToThePast = T.days(30).msecs() - private var selectedItems: SparseArray = SparseArray() private var showInvalidated = false - private var removeActionMode: ActionMode? = null - private var toolbar: Toolbar? = null - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = TreatmentsProfileswitchFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root + @SuppressLint("NotifyDataSetChanged") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + actionHelper = ActionModeHelper(rh, activity) + actionHelper.setUpdateListHandler { binding.recyclerview.adapter?.notifyDataSetChanged() } + actionHelper.setOnRemoveHandler { removeSelected(it) } setHasOptionsMenu(true) - toolbar = activity?.findViewById(R.id.toolbar) binding.recyclerview.setHasFixedSize(true) binding.recyclerview.layoutManager = LinearLayoutManager(view.context) + binding.recyclerview.emptyView = binding.noRecordsText + binding.recyclerview.loadingView = binding.progressBar } private fun refreshFromNightscout() { @@ -127,7 +130,7 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() { fun swapAdapter() { val now = System.currentTimeMillis() - + binding.recyclerview.isLoading = true disposable += if (showInvalidated) profileSwitchWithInvalid(now) @@ -164,13 +167,13 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() { @Synchronized override fun onPause() { super.onPause() + actionHelper.finish() disposable.clear() } @Synchronized override fun onDestroyView() { super.onDestroyView() - removeActionMode?.finish() binding.recyclerview.adapter = null // avoid leaks _binding = null } @@ -184,36 +187,35 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() { val profileSwitch = profileSwitchList[position] holder.binding.ph.visibility = (profileSwitch is ProfileSealed.EPS).toVisibility() holder.binding.ns.visibility = (profileSwitch.interfaceIDs_backing?.nightscoutId != null).toVisibility() - val sameDayPrevious = position > 0 && dateUtil.isSameDay(profileSwitch.timestamp, profileSwitchList[position - 1].timestamp) - holder.binding.date.visibility = sameDayPrevious.not().toVisibility() - holder.binding.date.text = dateUtil.dateString(profileSwitch.timestamp) + val newDay = position == 0 || !dateUtil.isSameDayGroup(profileSwitch.timestamp, profileSwitchList[position - 1].timestamp) + holder.binding.date.visibility = newDay.toVisibility() + holder.binding.date.text = if (newDay) dateUtil.dateStringRelative(profileSwitch.timestamp, rh) else "" holder.binding.time.text = dateUtil.timeString(profileSwitch.timestamp) holder.binding.duration.text = rh.gs(R.string.format_mins, T.msecs(profileSwitch.duration ?: 0L).mins()) holder.binding.name.text = if (profileSwitch is ProfileSealed.PS) profileSwitch.value.getCustomizedName() else if (profileSwitch is ProfileSealed.EPS) profileSwitch.value.originalCustomizedName else "" - if (profileSwitch.isInProgress(dateUtil)) holder.binding.date.setTextColor(rh.gc(R.color.colorActive)) + if (profileSwitch.isInProgress(dateUtil)) holder.binding.date.setTextColor(rh.gac(context , R.attr.activeColor)) else holder.binding.date.setTextColor(holder.binding.duration.currentTextColor) holder.binding.clone.tag = profileSwitch holder.binding.name.tag = profileSwitch holder.binding.date.tag = profileSwitch holder.binding.invalid.visibility = profileSwitch.isValid.not().toVisibility() holder.binding.duration.visibility = (profileSwitch.duration != 0L && profileSwitch.duration != null).toVisibility() - holder.binding.cbRemove.visibility = (removeActionMode != null && profileSwitch is ProfileSealed.PS).toVisibility() - if (removeActionMode != null) { + holder.binding.cbRemove.visibility = (actionHelper.isRemoving && profileSwitch is ProfileSealed.PS).toVisibility() + if (actionHelper.isRemoving) { holder.binding.cbRemove.setOnCheckedChangeListener { _, value -> - if (value) { - selectedItems.put(position, profileSwitch) - } else { - selectedItems.remove(position) - } - removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size()) + actionHelper.updateSelection(position, profileSwitch, value) } - holder.binding.cbRemove.isChecked = selectedItems.get(position) != null + holder.binding.root.setOnClickListener { + holder.binding.cbRemove.toggle() + actionHelper.updateSelection(position, profileSwitch, holder.binding.cbRemove.isChecked) + } + holder.binding.cbRemove.isChecked = actionHelper.isSelected(position) } holder.binding.clone.visibility = (profileSwitch is ProfileSealed.PS).toVisibility() holder.binding.spacer.visibility = (profileSwitch is ProfileSealed.PS).toVisibility() val nextTimestamp = if (profileSwitchList.size != position + 1) profileSwitchList[position + 1].timestamp else 0L - holder.binding.delimiter.visibility = dateUtil.isSameDay(profileSwitch.timestamp, nextTimestamp).toVisibility() + holder.binding.delimiter.visibility = dateUtil.isSameDayGroup(profileSwitch.timestamp, nextTimestamp).toVisibility() } override fun getItemCount() = profileSwitchList.size @@ -273,13 +275,18 @@ 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) { - menu.findItem(R.id.nav_hide_invalidated)?.isVisible = showInvalidated - menu.findItem(R.id.nav_show_invalidated)?.isVisible = !showInvalidated + updateMenuVisibility() val nsUploadOnly = !sp.getBoolean(R.string.key_ns_receive_profile_switch, false) || !buildHelper.isEngineeringMode() menu.findItem(R.id.nav_refresh_ns)?.isVisible = !nsUploadOnly @@ -288,20 +295,21 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() { override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) { - R.id.nav_remove_items -> { - removeActionMode = toolbar?.startActionMode(RemoveActionModeCallback()) - true - } + R.id.nav_remove_items -> actionHelper.startRemove() R.id.nav_show_invalidated -> { showInvalidated = true - rxBus.send(EventTreatmentUpdateGui()) + updateMenuVisibility() + ToastUtils.showToastInUiThread(context, rh.gs(R.string.show_invalidated_records)) + swapAdapter() true } R.id.nav_hide_invalidated -> { showInvalidated = false - rxBus.send(EventTreatmentUpdateGui()) + updateMenuVisibility() + ToastUtils.showToastInUiThread(context, rh.gs(R.string.hide_invalidated_records)) + swapAdapter() true } @@ -313,36 +321,7 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() { else -> false } - inner class RemoveActionModeCallback : ActionMode.Callback { - - override fun onCreateActionMode(mode: ActionMode, menu: Menu?): Boolean { - mode.menuInflater.inflate(R.menu.menu_delete_selection, menu) - selectedItems.clear() - mode.title = rh.gs(R.string.count_selected, selectedItems.size()) - binding.recyclerview.adapter?.notifyDataSetChanged() - return true - } - - override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?) = false - - override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { - return when (item.itemId) { - R.id.remove_selected -> { - removeSelected() - true - } - - else -> false - } - } - - override fun onDestroyActionMode(mode: ActionMode?) { - removeActionMode = null - binding.recyclerview.adapter?.notifyDataSetChanged() - } - } - - private fun getConfirmationText(): String { + private fun getConfirmationText(selectedItems: SparseArray): 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) @@ -350,25 +329,22 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() { return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size()) } - private fun removeSelected() { - if (selectedItems.size() > 0) - activity?.let { activity -> - OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(), Runnable { - selectedItems.forEach { _, profileSwitch -> - uel.log( - Action.PROFILE_SWITCH_REMOVED, Sources.Treatments, profileSwitch.profileName, - ValueWithUnit.Timestamp(profileSwitch.timestamp) + private fun removeSelected(selectedItems: SparseArray) { + 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) } ) - 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) } - ) - } - removeActionMode?.finish() - }) - } - else - removeActionMode?.finish() + } + actionHelper.finish() + }) + } } } diff --git a/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsTempTargetFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsTempTargetFragment.kt index 777a61a932..028ae4f99a 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsTempTargetFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsTempTargetFragment.kt @@ -4,46 +4,41 @@ import android.annotation.SuppressLint import android.os.Bundle import android.util.SparseArray import android.view.* -import androidx.appcompat.widget.Toolbar import androidx.core.util.forEach import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import dagger.android.support.DaggerFragment import info.nightscout.androidaps.R +import info.nightscout.androidaps.activities.fragments.TreatmentsTempTargetFragment.RecyclerViewAdapter.TempTargetsViewHolder import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.ValueWrapper -import info.nightscout.androidaps.database.entities.ValueWithUnit import info.nightscout.androidaps.database.entities.TemporaryTarget import info.nightscout.androidaps.database.entities.UserEntry.Action import info.nightscout.androidaps.database.entities.UserEntry.Sources +import info.nightscout.androidaps.database.entities.ValueWithUnit import info.nightscout.androidaps.database.interfaces.end import info.nightscout.androidaps.database.transactions.InvalidateTemporaryTargetTransaction import info.nightscout.androidaps.databinding.TreatmentsTemptargetFragmentBinding import info.nightscout.androidaps.databinding.TreatmentsTemptargetItemBinding -import info.nightscout.androidaps.events.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.events.EventEffectiveProfileSwitchChanged import info.nightscout.androidaps.events.EventProfileSwitchChanged -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.events.EventTempTargetChange import info.nightscout.androidaps.extensions.friendlyDescription import info.nightscout.androidaps.extensions.highValueToUnitsToString import info.nightscout.androidaps.extensions.lowValueToUnitsToString import info.nightscout.androidaps.extensions.toVisibility +import info.nightscout.androidaps.interfaces.ProfileFunction +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.utils.buildHelper.BuildHelper import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.rx.AapsSchedulers +import info.nightscout.shared.logging.AAPSLogger +import info.nightscout.shared.logging.LTag import info.nightscout.shared.sharedPreferences.SP import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.disposables.CompositeDisposable @@ -68,24 +63,28 @@ class TreatmentsTempTargetFragment : DaggerFragment() { @Inject lateinit var repository: AppRepository private var _binding: TreatmentsTemptargetFragmentBinding? = null + // This property is only valid between onCreateView and onDestroyView. private val binding get() = _binding!! - + private var menu: Menu? = null + private lateinit var actionHelper: ActionModeHelper private val disposable = CompositeDisposable() private val millsToThePast = T.days(30).msecs() - private var selectedItems: SparseArray = SparseArray() private var showInvalidated = false - private var toolbar: Toolbar? = null - private var removeActionMode: ActionMode? = null override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = TreatmentsTemptargetFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root + @SuppressLint("NotifyDataSetChanged") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { binding.recyclerview.setHasFixedSize(true) - toolbar = activity?.findViewById(R.id.toolbar) + actionHelper = ActionModeHelper(rh, activity) + actionHelper.setUpdateListHandler { binding.recyclerview.adapter?.notifyDataSetChanged() } + actionHelper.setOnRemoveHandler { removeSelected(it) } setHasOptionsMenu(true) binding.recyclerview.layoutManager = LinearLayoutManager(view.context) + binding.recyclerview.emptyView = binding.noRecordsText + binding.recyclerview.loadingView = binding.progressBar } private fun refreshFromNightscout() { @@ -114,6 +113,7 @@ class TreatmentsTempTargetFragment : DaggerFragment() { fun swapAdapter() { val now = System.currentTimeMillis() + binding.recyclerview.isLoading = true disposable += if (showInvalidated) repository @@ -131,30 +131,23 @@ class TreatmentsTempTargetFragment : DaggerFragment() { override fun onResume() { super.onResume() swapAdapter() - disposable += rxBus .toObservable(EventTempTargetChange::class.java) .observeOn(aapsSchedulers.io) .debounce(1L, TimeUnit.SECONDS) .subscribe({ swapAdapter() }, fabricPrivacy::logException) - - disposable += rxBus - .toObservable(EventTreatmentUpdateGui::class.java) // TODO join with above - .observeOn(aapsSchedulers.io) - .debounce(1L, TimeUnit.SECONDS) - .subscribe({ swapAdapter() }, fabricPrivacy::logException) } @Synchronized override fun onPause() { super.onPause() + actionHelper.finish() disposable.clear() } @Synchronized override fun onDestroyView() { super.onDestroyView() - removeActionMode?.let { it.finish() } binding.recyclerview.adapter = null // avoid leaks _binding = null } @@ -173,21 +166,20 @@ class TreatmentsTempTargetFragment : DaggerFragment() { val tempTarget = tempTargetList[position] holder.binding.ns.visibility = (tempTarget.interfaceIDs.nightscoutId != null).toVisibility() holder.binding.invalid.visibility = tempTarget.isValid.not().toVisibility() - holder.binding.cbRemove.visibility = (tempTarget.isValid && removeActionMode != null).toVisibility() - if (removeActionMode != null) { + holder.binding.cbRemove.visibility = (tempTarget.isValid && actionHelper.isRemoving).toVisibility() + if (actionHelper.isRemoving) { holder.binding.cbRemove.setOnCheckedChangeListener { _, value -> - if (value) { - selectedItems.put(position, tempTarget) - } else { - selectedItems.remove(position) - } - removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size()) + actionHelper.updateSelection(position, tempTarget, value) } - holder.binding.cbRemove.isChecked = selectedItems.get(position) != null + holder.binding.root.setOnClickListener { + holder.binding.cbRemove.toggle() + actionHelper.updateSelection(position, tempTarget, holder.binding.cbRemove.isChecked) + } + holder.binding.cbRemove.isChecked = actionHelper.isSelected(position) } - val sameDayPrevious = position > 0 && dateUtil.isSameDay(tempTarget.timestamp, tempTargetList[position - 1].timestamp) - holder.binding.date.visibility = sameDayPrevious.not().toVisibility() - holder.binding.date.text = dateUtil.dateString(tempTarget.timestamp) + val newDay = position == 0 || !dateUtil.isSameDayGroup(tempTarget.timestamp, tempTargetList[position - 1].timestamp) + holder.binding.date.visibility = newDay.toVisibility() + holder.binding.date.text = if (newDay) dateUtil.dateStringRelative(tempTarget.timestamp, rh) else "" holder.binding.time.text = dateUtil.timeRangeString(tempTarget.timestamp, tempTarget.end) holder.binding.duration.text = rh.gs(R.string.format_mins, T.msecs(tempTarget.duration).mins()) holder.binding.low.text = tempTarget.lowValueToUnitsToString(units) @@ -195,13 +187,13 @@ class TreatmentsTempTargetFragment : DaggerFragment() { holder.binding.reason.text = translator.translate(tempTarget.reason) holder.binding.time.setTextColor( when { - tempTarget.id == currentlyActiveTarget?.id -> rh.gc(R.color.colorActive) - tempTarget.timestamp > dateUtil.now() -> rh.gc(R.color.colorScheduled) + tempTarget.id == currentlyActiveTarget?.id -> rh.gac(context , R.attr.activeColor) + tempTarget.timestamp > dateUtil.now() -> rh.gac(context , R.attr.scheduledColor) else -> holder.binding.reasonColon.currentTextColor } ) val nextTimestamp = if (tempTargetList.size != position + 1) tempTargetList[position + 1].timestamp else 0L - holder.binding.delimiter.visibility = dateUtil.isSameDay(tempTarget.timestamp, nextTimestamp).toVisibility() + holder.binding.delimiter.visibility = dateUtil.isSameDayGroup(tempTarget.timestamp, nextTimestamp).toVisibility() } override fun getItemCount() = tempTargetList.size @@ -213,39 +205,19 @@ class TreatmentsTempTargetFragment : DaggerFragment() { } } - private fun removeSelected() { - if (selectedItems.size() > 0) - activity?.let { activity -> - OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(), 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) }) - } - removeActionMode?.finish() - }) - } - else - removeActionMode?.finish() - } - 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) { - menu.findItem(R.id.nav_hide_invalidated)?.isVisible = showInvalidated - menu.findItem(R.id.nav_show_invalidated)?.isVisible = !showInvalidated + updateMenuVisibility() val nsUploadOnly = !sp.getBoolean(R.string.key_ns_receive_temp_target, false) || !buildHelper.isEngineeringMode() menu.findItem(R.id.nav_refresh_ns)?.isVisible = !nsUploadOnly @@ -254,20 +226,21 @@ class TreatmentsTempTargetFragment : DaggerFragment() { override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) { - R.id.nav_remove_items -> { - removeActionMode = toolbar?.startActionMode(RemoveActionModeCallback()) - true - } + R.id.nav_remove_items -> actionHelper.startRemove() R.id.nav_show_invalidated -> { showInvalidated = true - rxBus.send(EventTreatmentUpdateGui()) + updateMenuVisibility() + ToastUtils.showToastInUiThread(context, rh.gs(R.string.show_invalidated_records)) + swapAdapter() true } R.id.nav_hide_invalidated -> { showInvalidated = false - rxBus.send(EventTreatmentUpdateGui()) + updateMenuVisibility() + ToastUtils.showToastInUiThread(context, rh.gs(R.string.show_invalidated_records)) + swapAdapter() true } @@ -279,36 +252,7 @@ class TreatmentsTempTargetFragment : DaggerFragment() { else -> false } - inner class RemoveActionModeCallback : ActionMode.Callback { - - override fun onCreateActionMode(mode: ActionMode, menu: Menu?): Boolean { - mode.menuInflater.inflate(R.menu.menu_delete_selection, menu) - selectedItems.clear() - mode.title = rh.gs(R.string.count_selected, selectedItems.size()) - binding.recyclerview.adapter?.notifyDataSetChanged() - return true - } - - override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?) = false - - override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { - return when (item.itemId) { - R.id.remove_selected -> { - removeSelected() - true - } - - else -> false - } - } - - override fun onDestroyActionMode(mode: ActionMode?) { - removeActionMode = null - binding.recyclerview.adapter?.notifyDataSetChanged() - } - } - - private fun getConfirmationText(): String { + private fun getConfirmationText(selectedItems: SparseArray): String { if (selectedItems.size() == 1) { val tempTarget = selectedItems.valueAt(0) return "${rh.gs(R.string.careportal_temporarytarget)}: ${tempTarget.friendlyDescription(profileFunction.getUnits(), rh)}\n" + @@ -317,4 +261,26 @@ class TreatmentsTempTargetFragment : DaggerFragment() { return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size()) } + private fun removeSelected(selectedItems: SparseArray) { + activity?.let { activity -> + OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(selectedItems), Runnable { + selectedItems.forEach { _, 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() + }) + } + } + } diff --git a/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsTemporaryBasalsFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsTemporaryBasalsFragment.kt index d5538f3a41..da76998092 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsTemporaryBasalsFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsTemporaryBasalsFragment.kt @@ -1,15 +1,15 @@ package info.nightscout.androidaps.activities.fragments +import android.annotation.SuppressLint import android.os.Bundle -import android.util.Log import android.util.SparseArray import android.view.* -import androidx.appcompat.widget.Toolbar import androidx.core.util.forEach import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import dagger.android.support.DaggerFragment import info.nightscout.androidaps.R +import info.nightscout.androidaps.activities.fragments.TreatmentsTemporaryBasalsFragment.RecyclerViewAdapter.TempBasalsViewHolder import info.nightscout.androidaps.data.IobTotal import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.ValueWrapper @@ -23,7 +23,6 @@ import info.nightscout.androidaps.database.transactions.InvalidateExtendedBolusT import info.nightscout.androidaps.database.transactions.InvalidateTemporaryBasalTransaction import info.nightscout.androidaps.databinding.TreatmentsTempbasalsFragmentBinding import info.nightscout.androidaps.databinding.TreatmentsTempbasalsItemBinding -import info.nightscout.androidaps.events.EventAutosensCalculationFinished import info.nightscout.androidaps.events.EventTempBasalChange import info.nightscout.androidaps.extensions.iobCalc import info.nightscout.androidaps.extensions.toStringFull @@ -31,18 +30,18 @@ import info.nightscout.androidaps.extensions.toTemporaryBasal import info.nightscout.androidaps.extensions.toVisibility import info.nightscout.androidaps.interfaces.ActivePlugin import info.nightscout.androidaps.interfaces.ProfileFunction -import info.nightscout.shared.logging.AAPSLogger -import info.nightscout.shared.logging.LTag import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.plugins.bus.RxBus -import info.nightscout.androidaps.activities.fragments.TreatmentsTemporaryBasalsFragment.RecyclerViewAdapter.TempBasalsViewHolder -import info.nightscout.androidaps.events.EventTreatmentUpdateGui +import info.nightscout.androidaps.utils.ActionModeHelper import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.T +import info.nightscout.androidaps.utils.ToastUtils import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.rx.AapsSchedulers +import info.nightscout.shared.logging.AAPSLogger +import info.nightscout.shared.logging.LTag import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign import java.util.concurrent.TimeUnit @@ -65,24 +64,28 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() { @Inject lateinit var repository: AppRepository private var _binding: TreatmentsTempbasalsFragmentBinding? = null + // This property is only valid between onCreateView and onDestroyView. private val binding get() = _binding!! - + private var menu: Menu? = null + private lateinit var actionHelper: ActionModeHelper private val millsToThePast = T.days(30).msecs() - private var selectedItems: SparseArray = SparseArray() private var showInvalidated = false - private var toolbar: Toolbar? = null - private var removeActionMode: ActionMode? = null override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = TreatmentsTempbasalsFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root + @SuppressLint("NotifyDataSetChanged") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - toolbar = activity?.findViewById(R.id.toolbar) + actionHelper = ActionModeHelper(rh, activity) + actionHelper.setUpdateListHandler { binding.recyclerview.adapter?.notifyDataSetChanged() } + actionHelper.setOnRemoveHandler { removeSelected(it) } setHasOptionsMenu(true) binding.recyclerview.setHasFixedSize(true) binding.recyclerview.layoutManager = LinearLayoutManager(view.context) + binding.recyclerview.emptyView = binding.noRecordsText + binding.recyclerview.loadingView = binding.progressBar } private fun tempBasalsWithInvalid(now: Long) = repository @@ -101,6 +104,7 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() { fun swapAdapter() { val now = System.currentTimeMillis() + binding.recyclerview.isLoading = true disposable += if (activePlugin.activePump.isFakingTempsByExtendedBoluses) { if (showInvalidated) @@ -134,28 +138,22 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() { override fun onResume() { super.onResume() swapAdapter() - disposable += rxBus .toObservable(EventTempBasalChange::class.java) .observeOn(aapsSchedulers.main) .subscribe({ swapAdapter() }, fabricPrivacy::logException) - - disposable += rxBus - .toObservable(EventAutosensCalculationFinished::class.java) - .observeOn(aapsSchedulers.main) - .subscribe({ swapAdapter() }, fabricPrivacy::logException) } @Synchronized override fun onPause() { super.onPause() + actionHelper.finish() disposable.clear() } @Synchronized override fun onDestroyView() { super.onDestroyView() - removeActionMode?.let { it.finish() } binding.recyclerview.adapter = null // avoid leaks _binding = null } @@ -172,10 +170,12 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() { holder.binding.ph.visibility = (tempBasal.interfaceIDs.pumpId != null).toVisibility() val sameDayPrevious = position > 0 && dateUtil.isSameDay(tempBasal.timestamp, tempBasalList[position - 1].timestamp) holder.binding.date.visibility = sameDayPrevious.not().toVisibility() - holder.binding.date.text = dateUtil.dateString(tempBasal.timestamp) + val newDay = position == 0 || !dateUtil.isSameDayGroup(tempBasal.timestamp, tempBasalList[position - 1].timestamp) + holder.binding.date.visibility = newDay.toVisibility() + holder.binding.date.text = if (newDay) dateUtil.dateStringRelative(tempBasal.timestamp, rh) else "" if (tempBasal.isInProgress) { holder.binding.time.text = dateUtil.timeString(tempBasal.timestamp) - holder.binding.time.setTextColor(rh.gc(R.color.colorActive)) + holder.binding.time.setTextColor(rh.gac(context, R.attr.activeColor)) } else { holder.binding.time.text = dateUtil.timeRangeString(tempBasal.timestamp, tempBasal.end) holder.binding.time.setTextColor(holder.binding.duration.currentTextColor) @@ -192,21 +192,20 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() { holder.binding.suspendFlag.visibility = (tempBasal.type == TemporaryBasal.Type.PUMP_SUSPEND).toVisibility() holder.binding.emulatedSuspendFlag.visibility = (tempBasal.type == TemporaryBasal.Type.EMULATED_PUMP_SUSPEND).toVisibility() holder.binding.superBolusFlag.visibility = (tempBasal.type == TemporaryBasal.Type.SUPERBOLUS).toVisibility() - if (abs(iob.basaliob) > 0.01) holder.binding.iob.setTextColor(rh.gc(R.color.colorActive)) else holder.binding.iob.setTextColor(holder.binding.duration.currentTextColor) - holder.binding.cbRemove.visibility = (tempBasal.isValid && removeActionMode != null).toVisibility() - if (removeActionMode != null) { + if (abs(iob.basaliob) > 0.01) holder.binding.iob.setTextColor(rh.gac(context, R.attr.activeColor)) else holder.binding.iob.setTextColor(holder.binding.duration.currentTextColor) + holder.binding.cbRemove.visibility = (tempBasal.isValid && actionHelper.isRemoving).toVisibility() + if (actionHelper.isRemoving) { holder.binding.cbRemove.setOnCheckedChangeListener { _, value -> - if (value) { - selectedItems.put(position, tempBasal) - } else { - selectedItems.remove(position) - } - removeActionMode?.title = rh.gs(R.string.count_selected, selectedItems.size()) + actionHelper.updateSelection(position, tempBasal, value) } - holder.binding.cbRemove.isChecked = selectedItems.get(position) != null + holder.binding.root.setOnClickListener { + holder.binding.cbRemove.toggle() + actionHelper.updateSelection(position, tempBasal, holder.binding.cbRemove.isChecked) + } + holder.binding.cbRemove.isChecked = actionHelper.isSelected(position) } val nextTimestamp = if (tempBasalList.size != position + 1) tempBasalList[position + 1].timestamp else 0L - holder.binding.delimiter.visibility = dateUtil.isSameDay(tempBasal.timestamp, nextTimestamp).toVisibility() + holder.binding.delimiter.visibility = dateUtil.isSameDayGroup(tempBasal.timestamp, nextTimestamp).toVisibility() } override fun getItemCount() = tempBasalList.size @@ -220,69 +219,46 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() { } 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) { - menu.findItem(R.id.nav_hide_invalidated)?.isVisible = showInvalidated - menu.findItem(R.id.nav_show_invalidated)?.isVisible = !showInvalidated + updateMenuVisibility() return super.onPrepareOptionsMenu(menu) } override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) { - R.id.nav_remove_items -> { - removeActionMode = toolbar?.startActionMode(RemoveActionModeCallback()) - true - } + R.id.nav_remove_items -> actionHelper.startRemove() R.id.nav_show_invalidated -> { showInvalidated = true - rxBus.send(EventTreatmentUpdateGui()) + updateMenuVisibility() + ToastUtils.showToastInUiThread(context, rh.gs(R.string.show_invalidated_records)) + swapAdapter() true } R.id.nav_hide_invalidated -> { showInvalidated = false - rxBus.send(EventTreatmentUpdateGui()) + updateMenuVisibility() + ToastUtils.showToastInUiThread(context, rh.gs(R.string.hide_invalidated_records)) + swapAdapter() true } else -> false } - inner class RemoveActionModeCallback : ActionMode.Callback { - - override fun onCreateActionMode(mode: ActionMode, menu: Menu?): Boolean { - mode.menuInflater.inflate(R.menu.menu_delete_selection, menu) - selectedItems.clear() - mode.title = rh.gs(R.string.count_selected, selectedItems.size()) - binding.recyclerview.adapter?.notifyDataSetChanged() - return true - } - - override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?) = false - - override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { - return when (item.itemId) { - R.id.remove_selected -> { - removeSelected() - true - } - - else -> false - } - } - - override fun onDestroyActionMode(mode: ActionMode?) { - removeActionMode = null - binding.recyclerview.adapter?.notifyDataSetChanged() - } - } - - private fun getConfirmationText(): String { + private fun getConfirmationText(selectedItems: SparseArray): String { if (selectedItems.size() == 1) { val tempBasal = selectedItems.valueAt(0) val isFakeExtended = tempBasal.type == TemporaryBasal.Type.FAKE_EXTENDED @@ -294,11 +270,11 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() { return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size()) } - private fun removeSelected() { + private fun removeSelected(selectedItems: SparseArray) { if (selectedItems.size() > 0) activity?.let { activity -> - OKDialog.showConfirmation(activity, rh.gs(R.string.removerecord), getConfirmationText(), Runnable { - selectedItems.forEach {_, tempBasal -> + 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) { @@ -330,10 +306,8 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() { { aapsLogger.error(LTag.DATABASE, "Error while invalidating temporary basal", it) }) } } - removeActionMode?.finish() + actionHelper.finish() }) } - else - removeActionMode?.finish() } } diff --git a/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsUserEntryFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsUserEntryFragment.kt index ac8c3b68e1..0303f5bf7c 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsUserEntryFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsUserEntryFragment.kt @@ -13,23 +13,22 @@ import info.nightscout.androidaps.database.entities.UserEntry.Sources import info.nightscout.androidaps.databinding.TreatmentsUserEntryFragmentBinding import info.nightscout.androidaps.databinding.TreatmentsUserEntryItemBinding import info.nightscout.androidaps.events.EventPreferenceChange +import info.nightscout.androidaps.extensions.toVisibility import info.nightscout.androidaps.interfaces.ImportExportPrefs import info.nightscout.androidaps.interfaces.ProfileFunction import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.plugins.bus.RxBus -import info.nightscout.androidaps.events.EventTreatmentUpdateGui -import info.nightscout.androidaps.extensions.toVisibility import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.T +import info.nightscout.androidaps.utils.ToastUtils import info.nightscout.androidaps.utils.Translator -import info.nightscout.androidaps.utils.userEntry.UserEntryPresentationHelper import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.rx.AapsSchedulers +import info.nightscout.androidaps.utils.userEntry.UserEntryPresentationHelper import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign -import java.util.concurrent.TimeUnit import javax.inject.Inject class TreatmentsUserEntryFragment : DaggerFragment() { @@ -47,9 +46,9 @@ class TreatmentsUserEntryFragment : DaggerFragment() { @Inject lateinit var userEntryPresentationHelper: UserEntryPresentationHelper private val disposable = CompositeDisposable() - private val millsToThePastFiltered = T.days(30).msecs() private val millsToThePastUnFiltered = T.days(3).msecs() + private var menu: Menu? = null private var showLoop = false private var _binding: TreatmentsUserEntryFragmentBinding? = null @@ -64,9 +63,11 @@ class TreatmentsUserEntryFragment : DaggerFragment() { setHasOptionsMenu(true) binding.recyclerview.setHasFixedSize(true) binding.recyclerview.layoutManager = LinearLayoutManager(view.context) + binding.recyclerview.emptyView = binding.noRecordsText + binding.recyclerview.loadingView = binding.progressBar } - fun exportUserEnteries() { + private fun exportUserEntries() { activity?.let { activity -> OKDialog.showConfirmation(activity, rh.gs(R.string.ue_export_to_csv) + "?") { uel.log(Action.EXPORT_CSV, Sources.Treatments) @@ -77,6 +78,7 @@ class TreatmentsUserEntryFragment : DaggerFragment() { fun swapAdapter() { val now = System.currentTimeMillis() + binding.recyclerview.isLoading = true disposable += if (showLoop) repository @@ -94,16 +96,10 @@ class TreatmentsUserEntryFragment : DaggerFragment() { override fun onResume() { super.onResume() swapAdapter() - disposable += rxBus .toObservable(EventPreferenceChange::class.java) .observeOn(aapsSchedulers.io) .subscribe({ swapAdapter() }, fabricPrivacy::logException) - disposable += rxBus - .toObservable(EventTreatmentUpdateGui::class.java) - .observeOn(aapsSchedulers.io) - .debounce(1L, TimeUnit.SECONDS) - .subscribe({ swapAdapter() }, fabricPrivacy::logException) } @Synchronized @@ -128,19 +124,18 @@ class TreatmentsUserEntryFragment : DaggerFragment() { override fun onBindViewHolder(holder: UserEntryViewHolder, position: Int) { val current = entries[position] - val sameDayPrevious = position > 0 && dateUtil.isSameDay(current.timestamp, entries[position - 1].timestamp) - holder.binding.date.visibility = sameDayPrevious.not().toVisibility() - holder.binding.date.text = dateUtil.dateString(current.timestamp) + val newDay = position == 0 || !dateUtil.isSameDayGroup(current.timestamp, entries[position - 1].timestamp) + holder.binding.date.visibility = newDay.toVisibility() + holder.binding.date.text = if (newDay) dateUtil.dateStringRelative(current.timestamp, rh) else "" holder.binding.time.text = dateUtil.timeStringWithSeconds(current.timestamp) holder.binding.action.text = userEntryPresentationHelper.actionToColoredString(current.action) holder.binding.notes.text = current.note - holder.binding.notes.visibility = if (current.note != "") View.VISIBLE else View.GONE + holder.binding.notes.visibility = (current.note != "").toVisibility() holder.binding.iconSource.setImageResource(userEntryPresentationHelper.iconId(current.source)) - holder.binding.iconSource.visibility = View.VISIBLE holder.binding.values.text = userEntryPresentationHelper.listToPresentationString(current.values) - holder.binding.values.visibility = if (holder.binding.values.text != "") View.VISIBLE else View.GONE + 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() + holder.binding.delimiter.visibility = dateUtil.isSameDayGroup(current.timestamp, nextTimestamp).toVisibility() } inner class UserEntryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { @@ -152,14 +147,18 @@ class TreatmentsUserEntryFragment : DaggerFragment() { } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + this.menu = menu inflater.inflate(R.menu.menu_treatments_user_entry, menu) super.onCreateOptionsMenu(menu, inflater) } - override fun onPrepareOptionsMenu(menu: Menu) { - menu.findItem(R.id.nav_hide_loop)?.isVisible = showLoop - menu.findItem(R.id.nav_show_loop)?.isVisible = !showLoop + 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) } @@ -167,21 +166,26 @@ class TreatmentsUserEntryFragment : DaggerFragment() { when (item.itemId) { R.id.nav_show_loop -> { showLoop = true - rxBus.send(EventTreatmentUpdateGui()) + updateMenuVisibility() + ToastUtils.showToastInUiThread(context, rh.gs(R.string.show_loop_records)) + swapAdapter() true } R.id.nav_hide_loop -> { showLoop = false - rxBus.send(EventTreatmentUpdateGui()) + updateMenuVisibility() + ToastUtils.showToastInUiThread(context, rh.gs(R.string.show_hide_records)) + swapAdapter() true } R.id.nav_export -> { - exportUserEnteries() + exportUserEntries() true } else -> false } + } diff --git a/app/src/main/java/info/nightscout/androidaps/db/CompatDBHelper.kt b/app/src/main/java/info/nightscout/androidaps/db/CompatDBHelper.kt index fb1136bc3e..2dcfac933e 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/CompatDBHelper.kt +++ b/app/src/main/java/info/nightscout/androidaps/db/CompatDBHelper.kt @@ -60,6 +60,11 @@ class CompatDBHelper @Inject constructor( rxBus.send(EventExtendedBolusChange()) rxBus.send(EventNewHistoryData(timestamp, false)) } + it.filterIsInstance().firstOrNull()?.let { eps -> + aapsLogger.debug(LTag.DATABASE, "Firing EventEffectiveProfileSwitchChanged $eps") + rxBus.send(EventEffectiveProfileSwitchChanged(eps)) + rxBus.send(EventNewHistoryData(eps.timestamp, false)) + } it.filterIsInstance().firstOrNull()?.let { tt -> aapsLogger.debug(LTag.DATABASE, "Firing EventTempTargetChange $tt") rxBus.send(EventTempTargetChange()) @@ -76,10 +81,6 @@ class CompatDBHelper @Inject constructor( aapsLogger.debug(LTag.DATABASE, "Firing EventProfileSwitchChanged $ps") rxBus.send(EventProfileSwitchChanged()) } - it.filterIsInstance().firstOrNull()?.let { eps -> - aapsLogger.debug(LTag.DATABASE, "Firing EventEffectiveProfileSwitchChanged $eps") - rxBus.send(EventEffectiveProfileSwitchChanged(eps)) - } it.filterIsInstance().firstOrNull()?.let { oe -> aapsLogger.debug(LTag.DATABASE, "Firing EventOfflineChange $oe") rxBus.send(EventOfflineChange()) diff --git a/app/src/main/java/info/nightscout/androidaps/di/APSModule.kt b/app/src/main/java/info/nightscout/androidaps/di/APSModule.kt index 54036aa7d3..2fe03ff711 100644 --- a/app/src/main/java/info/nightscout/androidaps/di/APSModule.kt +++ b/app/src/main/java/info/nightscout/androidaps/di/APSModule.kt @@ -8,8 +8,6 @@ import info.nightscout.androidaps.plugins.aps.openAPSAMA.DetermineBasalResultAMA import info.nightscout.androidaps.plugins.aps.openAPSSMB.DetermineBasalAdapterSMBJS import info.nightscout.androidaps.plugins.aps.openAPSSMB.DetermineBasalResultSMB import info.nightscout.androidaps.plugins.aps.openAPSSMBDynamicISF.DetermineBasalAdapterSMBDynamicISFJS -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobOref1Thread -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobThread @Module @Suppress("unused") @@ -21,6 +19,4 @@ abstract class APSModule { @ContributesAndroidInjector abstract fun determineBasalAdapterAMAJSInjector(): DetermineBasalAdapterAMAJS @ContributesAndroidInjector abstract fun determineBasalAdapterSMBJSInjector(): DetermineBasalAdapterSMBJS @ContributesAndroidInjector abstract fun determineBasalAdapterSMBAutoISFJSInjector(): DetermineBasalAdapterSMBDynamicISFJS - @ContributesAndroidInjector abstract fun iobCobThreadInjector(): IobCobThread - @ContributesAndroidInjector abstract fun iobCobOref1ThreadInjector(): IobCobOref1Thread } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/di/AppComponent.kt b/app/src/main/java/info/nightscout/androidaps/di/AppComponent.kt index c206de3837..876aa88311 100644 --- a/app/src/main/java/info/nightscout/androidaps/di/AppComponent.kt +++ b/app/src/main/java/info/nightscout/androidaps/di/AppComponent.kt @@ -46,6 +46,7 @@ import javax.inject.Singleton OmnipodDashModule::class, OmnipodErosModule::class, APSModule::class, + WorkflowModule::class, PreferencesModule::class, OverviewModule::class, DataClassesModule::class, diff --git a/app/src/main/java/info/nightscout/androidaps/di/PluginsModule.kt b/app/src/main/java/info/nightscout/androidaps/di/PluginsModule.kt index 9b95c9b956..33259f2d18 100644 --- a/app/src/main/java/info/nightscout/androidaps/di/PluginsModule.kt +++ b/app/src/main/java/info/nightscout/androidaps/di/PluginsModule.kt @@ -32,6 +32,7 @@ import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin import info.nightscout.androidaps.plugins.general.overview.OverviewPlugin import info.nightscout.androidaps.plugins.general.persistentNotification.PersistentNotificationPlugin import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin +import info.nightscout.androidaps.plugins.general.themes.ThemeSwitcherPlugin import info.nightscout.androidaps.plugins.general.wear.WearPlugin import info.nightscout.androidaps.plugins.general.xdripStatusline.StatusLinePlugin import info.nightscout.androidaps.plugins.insulin.InsulinLyumjevPlugin @@ -393,6 +394,12 @@ abstract class PluginsModule { @IntKey(490) abstract fun bindConfigBuilderPlugin(plugin: ConfigBuilderPlugin): PluginBase + @Binds + @AllConfigs + @IntoMap + @IntKey(500) + abstract fun bindThemeSwitcherPlugin(plugin: ThemeSwitcherPlugin): PluginBase + @Qualifier annotation class AllConfigs diff --git a/app/src/main/java/info/nightscout/androidaps/di/ReceiversModule.kt b/app/src/main/java/info/nightscout/androidaps/di/ReceiversModule.kt index 9d6615a362..72edb82bc1 100644 --- a/app/src/main/java/info/nightscout/androidaps/di/ReceiversModule.kt +++ b/app/src/main/java/info/nightscout/androidaps/di/ReceiversModule.kt @@ -2,12 +2,11 @@ package info.nightscout.androidaps.di import dagger.Module import dagger.android.ContributesAndroidInjector +import info.nightscout.androidaps.plugins.aps.loop.CarbSuggestionReceiver import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkBluetoothStateReceiver import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkBroadcastReceiver -import info.nightscout.androidaps.plugins.aps.loop.CarbSuggestionReceiver import info.nightscout.androidaps.receivers.* - @Module @Suppress("unused") abstract class ReceiversModule { @@ -16,8 +15,7 @@ abstract class ReceiversModule { @ContributesAndroidInjector abstract fun contributesBTReceiver(): BTReceiver @ContributesAndroidInjector abstract fun contributesChargingStateReceiver(): ChargingStateReceiver @ContributesAndroidInjector abstract fun contributesDataReceiver(): DataReceiver - @ContributesAndroidInjector abstract fun contributesKeepAliveReceiver(): KeepAliveReceiver - @ContributesAndroidInjector abstract fun contributesKeepAliveWorker(): KeepAliveReceiver.KeepAliveWorker + @ContributesAndroidInjector abstract fun contributesKeepAliveWorker(): KeepAliveWorker @ContributesAndroidInjector abstract fun contributesRileyLinkBluetoothStateReceiver(): RileyLinkBluetoothStateReceiver @ContributesAndroidInjector abstract fun contributesSmsReceiver(): SmsReceiver @ContributesAndroidInjector abstract fun contributesTimeDateOrTZChangeReceiver(): TimeDateOrTZChangeReceiver diff --git a/app/src/main/java/info/nightscout/androidaps/di/UIModule.kt b/app/src/main/java/info/nightscout/androidaps/di/UIModule.kt index 294fa28f0d..160601bad2 100644 --- a/app/src/main/java/info/nightscout/androidaps/di/UIModule.kt +++ b/app/src/main/java/info/nightscout/androidaps/di/UIModule.kt @@ -2,10 +2,16 @@ package info.nightscout.androidaps.di import dagger.Module import dagger.android.ContributesAndroidInjector +import info.nightscout.androidaps.widget.WidgetConfigureActivity import info.nightscout.androidaps.skins.SkinListPreference +import info.nightscout.androidaps.widget.Widget @Module @Suppress("unused") abstract class UIModule { + @ContributesAndroidInjector abstract fun skinListPreferenceInjector(): SkinListPreference + @ContributesAndroidInjector abstract fun aapsWidgetInjector(): Widget + @ContributesAndroidInjector abstract fun contributesWidgetConfigureActivity(): WidgetConfigureActivity + } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/di/WorkflowModule.kt b/app/src/main/java/info/nightscout/androidaps/di/WorkflowModule.kt new file mode 100644 index 0000000000..620064dc6a --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/di/WorkflowModule.kt @@ -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 +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/CalibrationDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/CalibrationDialog.kt index bd5daaf965..303df14f60 100644 --- a/app/src/main/java/info/nightscout/androidaps/dialogs/CalibrationDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/CalibrationDialog.kt @@ -64,7 +64,7 @@ class CalibrationDialog : DialogFragmentWithDate() { binding.bg.setParams(savedInstanceState?.getDouble("bg") ?: bg, 36.0, 500.0, 1.0, DecimalFormat("0"), false, binding.okcancel.ok) binding.units.text = if (units == GlucoseUnit.MMOL) rh.gs(R.string.mmol) else rh.gs(R.string.mgdl) - binding.bg.editText?.id?.let { binding.bgLabel.labelFor = it } + binding.bgLabel.labelFor = binding.bg.editTextId } override fun onDestroyView() { diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/CarbsDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/CarbsDialog.kt index 60835562dd..cdf688b4c3 100644 --- a/app/src/main/java/info/nightscout/androidaps/dialogs/CarbsDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/CarbsDialog.kt @@ -27,6 +27,8 @@ import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProv import info.nightscout.androidaps.queue.Callback import info.nightscout.androidaps.utils.* import info.nightscout.androidaps.utils.alertDialogs.OKDialog +import info.nightscout.androidaps.utils.protection.ProtectionCheck +import info.nightscout.androidaps.utils.protection.ProtectionCheck.Protection.BOLUS import info.nightscout.androidaps.utils.resources.ResourceHelper import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign @@ -50,6 +52,7 @@ class CarbsDialog : DialogFragmentWithDate() { @Inject lateinit var bolusTimer: BolusTimer @Inject lateinit var commandQueue: CommandQueue @Inject lateinit var repository: AppRepository + @Inject lateinit var protectionCheck: ProtectionCheck companion object { @@ -58,6 +61,7 @@ class CarbsDialog : DialogFragmentWithDate() { private const val FAV3_DEFAULT = 20 } + private var queryingProtection = false private val disposable = CompositeDisposable() private val textWatcher: TextWatcher = object : TextWatcher { @@ -195,9 +199,9 @@ class CarbsDialog : DialogFragmentWithDate() { binding.hypoTt.isChecked = false binding.activityTt.isChecked = false } - binding.duration.editText?.id?.let { binding.durationLabel.labelFor = it } - binding.time.editText?.id?.let { binding.timeLabel.labelFor = it } - binding.carbs.editText?.id?.let { binding.carbsLabel.labelFor = it } + binding.durationLabel.labelFor = binding.duration.editTextId + binding.timeLabel.labelFor = binding.time.editTextId + binding.carbsLabel.labelFor = binding.carbs.editTextId } override fun onDestroyView() { @@ -230,8 +234,9 @@ class CarbsDialog : DialogFragmentWithDate() { if (activitySelected) actions.add( rh.gs(R.string.temptargetshort) + ": " + (DecimalFormatter.to1Decimal(activityTT) + " " + unitLabel + " (" + rh.gs(R.string.format_mins, activityTTDuration) + ")").formatColor( + context, rh, - R.color.tempTargetConfirmation + R.attr.tempTargetConfirmation ) ) val eatingSoonSelected = binding.eatingSoonTt.isChecked @@ -240,27 +245,27 @@ class CarbsDialog : DialogFragmentWithDate() { rh.gs(R.string.temptargetshort) + ": " + (DecimalFormatter.to1Decimal(eatingSoonTT) + " " + unitLabel + " (" + rh.gs( R.string.format_mins, eatingSoonTTDuration - ) + ")").formatColor(rh, R.color.tempTargetConfirmation) + ) + ")").formatColor(context, rh, R.attr.tempTargetConfirmation) ) val hypoSelected = binding.hypoTt.isChecked if (hypoSelected) actions.add( - rh.gs(R.string.temptargetshort) + ": " + (DecimalFormatter.to1Decimal(hypoTT) + " " + unitLabel + " (" + rh.gs(R.string.format_mins, hypoTTDuration) + ")").formatColor( + rh.gs(R.string.temptargetshort) + ": " + (DecimalFormatter.to1Decimal(hypoTT) + " " + unitLabel + " (" + rh.gs(R.string.format_mins, hypoTTDuration) + ")").formatColor( context, rh, - R.color.tempTargetConfirmation + R.attr.tempTargetConfirmation ) ) val timeOffset = binding.time.value.toInt() if (useAlarm && carbs > 0 && timeOffset > 0) - actions.add(rh.gs(R.string.alarminxmin, timeOffset).formatColor(rh, R.color.info)) + actions.add(rh.gs(R.string.alarminxmin, timeOffset).formatColor(context , rh, R.attr.infoColor)) val duration = binding.duration.value.toInt() if (duration > 0) actions.add(rh.gs(R.string.duration) + ": " + duration + rh.gs(R.string.shorthour)) if (carbsAfterConstraints > 0) { - actions.add(rh.gs(R.string.carbs) + ": " + "" + rh.gs(R.string.format_carbs, carbsAfterConstraints) + "") + actions.add(rh.gs(R.string.carbs) + ": " + "" + rh.gs(R.string.format_carbs, carbsAfterConstraints) + "") if (carbsAfterConstraints != carbs) - actions.add("" + rh.gs(R.string.carbsconstraintapplied) + "") + actions.add("" + rh.gs(R.string.carbsconstraintapplied) + "") } val notes = binding.notesLayout.notes.text.toString() if (notes.isNotEmpty()) @@ -377,4 +382,20 @@ class CarbsDialog : DialogFragmentWithDate() { } return true } -} \ No newline at end of file + + override fun onResume() { + super.onResume() + if(!queryingProtection) { + queryingProtection = true + activity?.let { activity -> + val cancelFail = { + queryingProtection = false + aapsLogger.debug(LTag.APS, "Dialog canceled on resume protection: ${this.javaClass.name}") + ToastUtils.showToastInUiThread(ctx, R.string.dialog_canceled) + dismiss() + } + protectionCheck.queryProtection(activity, BOLUS, { queryingProtection = false }, cancelFail, cancelFail) + } + } + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/CareDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/CareDialog.kt index 03cb215a7f..994613c38f 100644 --- a/app/src/main/java/info/nightscout/androidaps/dialogs/CareDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/CareDialog.kt @@ -165,8 +165,8 @@ class CareDialog : DialogFragmentWithDate() { ?: 0.0, 0.0, Constants.MAX_PROFILE_SWITCH_DURATION, 10.0, DecimalFormat("0"), false, binding.okcancel.ok) if (options == EventType.NOTE || options == EventType.QUESTION || options == EventType.ANNOUNCEMENT || options == EventType.EXERCISE) binding.notesLayout.root.visibility = View.VISIBLE // independent to preferences - binding.bg.editText?.id?.let { binding.bgLabel.labelFor = it } - binding.duration.editText?.id?.let { binding.durationLabel.labelFor = it } + binding.bgLabel.labelFor = binding.bg.editTextId + binding.durationLabel.labelFor = binding.duration.editTextId } override fun onDestroyView() { diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/ExtendedBolusDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/ExtendedBolusDialog.kt index 7205a36939..e4f45c4e1e 100644 --- a/app/src/main/java/info/nightscout/androidaps/dialogs/ExtendedBolusDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/ExtendedBolusDialog.kt @@ -22,7 +22,11 @@ import info.nightscout.androidaps.utils.HtmlHelper import info.nightscout.shared.SafeParse import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.extensions.formatColor +import info.nightscout.androidaps.utils.ToastUtils +import info.nightscout.androidaps.utils.protection.ProtectionCheck +import info.nightscout.androidaps.utils.protection.ProtectionCheck.Protection.BOLUS import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.shared.logging.LTag import java.text.DecimalFormat import java.util.* import javax.inject.Inject @@ -36,11 +40,12 @@ class ExtendedBolusDialog : DialogFragmentWithDate() { @Inject lateinit var commandQueue: CommandQueue @Inject lateinit var activePlugin: ActivePlugin @Inject lateinit var uel: UserEntryLogger + @Inject lateinit var protectionCheck: ProtectionCheck + private var queryingProtection = false private var _binding: DialogExtendedbolusBinding? = null - // This property is only valid between onCreateView and - // onDestroyView. + // This property is only valid between onCreateView and onDestroyView. private val binding get() = _binding!! override fun onSaveInstanceState(savedInstanceState: Bundle) { @@ -70,8 +75,8 @@ class ExtendedBolusDialog : DialogFragmentWithDate() { val extendedMaxDuration = pumpDescription.extendedBolusMaxDuration binding.duration.setParams(savedInstanceState?.getDouble("duration") ?: extendedDurationStep, extendedDurationStep, extendedMaxDuration, extendedDurationStep, DecimalFormat("0"), false, binding.okcancel.ok) - binding.insulin.editText?.id?.let { binding.insulinLabel.labelFor = it } - binding.duration.editText?.id?.let { binding.durationLabel.labelFor = it } + binding.insulinLabel.labelFor = binding.insulin.editTextId + binding.durationLabel.labelFor = binding.duration.editTextId } override fun onDestroyView() { @@ -88,7 +93,7 @@ class ExtendedBolusDialog : DialogFragmentWithDate() { actions.add(rh.gs(R.string.formatinsulinunits, insulinAfterConstraint)) actions.add(rh.gs(R.string.duration) + ": " + rh.gs(R.string.format_mins, durationInMinutes)) if (abs(insulinAfterConstraint - insulin) > 0.01) - actions.add(rh.gs(R.string.constraintapllied).formatColor(rh, R.color.warning)) + actions.add(rh.gs(R.string.constraintapllied).formatColor(context, rh, R.attr.warningColor)) activity?.let { activity -> OKDialog.showConfirmation(activity, rh.gs(R.string.extended_bolus), HtmlHelper.fromHtml(Joiner.on("
").join(actions)), { @@ -106,4 +111,20 @@ class ExtendedBolusDialog : DialogFragmentWithDate() { } return true } -} \ No newline at end of file + + override fun onResume() { + super.onResume() + if(!queryingProtection) { + queryingProtection = true + activity?.let { activity -> + val cancelFail = { + queryingProtection = false + aapsLogger.debug(LTag.APS, "Dialog canceled on resume protection: ${this.javaClass.name}") + ToastUtils.showToastInUiThread(ctx, R.string.dialog_canceled) + dismiss() + } + protectionCheck.queryProtection(activity, BOLUS, { queryingProtection = false }, cancelFail, cancelFail) + } + } + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/FillDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/FillDialog.kt index 54b222ce2c..c52ca8436b 100644 --- a/app/src/main/java/info/nightscout/androidaps/dialogs/FillDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/FillDialog.kt @@ -28,6 +28,9 @@ import info.nightscout.androidaps.utils.HtmlHelper import info.nightscout.shared.SafeParse import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.extensions.formatColor +import info.nightscout.androidaps.utils.ToastUtils +import info.nightscout.androidaps.utils.protection.ProtectionCheck +import info.nightscout.androidaps.utils.protection.ProtectionCheck.Protection.BOLUS import info.nightscout.androidaps.utils.resources.ResourceHelper import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign @@ -44,13 +47,13 @@ class FillDialog : DialogFragmentWithDate() { @Inject lateinit var activePlugin: ActivePlugin @Inject lateinit var uel: UserEntryLogger @Inject lateinit var repository: AppRepository + @Inject lateinit var protectionCheck: ProtectionCheck + private var queryingProtection = false private val disposable = CompositeDisposable() - private var _binding: DialogFillBinding? = null - // This property is only valid between onCreateView and - // onDestroyView. + // This property is only valid between onCreateView and onDestroyView. private val binding get() = _binding!! override fun onSaveInstanceState(savedInstanceState: Bundle) { @@ -96,7 +99,7 @@ class FillDialog : DialogFragmentWithDate() { } else { binding.fillPresetButton3.visibility = View.GONE } - binding.fillInsulinamount.editText?.id?.let { binding.fillLabel.labelFor = it } + binding.fillLabel.labelFor = binding.fillInsulinamount.editTextId } override fun onDestroyView() { @@ -113,16 +116,16 @@ class FillDialog : DialogFragmentWithDate() { if (insulinAfterConstraints > 0) { actions.add(rh.gs(R.string.fillwarning)) actions.add("") - actions.add(rh.gs(R.string.bolus) + ": " + DecimalFormatter.toPumpSupportedBolus(insulinAfterConstraints, activePlugin.activePump, rh).formatColor(rh, R.color.colorInsulinButton)) + actions.add(rh.gs(R.string.bolus) + ": " + DecimalFormatter.toPumpSupportedBolus(insulinAfterConstraints, activePlugin.activePump, rh).formatColor(context, rh, R.attr.insulinButtonColor)) if (abs(insulinAfterConstraints - insulin) > 0.01) - actions.add(rh.gs(R.string.bolusconstraintappliedwarn, insulin, insulinAfterConstraints).formatColor(rh, R.color.warning)) + actions.add(rh.gs(R.string.bolusconstraintappliedwarn, insulin, insulinAfterConstraints).formatColor(context, rh, R.attr.warningColor)) } val siteChange = binding.fillCatheterChange.isChecked if (siteChange) - actions.add(rh.gs(R.string.record_pump_site_change).formatColor(rh, R.color.actionsConfirm)) + actions.add(rh.gs(R.string.record_pump_site_change).formatColor(context, rh, R.attr.actionsConfirmColor)) val insulinChange = binding.fillCartridgeChange.isChecked if (insulinChange) - actions.add(rh.gs(R.string.record_insulin_cartridge_change).formatColor(rh, R.color.actionsConfirm)) + actions.add(rh.gs(R.string.record_insulin_cartridge_change).formatColor(context, rh, R.attr.actionsConfirmColor)) val notes: String = binding.notesLayout.notes.text.toString() if (notes.isNotEmpty()) actions.add(rh.gs(R.string.notes_label) + ": " + notes) @@ -196,4 +199,20 @@ class FillDialog : DialogFragmentWithDate() { } }) } + + override fun onResume() { + super.onResume() + if(!queryingProtection) { + queryingProtection = true + activity?.let { activity -> + val cancelFail = { + queryingProtection = false + aapsLogger.debug(LTag.APS, "Dialog canceled on resume protection: ${this.javaClass.name}") + ToastUtils.showToastInUiThread(ctx, R.string.dialog_canceled) + dismiss() + } + protectionCheck.queryProtection(activity, BOLUS, { queryingProtection = false }, cancelFail, cancelFail) + } + } + } } diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/InsulinDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/InsulinDialog.kt index 21eb81ebce..5739902be0 100644 --- a/app/src/main/java/info/nightscout/androidaps/dialogs/InsulinDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/InsulinDialog.kt @@ -28,6 +28,8 @@ import info.nightscout.androidaps.queue.Callback import info.nightscout.androidaps.utils.* import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.utils.extensions.toSignedString +import info.nightscout.androidaps.utils.protection.ProtectionCheck +import info.nightscout.androidaps.utils.protection.ProtectionCheck.Protection.BOLUS import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.shared.SafeParse import io.reactivex.rxjava3.disposables.CompositeDisposable @@ -52,6 +54,7 @@ class InsulinDialog : DialogFragmentWithDate() { @Inject lateinit var config: Config @Inject lateinit var bolusTimer: BolusTimer @Inject lateinit var uel: UserEntryLogger + @Inject lateinit var protectionCheck: ProtectionCheck companion object { @@ -60,7 +63,12 @@ class InsulinDialog : DialogFragmentWithDate() { private const val PLUS3_DEFAULT = 2.0 } + private var queryingProtection = false private val disposable = CompositeDisposable() + private var _binding: DialogInsulinBinding? = null + + // This property is only valid between onCreateView and onDestroyView. + private val binding get() = _binding!! private val textWatcher: TextWatcher = object : TextWatcher { override fun afterTextChanged(s: Editable) { @@ -71,12 +79,6 @@ class InsulinDialog : DialogFragmentWithDate() { override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} } - private var _binding: DialogInsulinBinding? = null - - // This property is only valid between onCreateView and - // onDestroyView. - private val binding get() = _binding!! - private fun validateInputs() { val maxInsulin = constraintChecker.getMaxBolusAllowed().value() if (abs(binding.time.value.toInt()) > 12 * 60) { @@ -148,8 +150,8 @@ class InsulinDialog : DialogFragmentWithDate() { binding.recordOnly.setOnCheckedChangeListener { _, isChecked: Boolean -> binding.timeLayout.visibility = isChecked.toVisibility() } - binding.amount.editText?.id?.let { binding.insulinLabel.labelFor = it } - binding.time.editText?.id?.let { binding.timeLabel.labelFor = it } + binding.insulinLabel.labelFor = binding.amount.editTextId + binding.timeLabel.labelFor = binding.time.editTextId } override fun onDestroyView() { @@ -170,16 +172,17 @@ class InsulinDialog : DialogFragmentWithDate() { val eatingSoonChecked = binding.startEatingSoonTt.isChecked if (insulinAfterConstraints > 0) { - actions.add(rh.gs(R.string.bolus) + ": " + DecimalFormatter.toPumpSupportedBolus(insulinAfterConstraints, activePlugin.activePump, rh).formatColor(rh, R.color.bolus)) + actions.add(rh.gs(R.string.bolus) + ": " + DecimalFormatter.toPumpSupportedBolus(insulinAfterConstraints, activePlugin.activePump, rh).formatColor(context, rh, R.attr.bolusColor)) if (recordOnlyChecked) - actions.add(rh.gs(R.string.bolusrecordedonly).formatColor(rh, R.color.warning)) + actions.add(rh.gs(R.string.bolusrecordedonly).formatColor(context, rh, R.attr.warningColor)) if (abs(insulinAfterConstraints - insulin) > pumpDescription.pumpType.determineCorrectBolusStepSize(insulinAfterConstraints)) - actions.add(rh.gs(R.string.bolusconstraintappliedwarn, insulin, insulinAfterConstraints).formatColor(rh, R.color.warning)) + actions.add(rh.gs(R.string.bolusconstraintappliedwarn, insulin, insulinAfterConstraints).formatColor(context, rh, R.attr.warningColor)) } val eatingSoonTTDuration = defaultValueHelper.determineEatingSoonTTDuration() val eatingSoonTT = defaultValueHelper.determineEatingSoonTT() if (eatingSoonChecked) - actions.add(rh.gs(R.string.temptargetshort) + ": " + (DecimalFormatter.to1Decimal(eatingSoonTT) + " " + unitLabel + " (" + rh.gs(R.string.format_mins, eatingSoonTTDuration) + ")").formatColor(rh, R.color.tempTargetConfirmation)) + actions.add(rh.gs(R.string.temptargetshort) + ": " + (DecimalFormatter.to1Decimal(eatingSoonTT) + " " + unitLabel + " (" + rh.gs(R.string.format_mins, eatingSoonTTDuration) + ")") + .formatColor(context, rh, R.attr.tempTargetConfirmation)) val timeOffset = binding.time.value.toInt() val time = dateUtil.now() + T.mins(timeOffset.toLong()).msecs() @@ -255,4 +258,20 @@ class InsulinDialog : DialogFragmentWithDate() { } return true } -} \ No newline at end of file + + override fun onResume() { + super.onResume() + if(!queryingProtection) { + queryingProtection = true + activity?.let { activity -> + val cancelFail = { + queryingProtection = false + aapsLogger.debug(LTag.APS, "Dialog canceled on resume protection: ${this.javaClass.name}") + ToastUtils.showToastInUiThread(ctx, R.string.dialog_canceled) + dismiss() + } + protectionCheck.queryProtection(activity, BOLUS, { queryingProtection = false }, cancelFail, cancelFail) + } + } + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/LoopDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/LoopDialog.kt index 8e67a0ed6e..5802f928bd 100644 --- a/app/src/main/java/info/nightscout/androidaps/dialogs/LoopDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/LoopDialog.kt @@ -38,6 +38,8 @@ import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.T import info.nightscout.androidaps.utils.ToastUtils import info.nightscout.androidaps.utils.alertDialogs.OKDialog +import info.nightscout.androidaps.utils.protection.ProtectionCheck +import info.nightscout.androidaps.utils.protection.ProtectionCheck.Protection.BOLUS import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.shared.sharedPreferences.SP import io.reactivex.rxjava3.disposables.CompositeDisposable @@ -62,14 +64,15 @@ class LoopDialog : DaggerDialogFragment() { @Inject lateinit var dateUtil: DateUtil @Inject lateinit var repository: AppRepository @Inject lateinit var objectivePlugin: ObjectivesPlugin + @Inject lateinit var protectionCheck: ProtectionCheck + private var queryingProtection = false private var showOkCancel: Boolean = true private var _binding: DialogLoopBinding? = null private var handler = Handler(HandlerThread(this::class.simpleName + "Handler").also { it.start() }.looper) private lateinit var refreshDialog: Runnable - // This property is only valid between onCreateView and - // onDestroyView. + // This property is only valid between onCreateView and onDestroyView. private val binding get() = _binding!! val disposable = CompositeDisposable() @@ -437,4 +440,20 @@ class LoopDialog : DaggerDialogFragment() { aapsLogger.debug(e.localizedMessage ?: e.toString()) } } + + override fun onResume() { + super.onResume() + if(!queryingProtection) { + queryingProtection = true + activity?.let { activity -> + val cancelFail = { + queryingProtection = false + aapsLogger.debug(LTag.APS, "Dialog canceled on resume protection: ${this.javaClass.name}") + ToastUtils.showToastInUiThread(ctx, R.string.dialog_canceled) + dismiss() + } + protectionCheck.queryProtection(activity, BOLUS, { queryingProtection = false }, cancelFail, cancelFail) + } + } + } } diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/ProfileSwitchDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/ProfileSwitchDialog.kt index 70ed31b8bc..d7c7ecb809 100644 --- a/app/src/main/java/info/nightscout/androidaps/dialogs/ProfileSwitchDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/ProfileSwitchDialog.kt @@ -1,5 +1,6 @@ package info.nightscout.androidaps.dialogs +import android.content.Context import android.os.Bundle import android.text.Editable import android.text.TextWatcher @@ -30,7 +31,10 @@ import info.nightscout.androidaps.utils.DefaultValueHelper import info.nightscout.androidaps.utils.HardLimits import info.nightscout.androidaps.utils.HtmlHelper import info.nightscout.androidaps.utils.T +import info.nightscout.androidaps.utils.ToastUtils import info.nightscout.androidaps.utils.alertDialogs.OKDialog +import info.nightscout.androidaps.utils.protection.ProtectionCheck +import info.nightscout.androidaps.utils.protection.ProtectionCheck.Protection.BOLUS import info.nightscout.androidaps.utils.resources.ResourceHelper import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign @@ -51,15 +55,15 @@ class ProfileSwitchDialog : DialogFragmentWithDate() { @Inject lateinit var hardLimits: HardLimits @Inject lateinit var rxBus: RxBus @Inject lateinit var defaultValueHelper: DefaultValueHelper + @Inject lateinit var ctx: Context + @Inject lateinit var protectionCheck: ProtectionCheck - private var profileIndex: Int? = null - + private var queryingProtection = false + private var profileName: String? = null private val disposable = CompositeDisposable() - private var _binding: DialogProfileswitchBinding? = null - // This property is only valid between onCreateView and - // onDestroyView. + // This property is only valid between onCreateView and onDestroyView. private val binding get() = _binding!! private val textWatcher: TextWatcher = object : TextWatcher { @@ -86,7 +90,7 @@ class ProfileSwitchDialog : DialogFragmentWithDate() { ): View { onCreateViewGeneral() arguments?.let { bundle -> - profileIndex = bundle.getInt("profileIndex", 0) + profileName = bundle.getString("profileName", null) } _binding = DialogProfileswitchBinding.inflate(inflater, container, false) return binding.root @@ -112,8 +116,7 @@ class ProfileSwitchDialog : DialogFragmentWithDate() { // profile context?.let { context -> - val profileStore = activePlugin.activeProfileSource.profile - ?: return + val profileStore = activePlugin.activeProfileSource.profile ?: return val profileListToCheck = profileStore.getProfileList() val profileList = ArrayList() for (profileName in profileListToCheck) { @@ -125,15 +128,16 @@ class ProfileSwitchDialog : DialogFragmentWithDate() { dismiss() return } - val adapter = ArrayAdapter(context, R.layout.spinner_centered, profileList) - binding.profile.adapter = adapter + binding.profileList.setAdapter(ArrayAdapter(context, R.layout.spinner_centered, profileList)) // set selected to actual profile - if (profileIndex != null) - binding.profile.setSelection(profileIndex as Int) - else + if (profileName != null) + binding.profileList.setText(profileName, false) + else { + binding.profileList.setText(profileList[0], false) for (p in profileList.indices) if (profileList[p] == profileFunction.getOriginalProfileName()) - binding.profile.setSelection(p) + binding.profileList.setText(profileList[p], false) + } } profileFunction.getProfile()?.let { profile -> @@ -148,9 +152,9 @@ class ProfileSwitchDialog : DialogFragmentWithDate() { } } binding.ttLayout.visibility = View.GONE - binding.duration.editText?.id?.let { binding.durationLabel.labelFor = it } - binding.percentage.editText?.id?.let { binding.percentageLabel.labelFor = it } - binding.timeshift.editText?.id?.let { binding.timeshiftLabel.labelFor = it } + binding.durationLabel.labelFor = binding.duration.editTextId + binding.percentageLabel.labelFor = binding.percentage.editTextId + binding.timeshiftLabel.labelFor = binding.timeshift.editTextId } override fun onDestroyView() { @@ -168,7 +172,7 @@ class ProfileSwitchDialog : DialogFragmentWithDate() { val duration = binding.duration.value.toInt() if (duration > 0L) actions.add(rh.gs(R.string.duration) + ": " + rh.gs(R.string.format_mins, duration)) - val profileName = binding.profile.selectedItem.toString() + val profileName = binding.profileList.text.toString() actions.add(rh.gs(R.string.profile) + ": " + profileName) val percent = binding.percentage.value.toInt() if (percent != 100) @@ -245,4 +249,20 @@ class ProfileSwitchDialog : DialogFragmentWithDate() { } return true } + + override fun onResume() { + super.onResume() + if(!queryingProtection) { + queryingProtection = true + activity?.let { activity -> + val cancelFail = { + queryingProtection = false + aapsLogger.debug(LTag.APS, "Dialog canceled on resume protection: ${this.javaClass.name}") + ToastUtils.showToastInUiThread(ctx, R.string.dialog_canceled) + dismiss() + } + protectionCheck.queryProtection(activity, BOLUS, { queryingProtection = false }, cancelFail, cancelFail) + } + } + } } diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/TempBasalDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/TempBasalDialog.kt index d458055b9d..62bd73862d 100644 --- a/app/src/main/java/info/nightscout/androidaps/dialogs/TempBasalDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/TempBasalDialog.kt @@ -20,7 +20,11 @@ import info.nightscout.androidaps.utils.HtmlHelper import info.nightscout.shared.SafeParse import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.extensions.formatColor +import info.nightscout.androidaps.utils.ToastUtils +import info.nightscout.androidaps.utils.protection.ProtectionCheck +import info.nightscout.androidaps.utils.protection.ProtectionCheck.Protection.BOLUS import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.shared.logging.LTag import java.text.DecimalFormat import java.util.* import javax.inject.Inject @@ -35,13 +39,13 @@ class TempBasalDialog : DialogFragmentWithDate() { @Inject lateinit var commandQueue: CommandQueue @Inject lateinit var ctx: Context @Inject lateinit var uel: UserEntryLogger + @Inject lateinit var protectionCheck: ProtectionCheck + private var queryingProtection = false private var isPercentPump = true - private var _binding: DialogTempbasalBinding? = null - // This property is only valid between onCreateView and - // onDestroyView. + // This property is only valid between onCreateView and onDestroyView. private val binding get() = _binding!! override fun onSaveInstanceState(savedInstanceState: Bundle) { @@ -51,8 +55,7 @@ class TempBasalDialog : DialogFragmentWithDate() { savedInstanceState.putDouble("basalAbsoluteInput", binding.basalAbsoluteInput.value) } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?): View { + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { onCreateViewGeneral() _binding = DialogTempbasalBinding.inflate(inflater, container, false) return binding.root @@ -86,9 +89,9 @@ class TempBasalDialog : DialogFragmentWithDate() { binding.percentLayout.visibility = View.GONE binding.absoluteLayout.visibility = View.VISIBLE } - binding.basalPercentInput.editText?.id?.let { binding.basalPercentLabel.labelFor = it } - binding.basalAbsoluteInput.editText?.id?.let { binding.basalAbsoluteLabel.labelFor = it } - binding.duration.editText?.id?.let { binding.durationLabel.labelFor = it } + binding.basalPercentLabel.labelFor = binding.basalPercentInput.editTextId + binding.basalAbsoluteLabel.labelFor = binding.basalAbsoluteInput.editTextId + binding.durationLabel.labelFor = binding.duration.editTextId } override fun onDestroyView() { @@ -115,7 +118,7 @@ class TempBasalDialog : DialogFragmentWithDate() { actions.add(rh.gs(R.string.tempbasal_label) + ": " + rh.gs(R.string.pump_basebasalrate, absolute)) actions.add(rh.gs(R.string.duration) + ": " + rh.gs(R.string.format_mins, durationInMinutes)) if (abs(absolute - basalAbsoluteInput) > 0.01) - actions.add(rh.gs(R.string.constraintapllied).formatColor(rh, R.color.warning)) + actions.add(rh.gs(R.string.constraintapllied).formatColor(context, rh, R.attr.warningColor)) } activity?.let { activity -> OKDialog.showConfirmation(activity, rh.gs(R.string.tempbasal_label), HtmlHelper.fromHtml(Joiner.on("
").join(actions)), { @@ -141,4 +144,20 @@ class TempBasalDialog : DialogFragmentWithDate() { } return true } -} \ No newline at end of file + + override fun onResume() { + super.onResume() + if(!queryingProtection) { + queryingProtection = true + activity?.let { activity -> + val cancelFail = { + queryingProtection = false + aapsLogger.debug(LTag.APS, "Dialog canceled on resume protection: ${this.javaClass.name}") + ToastUtils.showToastInUiThread(ctx, R.string.dialog_canceled) + dismiss() + } + protectionCheck.queryProtection(activity, BOLUS, { queryingProtection = false }, cancelFail, cancelFail) + } + } + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/TempTargetDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/TempTargetDialog.kt index c1ed8faf5b..a1885c2d4e 100644 --- a/app/src/main/java/info/nightscout/androidaps/dialogs/TempTargetDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/TempTargetDialog.kt @@ -1,5 +1,6 @@ package info.nightscout.androidaps.dialogs +import android.content.Context import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -26,7 +27,10 @@ import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker import info.nightscout.androidaps.utils.DefaultValueHelper import info.nightscout.androidaps.utils.HtmlHelper +import info.nightscout.androidaps.utils.ToastUtils import info.nightscout.androidaps.utils.alertDialogs.OKDialog +import info.nightscout.androidaps.utils.protection.ProtectionCheck +import info.nightscout.androidaps.utils.protection.ProtectionCheck.Protection.BOLUS import info.nightscout.androidaps.utils.resources.ResourceHelper import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign @@ -43,15 +47,16 @@ class TempTargetDialog : DialogFragmentWithDate() { @Inject lateinit var defaultValueHelper: DefaultValueHelper @Inject lateinit var uel: UserEntryLogger @Inject lateinit var repository: AppRepository + @Inject lateinit var ctx: Context + @Inject lateinit var protectionCheck: ProtectionCheck private lateinit var reasonList: List + private var queryingProtection = false private val disposable = CompositeDisposable() - private var _binding: DialogTemptargetBinding? = null - // This property is only valid between onCreateView and - // onDestroyView. + // This property is only valid between onCreateView and onDestroyView. private val binding get() = _binding!! override fun onSaveInstanceState(savedInstanceState: Bundle) { @@ -60,8 +65,7 @@ class TempTargetDialog : DialogFragmentWithDate() { savedInstanceState.putDouble("tempTarget", binding.temptarget.value) } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?): View { + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { onCreateViewGeneral() _binding = DialogTemptargetBinding.inflate(inflater, container, false) return binding.root @@ -100,10 +104,9 @@ class TempTargetDialog : DialogFragmentWithDate() { rh.gs(R.string.activity), rh.gs(R.string.hypo) ) - val adapterReason = ArrayAdapter(context, R.layout.spinner_centered, reasonList) - binding.reason.adapter = adapterReason + binding.reasonList.setAdapter(ArrayAdapter(context, R.layout.spinner_centered, reasonList)) - binding.targetCancel.setOnClickListener { shortClick(it) } + binding.targetCancel.setOnClickListener { binding.duration.value = 0.0; shortClick(it) } binding.eatingSoon.setOnClickListener { shortClick(it) } binding.activity.setOnClickListener { shortClick(it) } binding.hypo.setOnClickListener { shortClick(it) } @@ -120,8 +123,8 @@ class TempTargetDialog : DialogFragmentWithDate() { longClick(it) return@setOnLongClickListener true } - binding.duration.editText?.id?.let { binding.durationLabel.labelFor = it } - binding.temptarget.editText?.id?.let { binding.temptargetLabel.labelFor = it } + binding.durationLabel.labelFor = binding.duration.editTextId + binding.temptargetLabel.labelFor = binding.temptarget.editTextId } } @@ -135,23 +138,19 @@ class TempTargetDialog : DialogFragmentWithDate() { R.id.eating_soon -> { binding.temptarget.value = defaultValueHelper.determineEatingSoonTT() binding.duration.value = defaultValueHelper.determineEatingSoonTTDuration().toDouble() - binding.reason.setSelection(reasonList.indexOf(rh.gs(R.string.eatingsoon))) + binding.reasonList.setText(rh.gs(R.string.eatingsoon), false) } R.id.activity -> { binding.temptarget.value = defaultValueHelper.determineActivityTT() binding.duration.value = defaultValueHelper.determineActivityTTDuration().toDouble() - binding.reason.setSelection(reasonList.indexOf(rh.gs(R.string.activity))) + binding.reasonList.setText(rh.gs(R.string.activity), false) } R.id.hypo -> { binding.temptarget.value = defaultValueHelper.determineHypoTT() binding.duration.value = defaultValueHelper.determineHypoTTDuration().toDouble() - binding.reason.setSelection(reasonList.indexOf(rh.gs(R.string.hypo))) - } - - R.id.cancel -> { - binding.duration.value = 0.0 + binding.reasonList.setText(rh.gs(R.string.hypo), false) } } } @@ -165,7 +164,7 @@ class TempTargetDialog : DialogFragmentWithDate() { override fun submit(): Boolean { if (_binding == null) return false val actions: LinkedList = LinkedList() - var reason = binding.reason.selectedItem?.toString() ?: return false + var reason = binding.reasonList.text.toString() val unitResId = if (profileFunction.getUnits() == GlucoseUnit.MGDL) R.string.mgdl else R.string.mmol val target = binding.temptarget.value val duration = binding.duration.value.toInt() @@ -222,4 +221,20 @@ class TempTargetDialog : DialogFragmentWithDate() { } return true } + + override fun onResume() { + super.onResume() + if(!queryingProtection) { + queryingProtection = true + activity?.let { activity -> + val cancelFail = { + queryingProtection = false + aapsLogger.debug(LTag.APS, "Dialog canceled on resume protection: ${this.javaClass.name}") + ToastUtils.showToastInUiThread(ctx, R.string.dialog_canceled) + dismiss() + } + protectionCheck.queryProtection(activity, BOLUS, { queryingProtection = false }, cancelFail, cancelFail) + } + } + } } diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/TreatmentDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/TreatmentDialog.kt index 0e8a509517..ff3dbfb30f 100644 --- a/app/src/main/java/info/nightscout/androidaps/dialogs/TreatmentDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/TreatmentDialog.kt @@ -30,6 +30,8 @@ import info.nightscout.shared.SafeParse import info.nightscout.androidaps.utils.ToastUtils import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.extensions.formatColor +import info.nightscout.androidaps.utils.protection.ProtectionCheck +import info.nightscout.androidaps.utils.protection.ProtectionCheck.Protection.BOLUS import info.nightscout.androidaps.utils.resources.ResourceHelper import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign @@ -48,8 +50,14 @@ class TreatmentDialog : DialogFragmentWithDate() { @Inject lateinit var config: Config @Inject lateinit var uel: UserEntryLogger @Inject lateinit var repository: AppRepository + @Inject lateinit var protectionCheck: ProtectionCheck + private var queryingProtection = false private val disposable = CompositeDisposable() + private var _binding: DialogTreatmentBinding? = null + + // This property is only valid between onCreateView and onDestroyView. + private val binding get() = _binding!! private val textWatcher: TextWatcher = object : TextWatcher { override fun afterTextChanged(s: Editable) {} @@ -72,12 +80,6 @@ class TreatmentDialog : DialogFragmentWithDate() { } } - private var _binding: DialogTreatmentBinding? = null - - // This property is only valid between onCreateView and - // onDestroyView. - private val binding get() = _binding!! - override fun onSaveInstanceState(savedInstanceState: Bundle) { super.onSaveInstanceState(savedInstanceState) savedInstanceState.putDouble("carbs", binding.carbs.value) @@ -106,8 +108,8 @@ class TreatmentDialog : DialogFragmentWithDate() { binding.insulin.setParams(savedInstanceState?.getDouble("insulin") ?: 0.0, 0.0, maxInsulin, pumpDescription.bolusStep, DecimalFormatter.pumpSupportedBolusFormat(activePlugin.activePump), false, binding.okcancel.ok, textWatcher) binding.recordOnlyLayout.visibility = View.GONE - binding.insulin.editText?.id?.let { binding.insulinLabel.labelFor = it } - binding.carbs.editText?.id?.let { binding.carbsLabel.labelFor = it } + binding.insulinLabel.labelFor = binding.insulin.editTextId + binding.carbsLabel.labelFor = binding.carbs.editTextId } override fun onDestroyView() { @@ -126,16 +128,16 @@ class TreatmentDialog : DialogFragmentWithDate() { val carbsAfterConstraints = constraintChecker.applyCarbsConstraints(Constraint(carbs)).value() if (insulinAfterConstraints > 0) { - actions.add(rh.gs(R.string.bolus) + ": " + DecimalFormatter.toPumpSupportedBolus(insulinAfterConstraints, activePlugin.activePump, rh).formatColor(rh, R.color.bolus)) + actions.add(rh.gs(R.string.bolus) + ": " + DecimalFormatter.toPumpSupportedBolus(insulinAfterConstraints, activePlugin.activePump, rh).formatColor(context, rh, R.attr.bolusColor)) if (recordOnlyChecked) - actions.add(rh.gs(R.string.bolusrecordedonly).formatColor(rh, R.color.warning)) + actions.add(rh.gs(R.string.bolusrecordedonly).formatColor(context, rh, R.attr.warningColor)) if (abs(insulinAfterConstraints - insulin) > pumpDescription.pumpType.determineCorrectBolusStepSize(insulinAfterConstraints)) - actions.add(rh.gs(R.string.bolusconstraintappliedwarn, insulin, insulinAfterConstraints).formatColor(rh, R.color.warning)) + actions.add(rh.gs(R.string.bolusconstraintappliedwarn, insulin, insulinAfterConstraints).formatColor(context, rh, R.attr.warningColor)) } if (carbsAfterConstraints > 0) { - actions.add(rh.gs(R.string.carbs) + ": " + rh.gs(R.string.format_carbs, carbsAfterConstraints).formatColor(rh, R.color.carbs)) + actions.add(rh.gs(R.string.carbs) + ": " + rh.gs(R.string.format_carbs, carbsAfterConstraints).formatColor(context, rh, R.attr.carbsColor)) if (carbsAfterConstraints != carbs) - actions.add(rh.gs(R.string.carbsconstraintapplied).formatColor(rh, R.color.warning)) + actions.add(rh.gs(R.string.carbsconstraintapplied).formatColor(context, rh, R.attr.warningColor)) } if (insulinAfterConstraints > 0 || carbsAfterConstraints > 0) { activity?.let { activity -> @@ -201,4 +203,20 @@ class TreatmentDialog : DialogFragmentWithDate() { } return true } -} \ No newline at end of file + + override fun onResume() { + super.onResume() + if(!queryingProtection) { + queryingProtection = true + activity?.let { activity -> + val cancelFail = { + queryingProtection = false + aapsLogger.debug(LTag.APS, "Dialog canceled on resume protection: ${this.javaClass.name}") + ToastUtils.showToastInUiThread(ctx, R.string.dialog_canceled) + dismiss() + } + protectionCheck.queryProtection(activity, BOLUS, { queryingProtection = false }, cancelFail, cancelFail) + } + } + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/WizardDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/WizardDialog.kt index fbfc23c17b..841344fae3 100644 --- a/app/src/main/java/info/nightscout/androidaps/dialogs/WizardDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/WizardDialog.kt @@ -1,12 +1,16 @@ package info.nightscout.androidaps.dialogs +import android.annotation.SuppressLint import android.content.Context import android.os.Bundle import android.text.Editable import android.text.TextWatcher -import android.view.* +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.Window +import android.view.WindowManager import android.widget.AdapterView -import android.widget.AdapterView.OnItemSelectedListener import android.widget.ArrayAdapter import android.widget.CompoundButton import androidx.fragment.app.FragmentManager @@ -20,22 +24,24 @@ import info.nightscout.androidaps.database.ValueWrapper import info.nightscout.androidaps.databinding.DialogWizardBinding import info.nightscout.androidaps.events.EventAutosensCalculationFinished import info.nightscout.androidaps.extensions.formatColor -import info.nightscout.shared.logging.AAPSLogger -import info.nightscout.shared.logging.LTag -import info.nightscout.androidaps.plugins.bus.RxBus -import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker -import info.nightscout.shared.SafeParse import info.nightscout.androidaps.extensions.toVisibility import info.nightscout.androidaps.extensions.valueToUnits import info.nightscout.androidaps.interfaces.* +import info.nightscout.androidaps.plugins.bus.RxBus +import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker import info.nightscout.androidaps.utils.* +import info.nightscout.androidaps.utils.protection.ProtectionCheck +import info.nightscout.androidaps.utils.protection.ProtectionCheck.Protection.BOLUS import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.rx.AapsSchedulers -import info.nightscout.shared.sharedPreferences.SP import info.nightscout.androidaps.utils.wizard.BolusWizard +import info.nightscout.shared.SafeParse +import info.nightscout.shared.logging.AAPSLogger +import info.nightscout.shared.logging.LTag +import info.nightscout.shared.sharedPreferences.SP import io.reactivex.rxjava3.disposables.CompositeDisposable +import io.reactivex.rxjava3.kotlin.plusAssign import java.text.DecimalFormat -import java.util.* import javax.inject.Inject import kotlin.math.abs @@ -55,16 +61,22 @@ class WizardDialog : DaggerDialogFragment() { @Inject lateinit var iobCobCalculator: IobCobCalculator @Inject lateinit var repository: AppRepository @Inject lateinit var dateUtil: DateUtil + @Inject lateinit var protectionCheck: ProtectionCheck + private var queryingProtection = false private var wizard: BolusWizard? = null private var calculatedPercentage = 100.0 private var calculatedCorrection = 0.0 private var correctionPercent = false private var carbsPassedIntoWizard = 0.0 private var notesPassedIntoWizard = "" + private var okClicked: Boolean = false // one shot guards + private var disposable: CompositeDisposable = CompositeDisposable() + private var bolusStep = 0.0 + private var _binding: DialogWizardBinding? = null - //one shot guards - private var okClicked: Boolean = false + // This property is only valid between onCreateView and onDestroyView. + private val binding get() = _binding!! private val textWatcher = object : TextWatcher { override fun afterTextChanged(s: Editable) {} @@ -83,15 +95,6 @@ class WizardDialog : DaggerDialogFragment() { } } - private var disposable: CompositeDisposable = CompositeDisposable() - private var bolusStep = 0.0 - - private var _binding: DialogWizardBinding? = null - - // This property is only valid between onCreateView and - // onDestroyView. - private val binding get() = _binding!! - override fun onStart() { super.onStart() dialog?.window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) @@ -106,10 +109,9 @@ class WizardDialog : DaggerDialogFragment() { savedInstanceState.putDouble("carb_time_input", binding.carbTimeInput.value) } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?): View { + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { this.arguments?.let { bundle -> - carbsPassedIntoWizard = bundle.getInt("carbs_input").toDouble() + carbsPassedIntoWizard = bundle.getDouble("carbs_input") notesPassedIntoWizard = bundle.getString("notes_input").toString() } @@ -128,7 +130,7 @@ class WizardDialog : DaggerDialogFragment() { val useSuperBolus = sp.getBoolean(R.string.key_usesuperbolus, false) binding.sbCheckbox.visibility = useSuperBolus.toVisibility() binding.superBolusRow.visibility = useSuperBolus.toVisibility() - binding.notesLayout.visibility = sp.getBoolean(R.string.key_show_notes_entry_dialogs, false).toVisibility() + binding.notesLayout.root.visibility = sp.getBoolean(R.string.key_show_notes_entry_dialogs, false).toVisibility() val maxCarbs = constraintChecker.getMaxCarbsAllowed().value() val maxCorrection = constraintChecker.getMaxBolusAllowed().value() @@ -137,14 +139,18 @@ class WizardDialog : DaggerDialogFragment() { if (profileFunction.getUnits() == GlucoseUnit.MGDL) { binding.bgInput.setParams( savedInstanceState?.getDouble("bg_input") - ?: 0.0, 0.0, 500.0, 1.0, DecimalFormat("0"), false, binding.okcancel.ok, timeTextWatcher) + ?: 0.0, 0.0, 500.0, 1.0, DecimalFormat("0"), false, binding.okcancel.ok, timeTextWatcher + ) } else { binding.bgInput.setParams( savedInstanceState?.getDouble("bg_input") - ?: 0.0, 0.0, 30.0, 0.1, DecimalFormat("0.0"), false, binding.okcancel.ok, textWatcher) + ?: 0.0, 0.0, 30.0, 0.1, DecimalFormat("0.0"), false, binding.okcancel.ok, textWatcher + ) } - binding.carbsInput.setParams(savedInstanceState?.getDouble("carbs_input") - ?: 0.0, 0.0, maxCarbs.toDouble(), 1.0, DecimalFormat("0"), false, binding.okcancel.ok, textWatcher) + binding.carbsInput.setParams( + savedInstanceState?.getDouble("carbs_input") + ?: 0.0, 0.0, maxCarbs.toDouble(), 1.0, DecimalFormat("0"), false, binding.okcancel.ok, textWatcher + ) if (correctionPercent) { calculatedPercentage = sp.getInt(R.string.key_boluswizard_percentage, 100).toDouble() @@ -154,11 +160,14 @@ class WizardDialog : DaggerDialogFragment() { } else { binding.correctionInput.setParams( savedInstanceState?.getDouble("correction_input") - ?: 0.0, -maxCorrection, maxCorrection, bolusStep, DecimalFormatter.pumpSupportedBolusFormat(activePlugin.activePump), false, binding.okcancel.ok, textWatcher) + ?: 0.0, -maxCorrection, maxCorrection, bolusStep, DecimalFormatter.pumpSupportedBolusFormat(activePlugin.activePump), false, binding.okcancel.ok, textWatcher + ) binding.correctionUnit.text = rh.gs(R.string.insulin_unit_shortname) } - binding.carbTimeInput.setParams(savedInstanceState?.getDouble("carb_time_input") - ?: 0.0, -60.0, 60.0, 5.0, DecimalFormat("0"), false, binding.okcancel.ok, timeTextWatcher) + binding.carbTimeInput.setParams( + savedInstanceState?.getDouble("carb_time_input") + ?: 0.0, -60.0, 60.0, 5.0, DecimalFormat("0"), false, binding.okcancel.ok, timeTextWatcher + ) initDialog() calculatedPercentage = sp.getInt(R.string.key_boluswizard_percentage, 100).toDouble() binding.percentUsed.text = rh.gs(R.string.format_percent, sp.getInt(R.string.key_boluswizard_percentage, 100)) @@ -209,7 +218,7 @@ class WizardDialog : DaggerDialogFragment() { processEnabledIcons() - binding.correctionPercent.setOnCheckedChangeListener {_, isChecked -> + binding.correctionPercent.setOnCheckedChangeListener { _, isChecked -> run { sp.putBoolean(rh.gs(R.string.key_wizard_correction_percent), isChecked) binding.correctionUnit.text = if (isChecked) "%" else rh.gs(R.string.insulin_unit_shortname) @@ -228,35 +237,21 @@ class WizardDialog : DaggerDialogFragment() { binding.correctionInput.value = if (correctionPercent) calculatedPercentage else Round.roundTo(calculatedCorrection, bolusStep) } } - // profile spinner - binding.profile.onItemSelectedListener = object : OnItemSelectedListener { - override fun onNothingSelected(parent: AdapterView<*>?) { - ToastUtils.showToastInUiThread(ctx, rh.gs(R.string.noprofileset)) - binding.okcancel.ok.visibility = View.GONE - } - - override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { - calculateInsulin() - binding.okcancel.ok.visibility = View.VISIBLE - } - } + // profile + binding.profileList.onItemClickListener = AdapterView.OnItemClickListener { _, _, _, _ -> calculateInsulin() } // bus - disposable.add(rxBus + disposable += rxBus .toObservable(EventAutosensCalculationFinished::class.java) .observeOn(aapsSchedulers.main) - .subscribe({ - activity?.runOnUiThread { calculateInsulin() } - }, fabricPrivacy::logException) - ) - + .subscribe({ calculateInsulin() }, fabricPrivacy::logException) setA11yLabels() } private fun setA11yLabels() { - binding.bgInput.editText?.id?.let { binding.bgInputLabel.labelFor = it } - binding.carbsInput.editText?.id?.let { binding.carbsInputLabel.labelFor = it } - binding.correctionInput.editText?.id?.let { binding.correctionInputLabel.labelFor = it } - binding.carbTimeInput.editText?.id?.let { binding.carbTimeInputLabel.labelFor = it } + binding.bgInputLabel.labelFor = binding.bgInput.editTextId + binding.carbsInputLabel.labelFor = binding.carbsInput.editTextId + binding.correctionInputLabel.labelFor = binding.correctionInput.editTextId + binding.carbTimeInputLabel.labelFor = binding.carbTimeInput.editTextId } override fun onDestroyView() { @@ -307,6 +302,7 @@ class WizardDialog : DaggerDialogFragment() { binding.trendCheckboxIcon.visibility = binding.calculationCheckbox.isChecked.not().toVisibility() binding.iobCheckboxIcon.visibility = binding.calculationCheckbox.isChecked.not().toVisibility() binding.cobCheckboxIcon.visibility = binding.calculationCheckbox.isChecked.not().toVisibility() + binding.checkboxRow.visibility = binding.calculationCheckbox.isChecked.not().toVisibility() } private fun saveCheckedStates() { @@ -318,7 +314,7 @@ class WizardDialog : DaggerDialogFragment() { private fun loadCheckedStates() { binding.bgTrendCheckbox.isChecked = sp.getBoolean(R.string.key_wizard_include_trend_bg, false) binding.cobCheckbox.isChecked = sp.getBoolean(R.string.key_wizard_include_cob, false) - correctionPercent = sp.getBoolean(R.string.key_wizard_correction_percent,false) + correctionPercent = sp.getBoolean(R.string.key_wizard_correction_percent, false) binding.correctionPercent.isChecked = correctionPercent } @@ -327,11 +323,11 @@ class WizardDialog : DaggerDialogFragment() { else DecimalFormatter.to1Decimal(value * Constants.MGDL_TO_MMOLL) private fun initDialog() { - if(carbsPassedIntoWizard != 0.0) { + if (carbsPassedIntoWizard != 0.0) { binding.carbsInput.value = carbsPassedIntoWizard } - if(notesPassedIntoWizard.isNotBlank()) { - binding.notes.setText(notesPassedIntoWizard) + if (notesPassedIntoWizard.isNotBlank()) { + binding.notesLayout.notes.setText(notesPassedIntoWizard) } val profile = profileFunction.getProfile() val profileStore = activePlugin.activeProfileSource.profile @@ -345,9 +341,9 @@ class WizardDialog : DaggerDialogFragment() { val profileList: ArrayList = profileStore.getProfileList() profileList.add(0, rh.gs(R.string.active)) context?.let { context -> - val adapter = ArrayAdapter(context, R.layout.spinner_centered, profileList) - binding.profile.adapter = adapter - } ?: return + binding.profileList.setAdapter(ArrayAdapter(context, R.layout.spinner_centered, profileList)) + binding.profileList.setText(profileList[0], false) + } val units = profileFunction.getUnits() binding.bgUnits.text = units.asText @@ -370,11 +366,10 @@ class WizardDialog : DaggerDialogFragment() { binding.percentUsed.visibility = (sp.getInt(R.string.key_boluswizard_percentage, 100) != 100 || correctionPercent).toVisibility() } + @SuppressLint("SetTextI18n") private fun calculateInsulin() { - val profileStore = activePlugin.activeProfileSource.profile - if (binding.profile.selectedItem == null || profileStore == null) - return // not initialized yet - var profileName = binding.profile.selectedItem.toString() + val profileStore = activePlugin.activeProfileSource.profile ?: return // not initialized yet + var profileName = binding.profileList.text.toString() val specificProfile: Profile? if (profileName == rh.gs(R.string.active)) { specificProfile = profileFunction.getProfile() @@ -396,7 +391,7 @@ class WizardDialog : DaggerDialogFragment() { } else 0.0 val percentageCorrection = if (usePercentage) { - if (Round.roundTo(calculatedPercentage,1.0) == SafeParse.stringToDouble(binding.correctionInput.text)) + if (Round.roundTo(calculatedPercentage, 1.0) == SafeParse.stringToDouble(binding.correctionInput.text)) calculatedPercentage else SafeParse.stringToDouble(binding.correctionInput.text) @@ -422,7 +417,8 @@ class WizardDialog : DaggerDialogFragment() { val carbTime = SafeParse.stringToInt(binding.carbTimeInput.text) - wizard = BolusWizard(injector).doCalc(specificProfile, profileName, tempTarget, carbsAfterConstraint, cob, bg, correction, sp.getInt(R.string.key_boluswizard_percentage, 100), + wizard = BolusWizard(injector).doCalc( + specificProfile, profileName, tempTarget, carbsAfterConstraint, cob, bg, correction, sp.getInt(R.string.key_boluswizard_percentage, 100), binding.bgCheckbox.isChecked, binding.cobCheckbox.isChecked, binding.iobCheckbox.isChecked, @@ -431,17 +427,17 @@ class WizardDialog : DaggerDialogFragment() { binding.ttCheckbox.isChecked, binding.bgTrendCheckbox.isChecked, binding.alarm.isChecked, - binding.notes.text.toString(), + binding.notesLayout.notes.text.toString(), carbTime, usePercentage = usePercentage, totalPercentage = percentageCorrection ) wizard?.let { wizard -> - binding.bg.text = String.format(rh.gs(R.string.format_bg_isf), valueToUnitsToString(Profile.toMgdl(bg, profileFunction.getUnits()), profileFunction.getUnits().asText), wizard.sens) + binding.bg.text = rh.gs(R.string.format_bg_isf, valueToUnitsToString(Profile.toMgdl(bg, profileFunction.getUnits()), profileFunction.getUnits().asText), wizard.sens) binding.bgInsulin.text = rh.gs(R.string.formatinsulinunits, wizard.insulinFromBG) - binding.carbs.text = String.format(rh.gs(R.string.format_carbs_ic), carbs.toDouble(), wizard.ic) + binding.carbs.text = rh.gs(R.string.format_carbs_ic, carbs.toDouble(), wizard.ic) binding.carbsInsulin.text = rh.gs(R.string.formatinsulinunits, wizard.insulinFromCarbs) binding.iobInsulin.text = rh.gs(R.string.formatinsulinunits, wizard.insulinFromBolusIOB + wizard.insulinFromBasalIOB) @@ -464,7 +460,7 @@ class WizardDialog : DaggerDialogFragment() { // COB if (binding.cobCheckbox.isChecked) { - binding.cob.text = String.format(rh.gs(R.string.format_cob_ic), cob, wizard.ic) + binding.cob.text = rh.gs(R.string.format_cob_ic, cob, wizard.ic) binding.cobInsulin.text = rh.gs(R.string.formatinsulinunits, wizard.insulinFromCOB) } else { binding.cob.text = "" @@ -472,12 +468,12 @@ class WizardDialog : DaggerDialogFragment() { } if (wizard.calculatedTotalInsulin > 0.0 || carbsAfterConstraint > 0.0) { - val insulinText = if (wizard.calculatedTotalInsulin > 0.0) rh.gs(R.string.formatinsulinunits, wizard.calculatedTotalInsulin).formatColor(rh, R.color.bolus) else "" - val carbsText = if (carbsAfterConstraint > 0.0) rh.gs(R.string.format_carbs, carbsAfterConstraint).formatColor(rh, R.color.carbs) else "" + val insulinText = if (wizard.calculatedTotalInsulin > 0.0) rh.gs(R.string.formatinsulinunits, wizard.calculatedTotalInsulin).formatColor(context, rh, R.attr.bolusColor) else "" + val carbsText = if (carbsAfterConstraint > 0.0) rh.gs(R.string.format_carbs, carbsAfterConstraint).formatColor(context, rh, R.attr.carbsColor) else "" binding.total.text = HtmlHelper.fromHtml(rh.gs(R.string.result_insulin_carbs, insulinText, carbsText)) binding.okcancel.ok.visibility = View.VISIBLE } else { - binding.total.text = HtmlHelper.fromHtml(rh.gs(R.string.missing_carbs, wizard.carbsEquivalent.toInt()).formatColor(rh, R.color.carbs)) + binding.total.text = HtmlHelper.fromHtml(rh.gs(R.string.missing_carbs, wizard.carbsEquivalent.toInt()).formatColor(context, rh, R.attr.carbsColor)) binding.okcancel.ok.visibility = View.INVISIBLE } binding.percentUsed.text = rh.gs(R.string.format_percent, wizard.percentageCorrection) @@ -497,4 +493,20 @@ class WizardDialog : DaggerDialogFragment() { aapsLogger.debug(e.localizedMessage ?: "") } } + + override fun onResume() { + super.onResume() + if(!queryingProtection) { + queryingProtection = true + activity?.let { activity -> + val cancelFail = { + queryingProtection = false + aapsLogger.debug(LTag.APS, "Dialog canceled on resume protection: ${this.javaClass.name}") + ToastUtils.showToastInUiThread(ctx, R.string.dialog_canceled) + dismiss() + } + protectionCheck.queryProtection(activity, BOLUS, { queryingProtection = false }, cancelFail, cancelFail) + } + } + } } diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventReloadProfileSwitchData.kt b/app/src/main/java/info/nightscout/androidaps/events/EventReloadProfileSwitchData.kt deleted file mode 100644 index 6f6d848b5e..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/events/EventReloadProfileSwitchData.kt +++ /dev/null @@ -1,3 +0,0 @@ -package info.nightscout.androidaps.events - -class EventReloadProfileSwitchData : Event() diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventTreatmentUpdateGui.kt b/app/src/main/java/info/nightscout/androidaps/events/EventTreatmentUpdateGui.kt deleted file mode 100644 index 4a32b3c659..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/events/EventTreatmentUpdateGui.kt +++ /dev/null @@ -1,3 +0,0 @@ -package info.nightscout.androidaps.events - -class EventTreatmentUpdateGui : EventUpdateGui() \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/events/EventLoopInvoked.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/events/EventLoopInvoked.kt deleted file mode 100644 index e90e8cfb88..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/events/EventLoopInvoked.kt +++ /dev/null @@ -1,5 +0,0 @@ -package info.nightscout.androidaps.plugins.aps.events - -import info.nightscout.androidaps.events.Event - -class EventLoopInvoked : Event() \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopPlugin.kt index e43c353d87..fa8c085404 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopPlugin.kt @@ -10,11 +10,13 @@ import android.content.Intent import android.os.SystemClock import androidx.core.app.NotificationCompat import dagger.android.HasAndroidInjector -import info.nightscout.androidaps.* +import info.nightscout.androidaps.BuildConfig +import info.nightscout.androidaps.Constants +import info.nightscout.androidaps.MainActivity +import info.nightscout.androidaps.R import info.nightscout.androidaps.activities.ErrorHelperActivity import info.nightscout.androidaps.annotations.OpenForTesting import info.nightscout.androidaps.data.DetailedBolusInfo -import info.nightscout.androidaps.interfaces.Profile import info.nightscout.androidaps.data.PumpEnactResult import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.ValueWrapper @@ -25,13 +27,13 @@ import info.nightscout.androidaps.database.entities.ValueWithUnit import info.nightscout.androidaps.database.transactions.InsertAndCancelCurrentOfflineEventTransaction import info.nightscout.androidaps.database.transactions.InsertTherapyEventAnnouncementTransaction import info.nightscout.androidaps.events.EventAcceptOpenLoopChange -import info.nightscout.androidaps.events.EventAutosensCalculationFinished -import info.nightscout.androidaps.events.EventNewBG import info.nightscout.androidaps.events.EventTempTargetChange +import info.nightscout.androidaps.extensions.buildDeviceStatus +import info.nightscout.androidaps.extensions.convertedToAbsolute +import info.nightscout.androidaps.extensions.convertedToPercent +import info.nightscout.androidaps.extensions.plannedRemainingMinutes import info.nightscout.androidaps.interfaces.* import info.nightscout.androidaps.interfaces.Loop.LastRun -import info.nightscout.shared.logging.AAPSLogger -import info.nightscout.shared.logging.LTag import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.plugins.aps.loop.events.EventLoopSetLastRunGui import info.nightscout.androidaps.plugins.aps.loop.events.EventLoopUpdateGui @@ -51,13 +53,10 @@ import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.HardLimits import info.nightscout.androidaps.utils.T -import info.nightscout.androidaps.extensions.buildDeviceStatus -import info.nightscout.androidaps.extensions.convertedToAbsolute -import info.nightscout.androidaps.extensions.convertedToPercent -import info.nightscout.androidaps.extensions.plannedRemainingMinutes -import info.nightscout.androidaps.plugins.aps.events.EventLoopInvoked import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.rx.AapsSchedulers +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 @@ -88,20 +87,21 @@ class LoopPlugin @Inject constructor( private val uel: UserEntryLogger, private val repository: AppRepository, private val runningConfiguration: RunningConfiguration -) : PluginBase(PluginDescription() - .mainType(PluginType.LOOP) - .fragmentClass(LoopFragment::class.java.name) - .pluginIcon(R.drawable.ic_loop_closed_white) - .pluginName(R.string.loop) - .shortName(R.string.loop_shortname) - .preferencesId(R.xml.pref_loop) - .enableByDefault(config.APS) - .description(R.string.description_loop), +) : PluginBase( + PluginDescription() + .mainType(PluginType.LOOP) + .fragmentClass(LoopFragment::class.java.name) + .pluginIcon(R.drawable.ic_loop_closed_white) + .pluginName(R.string.loop) + .shortName(R.string.loop_shortname) + .preferencesId(R.xml.pref_loop) + .enableByDefault(config.APS) + .description(R.string.description_loop), aapsLogger, rh, injector ), Loop { private val disposable = CompositeDisposable() - private var lastBgTriggeredRun: Long = 0 + override var lastBgTriggeredRun: Long = 0 private var carbsSuggestionsSuspendedUntil: Long = 0 private var prevCarbsreq = 0 override var lastRun: LastRun? = null @@ -109,39 +109,19 @@ class LoopPlugin @Inject constructor( override fun onStart() { createNotificationChannel() super.onStart() - disposable.add(rxBus + disposable += rxBus .toObservable(EventTempTargetChange::class.java) .observeOn(aapsSchedulers.io) .subscribe({ invoke("EventTempTargetChange", true) }, fabricPrivacy::logException) - ) - /* - This method is triggered once autosens calculation has completed, so the LoopPlugin - has current data to work with. However, autosens calculation can be triggered by multiple - sources and currently only a new BG should trigger a loop run. Hence we return early if - the event causing the calculation is not EventNewBg. -

- */ - disposable.add(rxBus - .toObservable(EventAutosensCalculationFinished::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ event: EventAutosensCalculationFinished -> - // Autosens calculation not triggered by a new BG - if (event.cause !is EventNewBG) return@subscribe - val glucoseValue = iobCobCalculator.ads.actualBg() ?: return@subscribe - // BG outdated - // already looped with that value - if (glucoseValue.timestamp <= lastBgTriggeredRun) return@subscribe - lastBgTriggeredRun = glucoseValue.timestamp - invoke("AutosenseCalculation for $glucoseValue", true) - }, fabricPrivacy::logException) - ) } private fun createNotificationChannel() { val mNotificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - @SuppressLint("WrongConstant") val channel = NotificationChannel(CHANNEL_ID, + @SuppressLint("WrongConstant") val channel = NotificationChannel( CHANNEL_ID, - NotificationManager.IMPORTANCE_HIGH) + CHANNEL_ID, + NotificationManager.IMPORTANCE_HIGH + ) mNotificationManager.createNotificationChannel(channel) } @@ -255,7 +235,7 @@ class LoopPlugin @Inject constructor( if (apsResult == null) { rxBus.send(EventLoopSetLastRunGui(rh.gs(R.string.noapsselected))) return - } else rxBus.send(EventLoopInvoked()) + } if (!isEmptyQueue()) { aapsLogger.debug(LTag.APS, rh.gs(R.string.pumpbusy)) @@ -296,9 +276,11 @@ class LoopPlugin @Inject constructor( lastRun.lastTBRRequest = 0 lastRun.lastSMBEnact = 0 lastRun.lastSMBRequest = 0 - buildDeviceStatus(dateUtil, this, iobCobCalculator, profileFunction, + buildDeviceStatus( + dateUtil, this, iobCobCalculator, profileFunction, activePlugin.activePump, receiverStatusStore, runningConfiguration, - BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION)?.also { + BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION + )?.also { repository.insert(it) } @@ -316,7 +298,11 @@ class LoopPlugin @Inject constructor( if (closedLoopEnabled.value()) { if (allowNotification) { if (resultAfterConstraints.isCarbsRequired - && resultAfterConstraints.carbsReq >= sp.getInt(R.string.key_smb_enable_carbs_suggestions_threshold, 0) && carbsSuggestionsSuspendedUntil < System.currentTimeMillis() && !treatmentTimeThreshold(-15)) { + && resultAfterConstraints.carbsReq >= sp.getInt( + R.string.key_smb_enable_carbs_suggestions_threshold, + 0 + ) && carbsSuggestionsSuspendedUntil < System.currentTimeMillis() && !treatmentTimeThreshold(-15) + ) { if (sp.getBoolean(R.string.key_enable_carbs_required_alert_local, true) && !sp.getBoolean(R.string.key_raise_notifications_as_android_notifications, true)) { val carbReqLocal = Notification(Notification.CARBS_REQUIRED, resultAfterConstraints.carbsRequiredText, Notification.NORMAL) rxBus.send(EventNewNotification(carbReqLocal)) @@ -353,9 +339,11 @@ class LoopPlugin @Inject constructor( // mId allows you to update the notification later on. mNotificationManager.notify(Constants.notificationID, builder.build()) - uel.log(Action.CAREPORTAL, Sources.Loop, rh.gs(R.string.carbsreq, resultAfterConstraints.carbsReq, resultAfterConstraints.carbsReqWithin), - ValueWithUnit.Gram(resultAfterConstraints.carbsReq), - ValueWithUnit.Minute(resultAfterConstraints.carbsReqWithin)) + uel.log( + Action.CAREPORTAL, Sources.Loop, rh.gs(R.string.carbsreq, resultAfterConstraints.carbsReq, resultAfterConstraints.carbsReqWithin), + ValueWithUnit.Gram(resultAfterConstraints.carbsReq), + ValueWithUnit.Minute(resultAfterConstraints.carbsReqWithin) + ) rxBus.send(EventNewOpenLoopNotification()) //only send to wear if Native notifications are turned off @@ -373,7 +361,8 @@ class LoopPlugin @Inject constructor( } } if (resultAfterConstraints.isChangeRequested - && !commandQueue.bolusInQueue()) { + && !commandQueue.bolusInQueue() + ) { val waiting = PumpEnactResult(injector) waiting.queued = true if (resultAfterConstraints.tempBasalRequested) lastRun.tbrSetByPump = waiting @@ -486,9 +475,11 @@ class LoopPlugin @Inject constructor( lastRun.lastTBRRequest = lastRun.lastAPSRun lastRun.lastTBREnact = dateUtil.now() lastRun.lastOpenModeAccept = dateUtil.now() - buildDeviceStatus(dateUtil, this@LoopPlugin, iobCobCalculator, profileFunction, + buildDeviceStatus( + dateUtil, this@LoopPlugin, iobCobCalculator, profileFunction, activePlugin.activePump, receiverStatusStore, runningConfiguration, - BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION)?.also { + BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION + )?.also { repository.insert(it) } sp.incInt(R.string.key_ObjectivesmanualEnacts) @@ -531,8 +522,10 @@ class LoopPlugin @Inject constructor( commandQueue.cancelTempBasal(false, callback) } else { aapsLogger.debug(LTag.APS, "applyAPSRequest: Basal set correctly") - callback?.result(PumpEnactResult(injector).absolute(request.rate).duration(0) - .enacted(false).success(true).comment(R.string.basal_set_correctly))?.run() + callback?.result( + PumpEnactResult(injector).absolute(request.rate).duration(0) + .enacted(false).success(true).comment(R.string.basal_set_correctly) + )?.run() } } else if (request.usePercent && allowPercentage()) { if (request.percent == 100 && request.duration == 0) { @@ -542,32 +535,52 @@ class LoopPlugin @Inject constructor( commandQueue.cancelTempBasal(false, callback) } else { aapsLogger.debug(LTag.APS, "applyAPSRequest: Basal set correctly") - callback?.result(PumpEnactResult(injector).percent(request.percent).duration(0) - .enacted(false).success(true).comment(R.string.basal_set_correctly))?.run() + callback?.result( + PumpEnactResult(injector).percent(request.percent).duration(0) + .enacted(false).success(true).comment(R.string.basal_set_correctly) + )?.run() } - } else if (activeTemp != null && activeTemp.plannedRemainingMinutes > 5 && request.duration - activeTemp.plannedRemainingMinutes < 30 && request.percent == activeTemp.convertedToPercent(now, profile)) { + } else if (activeTemp != null && activeTemp.plannedRemainingMinutes > 5 && request.duration - activeTemp.plannedRemainingMinutes < 30 && request.percent == activeTemp.convertedToPercent( + now, + profile + ) + ) { aapsLogger.debug(LTag.APS, "applyAPSRequest: Temp basal set correctly") - callback?.result(PumpEnactResult(injector).percent(request.percent) - .enacted(false).success(true).duration(activeTemp.plannedRemainingMinutes) - .comment(R.string.let_temp_basal_run))?.run() + callback?.result( + PumpEnactResult(injector).percent(request.percent) + .enacted(false).success(true).duration(activeTemp.plannedRemainingMinutes) + .comment(R.string.let_temp_basal_run) + )?.run() } else { aapsLogger.debug(LTag.APS, "applyAPSRequest: tempBasalPercent()") - uel.log(Action.TEMP_BASAL, Sources.Loop, + uel.log( + Action.TEMP_BASAL, Sources.Loop, ValueWithUnit.Percent(request.percent), - ValueWithUnit.Minute(request.duration)) + ValueWithUnit.Minute(request.duration) + ) commandQueue.tempBasalPercent(request.percent, request.duration, false, profile, PumpSync.TemporaryBasalType.NORMAL, callback) } } else { - if (activeTemp != null && activeTemp.plannedRemainingMinutes > 5 && request.duration - activeTemp.plannedRemainingMinutes < 30 && abs(request.rate - activeTemp.convertedToAbsolute(now, profile)) < pump.pumpDescription.basalStep) { + if (activeTemp != null && activeTemp.plannedRemainingMinutes > 5 && request.duration - activeTemp.plannedRemainingMinutes < 30 && abs( + request.rate - activeTemp.convertedToAbsolute( + now, + profile + ) + ) < pump.pumpDescription.basalStep + ) { aapsLogger.debug(LTag.APS, "applyAPSRequest: Temp basal set correctly") - callback?.result(PumpEnactResult(injector).absolute(activeTemp.convertedToAbsolute(now, profile)) - .enacted(false).success(true).duration(activeTemp.plannedRemainingMinutes) - .comment(R.string.let_temp_basal_run))?.run() + callback?.result( + PumpEnactResult(injector).absolute(activeTemp.convertedToAbsolute(now, profile)) + .enacted(false).success(true).duration(activeTemp.plannedRemainingMinutes) + .comment(R.string.let_temp_basal_run) + )?.run() } else { aapsLogger.debug(LTag.APS, "applyAPSRequest: setTempBasalAbsolute()") - uel.log(Action.TEMP_BASAL, Sources.Loop, + uel.log( + Action.TEMP_BASAL, Sources.Loop, ValueWithUnit.UnitPerHour(request.rate), - ValueWithUnit.Minute(request.duration)) + ValueWithUnit.Minute(request.duration) + ) commandQueue.tempBasalAbsolute(request.rate, request.duration, false, profile, PumpSync.TemporaryBasalType.NORMAL, callback) } } @@ -581,9 +594,11 @@ class LoopPlugin @Inject constructor( val lastBolusTime = repository.getLastBolusRecord()?.timestamp ?: 0L if (lastBolusTime != 0L && lastBolusTime + 3 * 60 * 1000 > System.currentTimeMillis()) { aapsLogger.debug(LTag.APS, "SMB requested but still in 3 min interval") - callback?.result(PumpEnactResult(injector) - .comment(R.string.smb_frequency_exceeded) - .enacted(false).success(false))?.run() + callback?.result( + PumpEnactResult(injector) + .comment(R.string.smb_frequency_exceeded) + .enacted(false).success(false) + )?.run() return } if (!pump.isInitialized()) { @@ -619,11 +634,11 @@ class LoopPlugin @Inject constructor( val pump = activePlugin.activePump disposable += repository.runTransactionForResult(InsertAndCancelCurrentOfflineEventTransaction(dateUtil.now(), T.mins(durationInMinutes.toLong()).msecs(), reason)) .subscribe({ result -> - result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated OfflineEvent $it") } - result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted OfflineEvent $it") } - }, { - aapsLogger.error(LTag.DATABASE, "Error while saving OfflineEvent", it) - }) + result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated OfflineEvent $it") } + result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted OfflineEvent $it") } + }, { + aapsLogger.error(LTag.DATABASE, "Error while saving OfflineEvent", it) + }) if (pump.pumpDescription.tempBasalStyle == PumpDescription.ABSOLUTE) { commandQueue.tempBasalAbsolute(0.0, durationInMinutes, true, profile, PumpSync.TemporaryBasalType.EMULATED_PUMP_SUSPEND, object : Callback() { override fun run() { @@ -655,11 +670,11 @@ class LoopPlugin @Inject constructor( override fun suspendLoop(durationInMinutes: Int) { disposable += repository.runTransactionForResult(InsertAndCancelCurrentOfflineEventTransaction(dateUtil.now(), T.mins(durationInMinutes.toLong()).msecs(), OfflineEvent.Reason.SUSPEND)) .subscribe({ result -> - result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated OfflineEvent $it") } - result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted OfflineEvent $it") } - }, { - aapsLogger.error(LTag.DATABASE, "Error while saving OfflineEvent", it) - }) + result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated OfflineEvent $it") } + result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted OfflineEvent $it") } + }, { + aapsLogger.error(LTag.DATABASE, "Error while saving OfflineEvent", it) + }) commandQueue.cancelTempBasal(true, object : Callback() { override fun run() { if (!result.success) { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalResultSMB.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalResultSMB.kt index ecef4ad3aa..d45fbfe26c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalResultSMB.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalResultSMB.kt @@ -51,7 +51,7 @@ class DetermineBasalResultSMB private constructor(injector: HasAndroidInjector) aapsLogger.error(LTag.APS, "Error parsing 'deliverAt' date: $date", e) } } - if (result.has("variable_sens")) variableSens = result.getDouble("variable_sens"); + if (result.has("variable_sens")) variableSens = result.getDouble("variable_sens") } catch (e: JSONException) { aapsLogger.error(LTag.APS, "Error parsing determine-basal result JSON", e) } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMBDynamicISF/DetermineBasalAdapterSMBDynamicISFJS.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMBDynamicISF/DetermineBasalAdapterSMBDynamicISFJS.kt index 317a7159b0..0c6eb35acb 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMBDynamicISF/DetermineBasalAdapterSMBDynamicISFJS.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMBDynamicISF/DetermineBasalAdapterSMBDynamicISFJS.kt @@ -194,9 +194,9 @@ class DetermineBasalAdapterSMBDynamicISFJS internal constructor(private val scri this.profile.put("current_basal_safety_multiplier", sp.getDouble(R.string.key_openapsama_current_basal_safety_multiplier, 4.0)) //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", false) + 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", false) + 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) @@ -222,12 +222,17 @@ class DetermineBasalAdapterSMBDynamicISFJS internal constructor(private val scri 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("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") } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMBDynamicISF/OpenAPSSMBDynamicISFPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMBDynamicISF/OpenAPSSMBDynamicISFPlugin.kt index 0187b85ece..739379a82c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMBDynamicISF/OpenAPSSMBDynamicISFPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMBDynamicISF/OpenAPSSMBDynamicISFPlugin.kt @@ -66,6 +66,7 @@ class OpenAPSSMBDynamicISFPlugin @Inject constructor( .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) } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ConfigBuilderFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ConfigBuilderFragment.kt index 0bf7dcfc30..96095067d6 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ConfigBuilderFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ConfigBuilderFragment.kt @@ -10,23 +10,26 @@ import android.widget.* import androidx.annotation.StringRes import androidx.core.content.ContextCompat import dagger.android.support.DaggerFragment -import info.nightscout.androidaps.interfaces.Config import info.nightscout.androidaps.R import info.nightscout.androidaps.activities.PreferencesActivity +import info.nightscout.androidaps.activities.SingleFragmentActivity import info.nightscout.androidaps.databinding.ConfigbuilderFragmentBinding import info.nightscout.androidaps.events.EventRebuildTabs -import info.nightscout.androidaps.interfaces.* +import info.nightscout.androidaps.extensions.toVisibility +import info.nightscout.androidaps.interfaces.ActivePlugin +import info.nightscout.androidaps.interfaces.Config +import info.nightscout.androidaps.interfaces.PluginBase +import info.nightscout.androidaps.interfaces.PluginType import info.nightscout.androidaps.plugins.bus.RxBus import info.nightscout.androidaps.plugins.configBuilder.events.EventConfigBuilderUpdateGui import info.nightscout.androidaps.utils.FabricPrivacy -import io.reactivex.rxjava3.kotlin.plusAssign -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.Protection.PREFERENCES import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.rx.AapsSchedulers import io.reactivex.rxjava3.disposables.CompositeDisposable -import java.util.* +import io.reactivex.rxjava3.kotlin.plusAssign import javax.inject.Inject class ConfigBuilderFragment : DaggerFragment() { @@ -44,48 +47,36 @@ class ConfigBuilderFragment : DaggerFragment() { private var disposable: CompositeDisposable = CompositeDisposable() private val pluginViewHolders = ArrayList() - + private var inMenu = false + private var queryingProtection = false private var _binding: ConfigbuilderFragmentBinding? = null - // This property is only valid between onCreateView and - // onDestroyView. + // This property is only valid between onCreateView and onDestroyView. private val binding get() = _binding!! - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?): View { + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { _binding = ConfigbuilderFragmentBinding.inflate(inflater, container, false) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - - if (protectionCheck.isLocked(ProtectionCheck.Protection.PREFERENCES)) - binding.mainLayout.visibility = View.GONE - else - binding.unlock.visibility = View.GONE - - binding.unlock.setOnClickListener { - activity?.let { activity -> - protectionCheck.queryProtection(activity, ProtectionCheck.Protection.PREFERENCES, { - activity.runOnUiThread { - binding.mainLayout.visibility = View.VISIBLE - binding.unlock.visibility = View.GONE - } - }) - } - } + val parentClass = this.activity?.let { it::class.java } + inMenu = parentClass == SingleFragmentActivity::class.java + updateProtectedUi() + binding.unlock.setOnClickListener { queryProtection() } } @Synchronized override fun onResume() { super.onResume() + if (inMenu) queryProtection() else updateProtectedUi() disposable += rxBus .toObservable(EventConfigBuilderUpdateGui::class.java) .observeOn(aapsSchedulers.main) .subscribe({ - for (pluginViewHolder in pluginViewHolders) pluginViewHolder.update() - }, fabricPrivacy::logException) + for (pluginViewHolder in pluginViewHolders) pluginViewHolder.update() + }, fabricPrivacy::logException) updateGUI() } @@ -215,4 +206,21 @@ class ConfigBuilderFragment : DaggerFragment() { return type == PluginType.GENERAL || type == PluginType.CONSTRAINTS || type == PluginType.LOOP } } + + private fun updateProtectedUi() { + val isLocked = protectionCheck.isLocked(PREFERENCES) + binding.mainLayout.visibility = isLocked.not().toVisibility() + binding.unlock.visibility = isLocked.toVisibility() + } + + private fun queryProtection() { + val isLocked = protectionCheck.isLocked(PREFERENCES) + if (isLocked && !queryingProtection) { + activity?.let { activity -> + queryingProtection = true + val doUpdate = { activity.runOnUiThread { queryingProtection = false; updateProtectedUi() } } + protectionCheck.queryProtection(activity, PREFERENCES, doUpdate, doUpdate, doUpdate) + } + } + } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/PluginStore.kt b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/PluginStore.kt index 3993606e73..a574219396 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/PluginStore.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/PluginStore.kt @@ -169,6 +169,9 @@ class PluginStore @Inject constructor( override val activeSafety: Safety get() = getSpecificPluginsListByInterface(Safety::class.java).first() as Safety + override val activeIobCobCalculator: IobCobCalculator + get() = getSpecificPluginsListByInterface(IobCobCalculator::class.java).first() as IobCobCalculator + override fun getPluginsList(): ArrayList = ArrayList(plugins) } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesFragment.kt index d4d0c628a9..5b412076b5 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesFragment.kt @@ -233,7 +233,7 @@ class ObjectivesFragment : DaggerFragment() { } } holder.binding.accomplished.text = rh.gs(R.string.accomplished, dateUtil.dateAndTimeString(objective.accomplishedOn)) - holder.binding.accomplished.setTextColor(-0x3e3e3f) + holder.binding.accomplished.setTextColor(rh.gac(context,R.attr.defaultTextColor)) holder.binding.verify.setOnClickListener { receiverStatusStore.updateNetworkStatus() if (binding.fake.isChecked) { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/safety/SafetyPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/safety/SafetyPlugin.kt index bb1dcae6ed..a91cf4fe54 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/safety/SafetyPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/safety/SafetyPlugin.kt @@ -109,29 +109,29 @@ class SafetyPlugin @Inject constructor( } override fun applyBasalConstraints(absoluteRate: Constraint, profile: Profile): Constraint { - absoluteRate.setIfGreater(aapsLogger, 0.0, String.format(rh.gs(R.string.limitingbasalratio), 0.0, rh.gs(R.string.itmustbepositivevalue)), this) + absoluteRate.setIfGreater(aapsLogger, 0.0, rh.gs(R.string.limitingbasalratio, 0.0, rh.gs(R.string.itmustbepositivevalue)), this) if (config.APS) { var maxBasal = sp.getDouble(R.string.key_openapsma_max_basal, 1.0) if (maxBasal < profile.getMaxDailyBasal()) { maxBasal = profile.getMaxDailyBasal() absoluteRate.addReason(rh.gs(R.string.increasingmaxbasal), this) } - absoluteRate.setIfSmaller(aapsLogger, maxBasal, String.format(rh.gs(R.string.limitingbasalratio), maxBasal, rh.gs(R.string.maxvalueinpreferences)), this) + absoluteRate.setIfSmaller(aapsLogger, maxBasal,rh.gs(R.string.limitingbasalratio, maxBasal, rh.gs(R.string.maxvalueinpreferences)), this) // Check percentRate but absolute rate too, because we know real current basal in pump val maxBasalMultiplier = sp.getDouble(R.string.key_openapsama_current_basal_safety_multiplier, 4.0) val maxFromBasalMultiplier = floor(maxBasalMultiplier * profile.getBasal() * 100) / 100 - absoluteRate.setIfSmaller(aapsLogger, maxFromBasalMultiplier, String.format(rh.gs(R.string.limitingbasalratio), maxFromBasalMultiplier, rh.gs(R.string.maxbasalmultiplier)), this) + absoluteRate.setIfSmaller(aapsLogger, maxFromBasalMultiplier, rh.gs(R.string.limitingbasalratio, maxFromBasalMultiplier, rh.gs(R.string.maxbasalmultiplier)), this) val maxBasalFromDaily = sp.getDouble(R.string.key_openapsama_max_daily_safety_multiplier, 3.0) val maxFromDaily = floor(profile.getMaxDailyBasal() * maxBasalFromDaily * 100) / 100 - absoluteRate.setIfSmaller(aapsLogger, maxFromDaily, String.format(rh.gs(R.string.limitingbasalratio), maxFromDaily, rh.gs(R.string.maxdailybasalmultiplier)), this) + absoluteRate.setIfSmaller(aapsLogger, maxFromDaily,rh.gs(R.string.limitingbasalratio, maxFromDaily, rh.gs(R.string.maxdailybasalmultiplier)), this) } - absoluteRate.setIfSmaller(aapsLogger, hardLimits.maxBasal(), String.format(rh.gs(R.string.limitingbasalratio), hardLimits.maxBasal(), rh.gs(R.string.hardlimit)), this) + absoluteRate.setIfSmaller(aapsLogger, hardLimits.maxBasal(),rh.gs(R.string.limitingbasalratio, hardLimits.maxBasal(), rh.gs(R.string.hardlimit)), this) val pump = activePlugin.activePump // check for pump max if (pump.pumpDescription.tempBasalStyle == PumpDescription.ABSOLUTE) { val pumpLimit = pump.pumpDescription.pumpType.tbrSettings?.maxDose ?: 0.0 - absoluteRate.setIfSmaller(aapsLogger, pumpLimit, String.format(rh.gs(R.string.limitingbasalratio), pumpLimit, rh.gs(R.string.pumplimit)), this) + absoluteRate.setIfSmaller(aapsLogger, pumpLimit, rh.gs(R.string.limitingbasalratio, pumpLimit, rh.gs(R.string.pumplimit)), this) } // do rounding @@ -151,19 +151,19 @@ class SafetyPlugin @Inject constructor( val pump = activePlugin.activePump var percentRateAfterConst = java.lang.Double.valueOf(absoluteConstraint.value() / currentBasal * 100).toInt() percentRateAfterConst = if (percentRateAfterConst < 100) Round.ceilTo(percentRateAfterConst.toDouble(), pump.pumpDescription.tempPercentStep.toDouble()).toInt() else Round.floorTo(percentRateAfterConst.toDouble(), pump.pumpDescription.tempPercentStep.toDouble()).toInt() - percentRate.set(aapsLogger, percentRateAfterConst, String.format(rh.gs(R.string.limitingpercentrate), percentRateAfterConst, rh.gs(R.string.pumplimit)), this) + percentRate.set(aapsLogger, percentRateAfterConst, rh.gs(R.string.limitingpercentrate, percentRateAfterConst, rh.gs(R.string.pumplimit)), this) if (pump.pumpDescription.tempBasalStyle == PumpDescription.PERCENT) { val pumpLimit = pump.pumpDescription.pumpType.tbrSettings?.maxDose ?: 0.0 - percentRate.setIfSmaller(aapsLogger, pumpLimit.toInt(), String.format(rh.gs(R.string.limitingbasalratio), pumpLimit, rh.gs(R.string.pumplimit)), this) + percentRate.setIfSmaller(aapsLogger, pumpLimit.toInt(), rh.gs(R.string.limitingbasalratio, pumpLimit, rh.gs(R.string.pumplimit)), this) } return percentRate } override fun applyBolusConstraints(insulin: Constraint): Constraint { - insulin.setIfGreater(aapsLogger, 0.0, String.format(rh.gs(R.string.limitingbolus), 0.0, rh.gs(R.string.itmustbepositivevalue)), this) + insulin.setIfGreater(aapsLogger, 0.0, rh.gs(R.string.limitingbolus, 0.0, rh.gs(R.string.itmustbepositivevalue)), this) val maxBolus = sp.getDouble(R.string.key_treatmentssafety_maxbolus, 3.0) - insulin.setIfSmaller(aapsLogger, maxBolus, String.format(rh.gs(R.string.limitingbolus), maxBolus, rh.gs(R.string.maxvalueinpreferences)), this) - insulin.setIfSmaller(aapsLogger, hardLimits.maxBolus(), String.format(rh.gs(R.string.limitingbolus), hardLimits.maxBolus(), rh.gs(R.string.hardlimit)), this) + insulin.setIfSmaller(aapsLogger, maxBolus, rh.gs(R.string.limitingbolus, maxBolus, rh.gs(R.string.maxvalueinpreferences)), this) + insulin.setIfSmaller(aapsLogger, hardLimits.maxBolus(), rh.gs(R.string.limitingbolus, hardLimits.maxBolus(), rh.gs(R.string.hardlimit)), this) val pump = activePlugin.activePump val rounded = pump.pumpDescription.pumpType.determineCorrectBolusSize(insulin.value()) insulin.setIfDifferent(aapsLogger, rounded, rh.gs(R.string.pumplimit), this) @@ -171,10 +171,10 @@ class SafetyPlugin @Inject constructor( } override fun applyExtendedBolusConstraints(insulin: Constraint): Constraint { - insulin.setIfGreater(aapsLogger, 0.0, String.format(rh.gs(R.string.limitingextendedbolus), 0.0, rh.gs(R.string.itmustbepositivevalue)), this) + insulin.setIfGreater(aapsLogger, 0.0, rh.gs(R.string.limitingextendedbolus, 0.0, rh.gs(R.string.itmustbepositivevalue)), this) val maxBolus = sp.getDouble(R.string.key_treatmentssafety_maxbolus, 3.0) - insulin.setIfSmaller(aapsLogger, maxBolus, String.format(rh.gs(R.string.limitingextendedbolus), maxBolus, rh.gs(R.string.maxvalueinpreferences)), this) - insulin.setIfSmaller(aapsLogger, hardLimits.maxBolus(), String.format(rh.gs(R.string.limitingextendedbolus), hardLimits.maxBolus(), rh.gs(R.string.hardlimit)), this) + insulin.setIfSmaller(aapsLogger, maxBolus, rh.gs(R.string.limitingextendedbolus, maxBolus, rh.gs(R.string.maxvalueinpreferences)), this) + insulin.setIfSmaller(aapsLogger, hardLimits.maxBolus(), rh.gs(R.string.limitingextendedbolus, hardLimits.maxBolus(), rh.gs(R.string.hardlimit)), this) val pump = activePlugin.activePump val rounded = pump.pumpDescription.pumpType.determineCorrectExtendedBolusSize(insulin.value()) insulin.setIfDifferent(aapsLogger, rounded, rh.gs(R.string.pumplimit), this) @@ -182,9 +182,9 @@ class SafetyPlugin @Inject constructor( } override fun applyCarbsConstraints(carbs: Constraint): Constraint { - carbs.setIfGreater(aapsLogger, 0, String.format(rh.gs(R.string.limitingcarbs), 0, rh.gs(R.string.itmustbepositivevalue)), this) + carbs.setIfGreater(aapsLogger, 0, rh.gs(R.string.limitingcarbs, 0, rh.gs(R.string.itmustbepositivevalue)), this) val maxCarbs = sp.getInt(R.string.key_treatmentssafety_maxcarbs, 48) - carbs.setIfSmaller(aapsLogger, maxCarbs, String.format(rh.gs(R.string.limitingcarbs), maxCarbs, rh.gs(R.string.maxvalueinpreferences)), this) + carbs.setIfSmaller(aapsLogger, maxCarbs, rh.gs(R.string.limitingcarbs, maxCarbs, rh.gs(R.string.maxvalueinpreferences)), this) return carbs } @@ -192,11 +192,11 @@ class SafetyPlugin @Inject constructor( val apsMode = sp.getString(R.string.key_aps_mode, "open") val maxIobPref: Double = if (openAPSSMBPlugin.isEnabled() || OpenAPSSMBDynamicISFPlugin.isEnabled()) sp.getDouble(R.string.key_openapssmb_max_iob, 3.0) else sp.getDouble(R.string .key_openapsma_max_iob, 1.5) - maxIob.setIfSmaller(aapsLogger, maxIobPref, String.format(rh.gs(R.string.limitingiob), maxIobPref, rh.gs(R.string.maxvalueinpreferences)), this) - if (openAPSAMAPlugin.isEnabled()) maxIob.setIfSmaller(aapsLogger, hardLimits.maxIobAMA(), String.format(rh.gs(R.string.limitingiob), hardLimits.maxIobAMA(), rh.gs(R.string.hardlimit)), this) - if (openAPSSMBPlugin.isEnabled()) maxIob.setIfSmaller(aapsLogger, hardLimits.maxIobSMB(), String.format(rh.gs(R.string.limitingiob), hardLimits.maxIobSMB(), rh.gs(R.string.hardlimit)), this) - if (OpenAPSSMBDynamicISFPlugin.isEnabled()) maxIob.setIfSmaller(aapsLogger, hardLimits.maxIobSMB(), String.format(rh.gs(R.string.limitingiob), hardLimits.maxIobSMB(), rh.gs(R.string.hardlimit)), this) - if (apsMode == "lgs") maxIob.setIfSmaller(aapsLogger, HardLimits.MAX_IOB_LGS, String.format(rh.gs(R.string.limitingiob), HardLimits.MAX_IOB_LGS, rh.gs(R.string.lowglucosesuspend)), this) + maxIob.setIfSmaller(aapsLogger, maxIobPref, rh.gs(R.string.limitingiob, maxIobPref, rh.gs(R.string.maxvalueinpreferences)), this) + if (openAPSAMAPlugin.isEnabled()) maxIob.setIfSmaller(aapsLogger, hardLimits.maxIobAMA(), rh.gs(R.string.limitingiob, hardLimits.maxIobAMA(), rh.gs(R.string.hardlimit)), this) + if (openAPSSMBPlugin.isEnabled()) maxIob.setIfSmaller(aapsLogger, hardLimits.maxIobSMB(), rh.gs(R.string.limitingiob, hardLimits.maxIobSMB(), rh.gs(R.string.hardlimit)), this) + if (OpenAPSSMBDynamicISFPlugin.isEnabled()) maxIob.setIfSmaller(aapsLogger, hardLimits.maxIobSMB(), rh.gs(R.string.limitingiob, hardLimits.maxIobSMB(), rh.gs(R.string.hardlimit)), this) + if (apsMode == "lgs") maxIob.setIfSmaller(aapsLogger, HardLimits.MAX_IOB_LGS, rh.gs(R.string.limitingiob, HardLimits.MAX_IOB_LGS, rh.gs(R.string.lowglucosesuspend)), this) return maxIob } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/actions/ActionsFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/actions/ActionsFragment.kt index dc5805cfd1..d93cd1fa83 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/actions/ActionsFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/actions/ActionsFragment.kt @@ -8,10 +8,8 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.LinearLayout -import android.widget.TextView import androidx.core.content.ContextCompat import dagger.android.support.DaggerFragment -import info.nightscout.androidaps.Constants import info.nightscout.androidaps.R import info.nightscout.androidaps.activities.ErrorHelperActivity import info.nightscout.androidaps.activities.HistoryBrowseActivity @@ -20,7 +18,7 @@ import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.ValueWrapper import info.nightscout.androidaps.database.entities.UserEntry.Action import info.nightscout.androidaps.database.entities.UserEntry.Sources -import info.nightscout.androidaps.diaconn.DiaconnG8Plugin +import info.nightscout.androidaps.databinding.ActionsFragmentBinding import info.nightscout.androidaps.dialogs.* import info.nightscout.androidaps.events.EventCustomActionsChanged import info.nightscout.androidaps.events.EventExtendedBolusChange @@ -36,7 +34,6 @@ import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.plugins.bus.RxBus import info.nightscout.androidaps.plugins.general.actions.defs.CustomAction import info.nightscout.androidaps.plugins.general.overview.StatusLightHandler -import info.nightscout.androidaps.plugins.pump.omnipod.eros.OmnipodErosPumpPlugin import info.nightscout.androidaps.queue.Callback import info.nightscout.androidaps.skins.SkinProvider import info.nightscout.androidaps.utils.DateUtil @@ -81,88 +78,47 @@ class ActionsFragment : DaggerFragment() { private val pumpCustomActions = HashMap() private val pumpCustomButtons = ArrayList() - private var smallWidth = false - private var smallHeight = false private lateinit var dm: DisplayMetrics - private var buttonsLayout: LinearLayout? = null - private var profileSwitch: SingleClickButton? = null - private var tempTarget: SingleClickButton? = null - private var extendedBolus: SingleClickButton? = null - private var extendedBolusCancel: SingleClickButton? = null - private var setTempBasal: SingleClickButton? = null - private var cancelTempBasal: SingleClickButton? = null - private var fill: SingleClickButton? = null - private var historyBrowser: SingleClickButton? = null - private var tddStats: SingleClickButton? = null - private var pumpBatteryChange: SingleClickButton? = null + private var _binding: ActionsFragmentBinding? = null + // This property is only valid between onCreateView and onDestroyView. + private val binding get() = _binding!! - private var cannulaAge: TextView? = null - private var insulinAge: TextView? = null - private var reservoirLevel: TextView? = null - private var sensorAge: TextView? = null - private var sensorLevel: TextView? = null - private var pbAge: TextView? = null - private var batteryLevel: TextView? = null - private var sensorLevelLabel: TextView? = null - private var insulinLevelLabel: TextView? = null - private var pbLevelLabel: TextView? = null - private var cannulaOrPatch: TextView? = null - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { //check screen width dm = DisplayMetrics() - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) { + @Suppress("DEPRECATION") activity?.display?.getRealMetrics(dm) - else + } else { @Suppress("DEPRECATION") activity?.windowManager?.defaultDisplay?.getMetrics(dm) - - val screenWidth = dm.widthPixels - val screenHeight = dm.heightPixels - smallWidth = screenWidth <= Constants.SMALL_WIDTH - smallHeight = screenHeight <= Constants.SMALL_HEIGHT - val landscape = screenHeight < screenWidth - - return inflater.inflate(skinProvider.activeSkin().actionsLayout(landscape, smallWidth), container, false) + } + _binding = ActionsFragmentBinding.inflate(inflater, container, false) + return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - buttonsLayout = view.findViewById(R.id.action_buttons_layout) - profileSwitch = view.findViewById(R.id.actions_profileswitch) - tempTarget = view.findViewById(R.id.actions_temptarget) - extendedBolus = view.findViewById(R.id.actions_extendedbolus) - extendedBolusCancel = view.findViewById(R.id.actions_extendedbolus_cancel) - setTempBasal = view.findViewById(R.id.actions_settempbasal) - cancelTempBasal = view.findViewById(R.id.actions_canceltempbasal) - fill = view.findViewById(R.id.actions_fill) - historyBrowser = view.findViewById(R.id.actions_historybrowser) - tddStats = view.findViewById(R.id.actions_tddstats) - pumpBatteryChange = view.findViewById(R.id.actions_pumpbatterychange) + skinProvider.activeSkin().preProcessLandscapeActionsLayout(dm, binding) - cannulaAge = view.findViewById(R.id.cannula_age) - insulinAge = view.findViewById(R.id.insulin_age) - reservoirLevel = view.findViewById(R.id.reservoir_level) - sensorAge = view.findViewById(R.id.sensor_age) - sensorLevel = view.findViewById(R.id.sensor_level) - pbAge = view.findViewById(R.id.pb_age) - batteryLevel = view.findViewById(R.id.battery_level) - sensorLevelLabel = view.findViewById(R.id.sensor_level_label) - insulinLevelLabel = view.findViewById(R.id.insulin_level_label) - pbLevelLabel = view.findViewById(R.id.pb_level_label) - cannulaOrPatch = view.findViewById(R.id.cannula_or_patch) - - profileSwitch?.setOnClickListener { - ProfileSwitchDialog().show(childFragmentManager, "ProfileSwitchDialog") + binding.profileSwitch.setOnClickListener { + activity?.let { activity -> + protectionCheck.queryProtection( + activity, + ProtectionCheck.Protection.BOLUS, + UIRunnable { ProfileSwitchDialog().show(childFragmentManager, "ProfileSwitchDialog")}) + } } - tempTarget?.setOnClickListener { - TempTargetDialog().show(childFragmentManager, "Actions") + binding.tempTarget.setOnClickListener { + activity?.let { activity -> + protectionCheck.queryProtection( + activity, + ProtectionCheck.Protection.BOLUS, + UIRunnable { TempTargetDialog().show(childFragmentManager, "Actions") }) + } } - extendedBolus?.setOnClickListener { + binding.extendedBolus.setOnClickListener { activity?.let { activity -> protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, UIRunnable { OKDialog.showConfirmation( @@ -174,7 +130,7 @@ class ActionsFragment : DaggerFragment() { }) } } - extendedBolusCancel?.setOnClickListener { + binding.extendedBolusCancel.setOnClickListener { if (iobCobCalculator.getExtendedBolus(dateUtil.now()) != null) { uel.log(Action.CANCEL_EXTENDED_BOLUS, Sources.Actions) commandQueue.cancelExtended(object : Callback() { @@ -186,10 +142,15 @@ class ActionsFragment : DaggerFragment() { }) } } - setTempBasal?.setOnClickListener { - TempBasalDialog().show(childFragmentManager, "Actions") + binding.setTempBasal.setOnClickListener { + activity?.let { activity -> + protectionCheck.queryProtection( + activity, + ProtectionCheck.Protection.BOLUS, + UIRunnable { TempBasalDialog().show(childFragmentManager, "Actions") }) + } } - cancelTempBasal?.setOnClickListener { + binding.cancelTempBasal.setOnClickListener { if (iobCobCalculator.getTempBasalIncludingConvertedExtended(dateUtil.now()) != null) { uel.log(Action.CANCEL_TEMP_BASAL, Sources.Actions) commandQueue.cancelTempBasal(true, object : Callback() { @@ -201,32 +162,32 @@ class ActionsFragment : DaggerFragment() { }) } } - fill?.setOnClickListener { + binding.fill.setOnClickListener { activity?.let { activity -> protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, UIRunnable { FillDialog().show(childFragmentManager, "FillDialog") }) } } - historyBrowser?.setOnClickListener { startActivity(Intent(context, HistoryBrowseActivity::class.java)) } - tddStats?.setOnClickListener { startActivity(Intent(context, TDDStatsActivity::class.java)) } - view.findViewById(R.id.actions_bgcheck).setOnClickListener { + binding.historyBrowser.setOnClickListener { startActivity(Intent(context, HistoryBrowseActivity::class.java)) } + binding.tddStats.setOnClickListener { startActivity(Intent(context, TDDStatsActivity::class.java)) } + binding.bgCheck.setOnClickListener { CareDialog().setOptions(CareDialog.EventType.BGCHECK, R.string.careportal_bgcheck).show(childFragmentManager, "Actions") } - view.findViewById(R.id.actions_cgmsensorinsert).setOnClickListener { + binding.cgmSensorInsert.setOnClickListener { CareDialog().setOptions(CareDialog.EventType.SENSOR_INSERT, R.string.careportal_cgmsensorinsert).show(childFragmentManager, "Actions") } - pumpBatteryChange?.setOnClickListener { + binding.pumpBatteryChange.setOnClickListener { CareDialog().setOptions(CareDialog.EventType.BATTERY_CHANGE, R.string.careportal_pumpbatterychange).show(childFragmentManager, "Actions") } - view.findViewById(R.id.actions_note).setOnClickListener { + binding.note.setOnClickListener { CareDialog().setOptions(CareDialog.EventType.NOTE, R.string.careportal_note).show(childFragmentManager, "Actions") } - view.findViewById(R.id.actions_exercise).setOnClickListener { + binding.exercise.setOnClickListener { CareDialog().setOptions(CareDialog.EventType.EXERCISE, R.string.careportal_exercise).show(childFragmentManager, "Actions") } - view.findViewById(R.id.actions_question).setOnClickListener { + binding.question.setOnClickListener { CareDialog().setOptions(CareDialog.EventType.QUESTION, R.string.careportal_question).show(childFragmentManager, "Actions") } - view.findViewById(R.id.actions_announcement).setOnClickListener { + binding.announcement.setOnClickListener { CareDialog().setOptions(CareDialog.EventType.ANNOUNCEMENT, R.string.careportal_announcement).show(childFragmentManager, "Actions") } @@ -265,13 +226,18 @@ class ActionsFragment : DaggerFragment() { disposable.clear() } + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + @Synchronized fun updateGui() { val profile = profileFunction.getProfile() val pump = activePlugin.activePump - profileSwitch?.visibility = ( + binding.profileSwitch.visibility = ( activePlugin.activeProfileSource.profile != null && pump.pumpDescription.isSetBasalProfileCapable && pump.isInitialized() && @@ -279,60 +245,58 @@ class ActionsFragment : DaggerFragment() { !loop.isDisconnected).toVisibility() if (!pump.pumpDescription.isExtendedBolusCapable || !pump.isInitialized() || pump.isSuspended() || loop.isDisconnected || pump.isFakingTempsByExtendedBoluses || config.NSCLIENT) { - extendedBolus?.visibility = View.GONE - extendedBolusCancel?.visibility = View.GONE + binding.extendedBolus.visibility = View.GONE + binding.extendedBolusCancel.visibility = View.GONE } else { val activeExtendedBolus = repository.getExtendedBolusActiveAt(dateUtil.now()).blockingGet() if (activeExtendedBolus is ValueWrapper.Existing) { - extendedBolus?.visibility = View.GONE - extendedBolusCancel?.visibility = View.VISIBLE + binding.extendedBolus.visibility = View.GONE + binding.extendedBolusCancel.visibility = View.VISIBLE @Suppress("SetTextI18n") - extendedBolusCancel?.text = rh.gs(R.string.cancel) + " " + activeExtendedBolus.value.toStringMedium(dateUtil) + binding.extendedBolusCancel.text = rh.gs(R.string.cancel) + " " + activeExtendedBolus.value.toStringMedium(dateUtil) } else { - extendedBolus?.visibility = View.VISIBLE - extendedBolusCancel?.visibility = View.GONE + binding.extendedBolus.visibility = View.VISIBLE + binding.extendedBolusCancel.visibility = View.GONE } } if (!pump.pumpDescription.isTempBasalCapable || !pump.isInitialized() || pump.isSuspended() || loop.isDisconnected || config.NSCLIENT) { - setTempBasal?.visibility = View.GONE - cancelTempBasal?.visibility = View.GONE + binding.setTempBasal.visibility = View.GONE + binding.cancelTempBasal.visibility = View.GONE } else { val activeTemp = iobCobCalculator.getTempBasalIncludingConvertedExtended(System.currentTimeMillis()) if (activeTemp != null) { - setTempBasal?.visibility = View.GONE - cancelTempBasal?.visibility = View.VISIBLE + binding.setTempBasal.visibility = View.GONE + binding.cancelTempBasal.visibility = View.VISIBLE @Suppress("SetTextI18n") - cancelTempBasal?.text = rh.gs(R.string.cancel) + " " + activeTemp.toStringShort() + binding.cancelTempBasal.text = rh.gs(R.string.cancel) + " " + activeTemp.toStringShort() } else { - setTempBasal?.visibility = View.VISIBLE - cancelTempBasal?.visibility = View.GONE + binding.setTempBasal.visibility = View.VISIBLE + binding.cancelTempBasal.visibility = View.GONE } } val activeBgSource = activePlugin.activeBgSource - historyBrowser?.visibility = (profile != null).toVisibility() - fill?.visibility = (pump.pumpDescription.isRefillingCapable && pump.isInitialized() && !pump.isSuspended()).toVisibility() - if (pump is DiaconnG8Plugin) { - pumpBatteryChange?.visibility = (pump.pumpDescription.isBatteryReplaceable && !pump.isBatteryChangeLoggingEnabled()).toVisibility() - } else { - pumpBatteryChange?.visibility = - (pump.pumpDescription.isBatteryReplaceable || (pump is OmnipodErosPumpPlugin && pump.isUseRileyLinkBatteryLevel && pump.isBatteryChangeLoggingEnabled)).toVisibility() - } - tempTarget?.visibility = (profile != null && !loop.isDisconnected).toVisibility() - tddStats?.visibility = pump.pumpDescription.supportsTDDs.toVisibility() + binding.historyBrowser.visibility = (profile != null).toVisibility() + binding.fill.visibility = (pump.pumpDescription.isRefillingCapable && pump.isInitialized() && !pump.isSuspended()).toVisibility() + binding.pumpBatteryChange.visibility = (pump.pumpDescription.isBatteryReplaceable || pump.isBatteryChangeLoggingEnabled()).toVisibility() + binding.tempTarget.visibility = (profile != null && !loop.isDisconnected).toVisibility() + binding.tddStats.visibility = pump.pumpDescription.supportsTDDs.toVisibility() + val isPatchPump = pump.pumpDescription.isPatchPump + binding.status.apply { + cannulaOrPatch.text = if (isPatchPump) rh.gs(R.string.patch_pump) else rh.gs(R.string.cannula) + val imageResource = if (isPatchPump) R.drawable.ic_patch_pump_outline else R.drawable.ic_cp_age_cannula + cannulaOrPatch.setCompoundDrawablesWithIntrinsicBounds(imageResource, 0, 0, 0) + batteryLayout.visibility = (!isPatchPump || pump.pumpDescription.useHardwareLink).toVisibility() - cannulaOrPatch?.text = if (pump.pumpDescription.isPatchPump) rh.gs(R.string.patch_pump) else rh.gs(R.string.cannula) - val imageResource = if (pump.pumpDescription.isPatchPump) R.drawable.ic_patch_pump_outline else R.drawable.ic_cp_age_cannula - cannulaOrPatch?.setCompoundDrawablesWithIntrinsicBounds(imageResource, 0, 0, 0) - - if (!config.NSCLIENT) { - statusLightHandler.updateStatusLights(cannulaAge, insulinAge, reservoirLevel, sensorAge, sensorLevel, pbAge, batteryLevel) - sensorLevelLabel?.text = if (activeBgSource.sensorBatteryLevel == -1) "" else rh.gs(R.string.careportal_level_label) - } else { - statusLightHandler.updateStatusLights(cannulaAge, insulinAge, null, sensorAge, null, pbAge, null) - sensorLevelLabel?.text = "" - insulinLevelLabel?.text = "" - pbLevelLabel?.text = "" + if (!config.NSCLIENT) { + statusLightHandler.updateStatusLights(cannulaAge, insulinAge, reservoirLevel, sensorAge, sensorLevel, pbAge, batteryLevel) + sensorLevelLabel.text = if (activeBgSource.sensorBatteryLevel == -1) "" else rh.gs(R.string.careportal_level_label) + } else { + statusLightHandler.updateStatusLights(cannulaAge, insulinAge, null, sensorAge, null, pbAge, null) + sensorLevelLabel.text = "" + insulinLevelLabel.text = "" + pbLevelLabel.text = "" + } } checkPumpCustomActions() @@ -365,7 +329,7 @@ class ActionsFragment : DaggerFragment() { val top = activity?.let { ContextCompat.getDrawable(it, customAction.iconResourceId) } btn.setCompoundDrawablesWithIntrinsicBounds(null, top, null, null) - buttonsLayout?.addView(btn) + binding.buttonsLayout.addView(btn) this.pumpCustomActions[rh.gs(customAction.name)] = customAction this.pumpCustomButtons.add(btn) @@ -373,7 +337,7 @@ class ActionsFragment : DaggerFragment() { } private fun removePumpCustomActions() { - for (customButton in pumpCustomButtons) buttonsLayout?.removeView(customButton) + for (customButton in pumpCustomButtons) binding.buttonsLayout.removeView(customButton) pumpCustomButtons.clear() } -} \ No newline at end of file +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/dataBroadcaster/DataBroadcastPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/dataBroadcaster/DataBroadcastPlugin.kt index 035c3a13a1..4d08f94a4e 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/dataBroadcaster/DataBroadcastPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/dataBroadcaster/DataBroadcastPlugin.kt @@ -6,12 +6,11 @@ import android.content.pm.ResolveInfo import android.os.Bundle import dagger.android.HasAndroidInjector import info.nightscout.androidaps.R -import info.nightscout.androidaps.events.* +import info.nightscout.androidaps.events.Event +import info.nightscout.androidaps.events.EventAutosensCalculationFinished import info.nightscout.androidaps.extensions.durationInMinutes import info.nightscout.androidaps.extensions.toStringFull import info.nightscout.androidaps.interfaces.* -import info.nightscout.shared.logging.AAPSLogger -import info.nightscout.shared.logging.LTag import info.nightscout.androidaps.plugins.aps.events.EventOpenAPSUpdateGui import info.nightscout.androidaps.plugins.bus.RxBus import info.nightscout.androidaps.plugins.general.nsclient.data.DeviceStatusData @@ -25,6 +24,8 @@ import info.nightscout.androidaps.utils.DefaultValueHelper import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.rx.AapsSchedulers +import info.nightscout.shared.logging.AAPSLogger +import info.nightscout.shared.logging.LTag import io.reactivex.rxjava3.disposables.CompositeDisposable import javax.inject.Inject import javax.inject.Singleton @@ -68,26 +69,6 @@ class DataBroadcastPlugin @Inject constructor( .observeOn(aapsSchedulers.io) .subscribe({ sendData(it) }, fabricPrivacy::logException) ) - disposable.add(rxBus - .toObservable(EventExtendedBolusChange::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ sendData(it) }, fabricPrivacy::logException) - ) - disposable.add(rxBus - .toObservable(EventTempBasalChange::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ sendData(it) }, fabricPrivacy::logException) - ) - disposable.add(rxBus - .toObservable(EventTreatmentChange::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ sendData(it) }, fabricPrivacy::logException) - ) - disposable.add(rxBus - .toObservable(EventEffectiveProfileSwitchChanged::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ sendData(it) }, fabricPrivacy::logException) - ) disposable.add(rxBus .toObservable(EventAutosensCalculationFinished::class.java) .observeOn(aapsSchedulers.io) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/food/FoodFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/food/FoodFragment.kt index eae7c2b177..3cba103783 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/food/FoodFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/food/FoodFragment.kt @@ -1,7 +1,6 @@ package info.nightscout.androidaps.plugins.general.food import android.annotation.SuppressLint -import android.graphics.Paint import android.os.Bundle import android.text.Editable import android.text.TextWatcher @@ -24,18 +23,18 @@ import info.nightscout.androidaps.databinding.FoodFragmentBinding import info.nightscout.androidaps.databinding.FoodItemBinding import info.nightscout.androidaps.dialogs.WizardDialog import info.nightscout.androidaps.events.EventFoodDatabaseChanged -import info.nightscout.shared.logging.AAPSLogger -import info.nightscout.shared.logging.LTag +import info.nightscout.androidaps.extensions.toVisibility import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.plugins.bus.RxBus import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.alertDialogs.OKDialog -import info.nightscout.androidaps.extensions.toVisibility import info.nightscout.androidaps.utils.protection.ProtectionCheck import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.rx.AapsSchedulers import info.nightscout.androidaps.utils.ui.UIRunnable +import info.nightscout.shared.logging.AAPSLogger +import info.nightscout.shared.logging.LTag import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign @@ -43,7 +42,6 @@ import io.reactivex.rxjava3.kotlin.subscribeBy import java.util.* import java.util.concurrent.TimeUnit import javax.inject.Inject -import kotlin.collections.ArrayList class FoodFragment : DaggerFragment() { @@ -66,10 +64,8 @@ class FoodFragment : DaggerFragment() { // onDestroyView. private val binding get() = _binding!! - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - _binding = FoodFragmentBinding.inflate(inflater, container, false) - return binding.root - } + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = + FoodFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -80,8 +76,10 @@ class FoodFragment : DaggerFragment() { binding.refreshFromNightscout.setOnClickListener { context?.let { context -> OKDialog.showConfirmation(context, rh.gs(R.string.refresheventsfromnightscout) + " ?", { - uel.log(Action.FOOD, Sources.Food, rh.gs(R.string.refresheventsfromnightscout), - ValueWithUnit.SimpleString(rh.gsNotLocalised(R.string.refresheventsfromnightscout))) + 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) @@ -95,32 +93,14 @@ class FoodFragment : DaggerFragment() { } } - binding.clearfilter.setOnClickListener { + binding.filterinputLayout.setEndIconOnClickListener { binding.filter.setText("") - binding.category.setSelection(0) - binding.subcategory.setSelection(0) + binding.categoryList.setText(rh.gs(R.string.none), false) + binding.subcategoryList.setText(rh.gs(R.string.none), false) filterData() } - binding.category.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { - override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { - fillSubcategories() - filterData() - } - - override fun onNothingSelected(parent: AdapterView<*>?) { - fillSubcategories() - filterData() - } - } - binding.subcategory.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { - override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { - filterData() - } - - override fun onNothingSelected(parent: AdapterView<*>?) { - filterData() - } - } + binding.categoryList.onItemClickListener = AdapterView.OnItemClickListener { _, _, _, _ -> fillSubcategories(); filterData() } + binding.subcategoryList.onItemClickListener = AdapterView.OnItemClickListener { _, _, _, _ -> filterData() } binding.filter.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { @@ -134,12 +114,11 @@ class FoodFragment : DaggerFragment() { @Synchronized override fun onResume() { super.onResume() - disposable.add(rxBus + disposable += rxBus .toObservable(EventFoodDatabaseChanged::class.java) .observeOn(aapsSchedulers.main) .debounce(1L, TimeUnit.SECONDS) .subscribe({ swapAdapter() }, fabricPrivacy::logException) - ) swapAdapter() } @@ -178,13 +157,13 @@ class FoodFragment : DaggerFragment() { val categories = ArrayList(catSet) categories.add(0, rh.gs(R.string.none)) context?.let { context -> - val adapterCategories = ArrayAdapter(context, R.layout.spinner_centered, categories) - binding.category.adapter = adapterCategories + binding.categoryList.setAdapter(ArrayAdapter(context, R.layout.spinner_centered, categories)) + binding.categoryList.setText(rh.gs(R.string.none), false) } } private fun fillSubcategories() { - val categoryFilter = binding.category.selectedItem.toString() + val categoryFilter = binding.categoryList.text.toString() val subCatSet: MutableSet = HashSet() if (categoryFilter != rh.gs(R.string.none)) { for (f in unfiltered) { @@ -198,17 +177,15 @@ class FoodFragment : DaggerFragment() { val subcategories = ArrayList(subCatSet) subcategories.add(0, rh.gs(R.string.none)) context?.let { context -> - val adapterSubcategories = ArrayAdapter(context, R.layout.spinner_centered, subcategories) - binding.subcategory.adapter = adapterSubcategories + binding.subcategoryList.setAdapter(ArrayAdapter(context, R.layout.spinner_centered, subcategories)) + binding.subcategoryList.setText(rh.gs(R.string.none), false) } } private fun filterData() { val textFilter = binding.filter.text.toString() - val categoryFilter = binding.category.selectedItem?.toString() - ?: rh.gs(R.string.none) - val subcategoryFilter = binding.subcategory.selectedItem?.toString() - ?: rh.gs(R.string.none) + val categoryFilter = binding.categoryList.text.toString() + val subcategoryFilter = binding.subcategoryList.text.toString() val newFiltered = ArrayList() for (f in unfiltered) { if (f.category == null || f.subCategory == null) continue @@ -267,18 +244,17 @@ class FoodFragment : DaggerFragment() { }, null) } } - binding.icCalculator.setOnClickListener { v:View -> + binding.icCalculator.setOnClickListener { v: View -> val food = v.tag as Food activity?.let { activity -> protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, UIRunnable { - if (isAdded) { - val wizardDialog = WizardDialog() - val bundle = Bundle() - bundle.putInt("carbs_input", food.carbs) - bundle.putString("notes_input", " ${food.name} - ${food.carbs}g") - wizardDialog.setArguments(bundle) - wizardDialog.show(childFragmentManager, "Food Item") - } + if (isAdded) + WizardDialog().also { dialog -> + dialog.arguments = Bundle().also { bundle -> + bundle.putDouble("carbs_input", food.carbs.toDouble()) + bundle.putString("notes_input", " ${food.name} - ${food.carbs}g") + } + }.show(childFragmentManager, "Food Item") }) } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenanceFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenanceFragment.kt index 8d6cc9c91b..c927072d83 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenanceFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenanceFragment.kt @@ -7,6 +7,7 @@ import android.view.View import android.view.ViewGroup import dagger.android.support.DaggerFragment import info.nightscout.androidaps.R +import info.nightscout.androidaps.activities.SingleFragmentActivity import info.nightscout.androidaps.dana.database.DanaHistoryDatabase import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.entities.UserEntry.Action @@ -14,6 +15,7 @@ import info.nightscout.androidaps.database.entities.UserEntry.Sources import info.nightscout.androidaps.databinding.MaintenanceFragmentBinding import info.nightscout.androidaps.diaconn.database.DiaconnHistoryDatabase import info.nightscout.androidaps.events.EventPreferenceChange +import info.nightscout.androidaps.extensions.toVisibility import info.nightscout.androidaps.insight.database.InsightDatabase import info.nightscout.androidaps.interfaces.DataSyncSelector import info.nightscout.androidaps.interfaces.ImportExportPrefs @@ -27,6 +29,8 @@ import info.nightscout.androidaps.plugins.general.overview.OverviewData import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.database.DashHistoryDatabase import info.nightscout.androidaps.plugins.pump.omnipod.eros.history.database.ErosHistoryDatabase import info.nightscout.androidaps.utils.alertDialogs.OKDialog +import info.nightscout.androidaps.utils.protection.ProtectionCheck +import info.nightscout.androidaps.utils.protection.ProtectionCheck.Protection.PREFERENCES import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.rx.AapsSchedulers import io.reactivex.rxjava3.core.Completable.fromAction @@ -48,6 +52,7 @@ class MaintenanceFragment : DaggerFragment() { @Inject lateinit var diaconnDatabase: DiaconnHistoryDatabase @Inject lateinit var erosDatabase: ErosHistoryDatabase @Inject lateinit var dashDatabase: DashHistoryDatabase + @Inject lateinit var protectionCheck: ProtectionCheck @Inject lateinit var uel: UserEntryLogger @Inject lateinit var dataSyncSelector: DataSyncSelector @Inject lateinit var pumpSync: PumpSync @@ -55,11 +60,11 @@ class MaintenanceFragment : DaggerFragment() { @Inject lateinit var overviewData: OverviewData private val compositeDisposable = CompositeDisposable() - + private var inMenu = false + private var queryingProtection = false private var _binding: MaintenanceFragmentBinding? = null - // This property is only valid between onCreateView and - // onDestroyView. + // This property is only valid between onCreateView and onDestroyView. private val binding get() = _binding!! override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { @@ -69,6 +74,9 @@ class MaintenanceFragment : DaggerFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + val parentClass = this.activity?.let { it::class.java } + inMenu = parentClass == SingleFragmentActivity::class.java + updateProtectedUi() binding.logSend.setOnClickListener { maintenancePlugin.sendLogs() } binding.logDelete.setOnClickListener { uel.log(Action.DELETE_LOGS, Sources.Maintenance) @@ -128,6 +136,13 @@ class MaintenanceFragment : DaggerFragment() { } } } + + binding.unlock.setOnClickListener { queryProtection() } + } + + override fun onResume() { + super.onResume() + if (inMenu) queryProtection() else updateProtectedUi() } @Synchronized @@ -136,4 +151,21 @@ class MaintenanceFragment : DaggerFragment() { compositeDisposable.clear() _binding = null } -} \ No newline at end of file + + private fun updateProtectedUi() { + val isLocked = protectionCheck.isLocked(PREFERENCES) + binding.mainLayout.visibility = isLocked.not().toVisibility() + binding.unlock.visibility = isLocked.toVisibility() + } + + private fun queryProtection() { + val isLocked = protectionCheck.isLocked(PREFERENCES) + if (isLocked && !queryingProtection) { + activity?.let { activity -> + queryingProtection = true + val doUpdate = { activity.runOnUiThread { queryingProtection = false; updateProtectedUi() } } + protectionCheck.queryProtection(activity, PREFERENCES, doUpdate, doUpdate, doUpdate) + } + } + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenancePlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenancePlugin.kt index 377a39d7b8..15f6ae2bd4 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenancePlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenancePlugin.kt @@ -64,8 +64,6 @@ class MaintenancePlugin @Inject constructor( context.startActivity(emailIntent) } - //todo replace this with a call on startup of the application, specifically to remove - // unnecessary garbage from the log exports fun deleteLogs(keep: Int) { val logDir = File(loggerUtils.logDirectory) val files = logDir.listFiles { _: File?, name: String -> diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientPlugin.kt index d63cf23512..94a2eecbac 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientPlugin.kt @@ -170,8 +170,7 @@ class NSClientPlugin @Inject constructor( override fun onServiceConnected(name: ComponentName, service: IBinder) { aapsLogger.debug(LTag.NSCLIENT, "Service is connected") - val mLocalBinder = service as NSClientService.LocalBinder - @Suppress("UNNECESSARY_SAFE_CALL") + val mLocalBinder = service as NSClientService.LocalBinder? nsClientService = mLocalBinder?.serviceInstance // is null when running in roboelectric } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/services/NSClientService.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/services/NSClientService.kt index 970b047ddb..d5ea640f00 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/services/NSClientService.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/services/NSClientService.kt @@ -190,7 +190,7 @@ class NSClientService : DaggerService() { lastAckTime = dateUtil.now() dataWorker.enqueue( OneTimeWorkRequest.Builder(NSClientAddAckWorker::class.java) - .setInputData(dataWorker.storeInputData(ack, null)) + .setInputData(dataWorker.storeInputData(ack)) .build() ) } @@ -199,7 +199,7 @@ class NSClientService : DaggerService() { lastAckTime = dateUtil.now() dataWorker.enqueue( OneTimeWorkRequest.Builder(NSClientUpdateRemoveAckWorker::class.java) - .setInputData(dataWorker.storeInputData(ack, null)) + .setInputData(dataWorker.storeInputData(ack)) .build() ) } @@ -483,7 +483,7 @@ class NSClientService : DaggerService() { rxBus.send(EventNSClientNewLog("PROFILE", "profile received")) dataWorker.enqueue( OneTimeWorkRequest.Builder(LocalProfilePlugin.NSProfileWorker::class.java) - .setInputData(dataWorker.storeInputData(profileStoreJson, null)) + .setInputData(dataWorker.storeInputData(profileStoreJson)) .build() ) xDripBroadcast.sendProfile(profileStoreJson) @@ -502,7 +502,7 @@ class NSClientService : DaggerService() { if (addedOrUpdatedTreatments.length() > 0) { dataWorker.enqueue( OneTimeWorkRequest.Builder(NSClientAddUpdateWorker::class.java) - .setInputData(dataWorker.storeInputData(addedOrUpdatedTreatments, null)) + .setInputData(dataWorker.storeInputData(addedOrUpdatedTreatments)) .build() ) xDripBroadcast.sendTreatments(addedOrUpdatedTreatments) @@ -520,7 +520,7 @@ class NSClientService : DaggerService() { if (foods.length() > 0) rxBus.send(EventNSClientNewLog("DATA", "received " + foods.length() + " foods")) dataWorker.enqueue( OneTimeWorkRequest.Builder(FoodWorker::class.java) - .setInputData(dataWorker.storeInputData(foods, null)) + .setInputData(dataWorker.storeInputData(foods)) .build() ) } @@ -529,7 +529,7 @@ class NSClientService : DaggerService() { if (mbgArray.length() > 0) rxBus.send(EventNSClientNewLog("DATA", "received " + mbgArray.length() + " mbgs")) dataWorker.enqueue( OneTimeWorkRequest.Builder(NSClientMbgWorker::class.java) - .setInputData(dataWorker.storeInputData(mbgArray, null)) + .setInputData(dataWorker.storeInputData(mbgArray)) .build() ) } @@ -546,7 +546,7 @@ class NSClientService : DaggerService() { sp.putBoolean(R.string.key_ObjectivesbgIsAvailableInNS, true) dataWorker.enqueue( OneTimeWorkRequest.Builder(NSClientSourceWorker::class.java) - .setInputData(dataWorker.storeInputData(sgvs, null)) + .setInputData(dataWorker.storeInputData(sgvs)) .build() ) xDripBroadcast.sendSgvs(sgvs) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewData.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewData.kt index 8eaf29a08c..deb355c24d 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewData.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewData.kt @@ -1,44 +1,42 @@ package info.nightscout.androidaps.plugins.general.overview -import android.graphics.DashPathEffect -import android.graphics.Paint +import android.content.Context +import androidx.annotation.ColorInt import com.jjoe64.graphview.series.BarGraphSeries import com.jjoe64.graphview.series.DataPoint import com.jjoe64.graphview.series.LineGraphSeries -import dagger.android.HasAndroidInjector import info.nightscout.androidaps.R import info.nightscout.androidaps.data.IobTotal import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.ValueWrapper -import info.nightscout.androidaps.database.entities.Bolus import info.nightscout.androidaps.database.entities.GlucoseValue import info.nightscout.androidaps.database.entities.TemporaryTarget -import info.nightscout.androidaps.database.entities.TherapyEvent -import info.nightscout.androidaps.extensions.* -import info.nightscout.androidaps.interfaces.* -import info.nightscout.shared.logging.AAPSLogger -import info.nightscout.shared.logging.LTag -import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults -import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus -import info.nightscout.androidaps.plugins.general.overview.graphExtensions.* -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult +import info.nightscout.androidaps.extensions.convertedToPercent +import info.nightscout.androidaps.extensions.isInProgress +import info.nightscout.androidaps.extensions.toStringFull +import info.nightscout.androidaps.extensions.toStringShort +import info.nightscout.androidaps.extensions.valueToUnits +import info.nightscout.androidaps.interfaces.ActivePlugin +import info.nightscout.androidaps.interfaces.IobCobCalculator +import info.nightscout.androidaps.interfaces.ProfileFunction +import info.nightscout.androidaps.plugins.general.overview.graphExtensions.DataPointWithLabelInterface +import info.nightscout.androidaps.plugins.general.overview.graphExtensions.FixedLineGraphSeries +import info.nightscout.androidaps.plugins.general.overview.graphExtensions.PointsWithLabelGraphSeries +import info.nightscout.androidaps.plugins.general.overview.graphExtensions.Scale +import info.nightscout.androidaps.plugins.general.overview.graphExtensions.ScaledDataPoint import info.nightscout.androidaps.plugins.iob.iobCobCalculator.CobInfo -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.data.AutosensData -import info.nightscout.androidaps.utils.* +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.DefaultValueHelper +import info.nightscout.androidaps.utils.T import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.shared.logging.AAPSLogger import info.nightscout.shared.sharedPreferences.SP import java.util.* import javax.inject.Inject import javax.inject.Singleton -import kotlin.collections.ArrayList -import kotlin.math.abs -import kotlin.math.ceil -import kotlin.math.max -import kotlin.math.min @Singleton class OverviewData @Inject constructor( - private val injector: HasAndroidInjector, private val aapsLogger: AAPSLogger, private val rh: ResourceHelper, private val dateUtil: DateUtil, @@ -46,13 +44,7 @@ class OverviewData @Inject constructor( private val activePlugin: ActivePlugin, private val defaultValueHelper: DefaultValueHelper, private val profileFunction: ProfileFunction, - private val config: Config, - private val loop: Loop, - private val nsDeviceStatus: NSDeviceStatus, - private val repository: AppRepository, - private val overviewMenus: OverviewMenus, - private val iobCobCalculator: IobCobCalculator, - private val translator: Translator + private val repository: AppRepository ) { var rangeToDisplay = 6 // for graph @@ -62,14 +54,7 @@ class OverviewData @Inject constructor( fun reset() { pumpStatus = "" - calcProgress = "" - lastBg = null - bolusIob = null - basalIob = null - cobInfo = null - lastCarbsTime = 0L - temporaryTarget = null - lastAutosensData = null + calcProgressPct = 100 bgReadingsArray = ArrayList() bucketedGraphSeries = PointsWithLabelGraphSeries() bgReadingGraphSeries = PointsWithLabelGraphSeries() @@ -120,13 +105,18 @@ class OverviewData @Inject constructor( * CALC PROGRESS */ - var calcProgress: String = "" + var calcProgressPct: Int = 100 /* * BG */ - var lastBg: GlucoseValue? = null + val lastBg: GlucoseValue? + get() = + repository.getLastGlucoseValueWrapped().blockingGet().let { gvWrapped -> + if (gvWrapped is ValueWrapper.Existing) gvWrapped.value + else null + } val isLow: Boolean get() = lastBg?.let { lastBg -> @@ -138,11 +128,12 @@ class OverviewData @Inject constructor( lastBg.valueToUnits(profileFunction.getUnits()) > defaultValueHelper.determineHighLine() } ?: false - val lastBgColor: Int - get() = when { - isLow -> rh.gc(R.color.low) - isHigh -> rh.gc(R.color.high) - else -> rh.gc(R.color.inrange) + @ColorInt + fun lastBgColor(context: Context?): Int = + when { + isLow -> rh.gac(context, R.attr.bgLow) + isHigh -> rh.gac(context, R.attr.highColor) + else -> rh.gac(context, R.attr.bgInRange) } val lastBgDescription: String @@ -162,17 +153,16 @@ class OverviewData @Inject constructor( * TEMPORARY BASAL */ - val temporaryBasalText: String - get() = - profileFunction.getProfile()?.let { profile -> - var temporaryBasal = iobCobCalculator.getTempBasalIncludingConvertedExtended(dateUtil.now()) - if (temporaryBasal?.isInProgress == false) temporaryBasal = null - temporaryBasal?.let { "T:" + it.toStringShort() } - ?: rh.gs(R.string.pump_basebasalrate, profile.getBasal()) - } ?: rh.gs(R.string.notavailable) + fun temporaryBasalText(iobCobCalculator: IobCobCalculator): String = + profileFunction.getProfile()?.let { profile -> + var temporaryBasal = iobCobCalculator.getTempBasalIncludingConvertedExtended(dateUtil.now()) + if (temporaryBasal?.isInProgress == false) temporaryBasal = null + temporaryBasal?.let { "T:" + it.toStringShort() } + ?: rh.gs(R.string.pump_basebasalrate, profile.getBasal()) + } ?: rh.gs(R.string.notavailable) - val temporaryBasalDialogText: String - get() = profileFunction.getProfile()?.let { profile -> + fun temporaryBasalDialogText(iobCobCalculator: IobCobCalculator): String = + profileFunction.getProfile()?.let { profile -> iobCobCalculator.getTempBasalIncludingConvertedExtended(dateUtil.now())?.let { temporaryBasal -> "${rh.gs(R.string.basebasalrate_label)}: ${rh.gs(R.string.pump_basebasalrate, profile.getBasal())}" + "\n" + rh.gs(R.string.tempbasal_label) + ": " + temporaryBasal.toStringFull(profile, dateUtil) @@ -180,76 +170,73 @@ class OverviewData @Inject constructor( ?: "${rh.gs(R.string.basebasalrate_label)}: ${rh.gs(R.string.pump_basebasalrate, profile.getBasal())}" } ?: rh.gs(R.string.notavailable) - val temporaryBasalIcon: Int - get() = - profileFunction.getProfile()?.let { profile -> - iobCobCalculator.getTempBasalIncludingConvertedExtended(dateUtil.now())?.let { temporaryBasal -> - val percentRate = temporaryBasal.convertedToPercent(dateUtil.now(), profile) - when { - percentRate > 100 -> R.drawable.ic_cp_basal_tbr_high - percentRate < 100 -> R.drawable.ic_cp_basal_tbr_low - else -> R.drawable.ic_cp_basal_no_tbr - } + fun temporaryBasalIcon(iobCobCalculator: IobCobCalculator): Int = + profileFunction.getProfile()?.let { profile -> + iobCobCalculator.getTempBasalIncludingConvertedExtended(dateUtil.now())?.let { temporaryBasal -> + val percentRate = temporaryBasal.convertedToPercent(dateUtil.now(), profile) + when { + percentRate > 100 -> R.drawable.ic_cp_basal_tbr_high + percentRate < 100 -> R.drawable.ic_cp_basal_tbr_low + else -> R.drawable.ic_cp_basal_no_tbr } - } ?: R.drawable.ic_cp_basal_no_tbr + } + } ?: R.drawable.ic_cp_basal_no_tbr - val temporaryBasalColor: Int - get() = iobCobCalculator.getTempBasalIncludingConvertedExtended(dateUtil.now())?.let { rh.gc(R.color.basal) } - ?: rh.gc(R.color.defaulttextcolor) + fun temporaryBasalColor(context: Context?, iobCobCalculator: IobCobCalculator): Int = iobCobCalculator.getTempBasalIncludingConvertedExtended(dateUtil.now())?.let { rh.gac(context , R.attr.basal) } + ?: rh.gac(context, R.attr.defaultTextColor) /* * EXTENDED BOLUS */ - val extendedBolusText: String - get() = - iobCobCalculator.getExtendedBolus(dateUtil.now())?.let { extendedBolus -> - if (!extendedBolus.isInProgress(dateUtil)) "" - else if (!activePlugin.activePump.isFakingTempsByExtendedBoluses) rh.gs(R.string.pump_basebasalrate, extendedBolus.rate) - else "" - } ?: "" + fun extendedBolusText(iobCobCalculator: IobCobCalculator): String = + iobCobCalculator.getExtendedBolus(dateUtil.now())?.let { extendedBolus -> + if (!extendedBolus.isInProgress(dateUtil)) "" + else if (!activePlugin.activePump.isFakingTempsByExtendedBoluses) rh.gs(R.string.pump_basebasalrate, extendedBolus.rate) + else "" + } ?: "" - val extendedBolusDialogText: String - get() = iobCobCalculator.getExtendedBolus(dateUtil.now())?.toStringFull(dateUtil) ?: "" + fun extendedBolusDialogText(iobCobCalculator: IobCobCalculator): String = + iobCobCalculator.getExtendedBolus(dateUtil.now())?.toStringFull(dateUtil) ?: "" /* * IOB, COB */ - var bolusIob: IobTotal? = null - var basalIob: IobTotal? = null - var cobInfo: CobInfo? = null - var lastCarbsTime: Long = 0L + fun bolusIob(iobCobCalculator: IobCobCalculator): IobTotal = iobCobCalculator.calculateIobFromBolus().round() + fun basalIob(iobCobCalculator: IobCobCalculator): IobTotal = iobCobCalculator.calculateIobFromTempBasalsIncludingConvertedExtended().round() + fun cobInfo(iobCobCalculator: IobCobCalculator): CobInfo = iobCobCalculator.getCobInfo(true, "Overview COB") - val iobText: String - get() = - bolusIob?.let { bolusIob -> - basalIob?.let { basalIob -> - rh.gs(R.string.formatinsulinunits, bolusIob.iob + basalIob.basaliob) - } ?: rh.gs(R.string.value_unavailable_short) - } ?: rh.gs(R.string.value_unavailable_short) + val lastCarbsTime: Long + get() = repository.getLastCarbsRecordWrapped().blockingGet().let { lastCarbs -> + if (lastCarbs is ValueWrapper.Existing) lastCarbs.value.timestamp else 0L + } - val iobDialogText: String - get() = - bolusIob?.let { bolusIob -> - basalIob?.let { basalIob -> - rh.gs(R.string.formatinsulinunits, bolusIob.iob + basalIob.basaliob) + "\n" + - rh.gs(R.string.bolus) + ": " + rh.gs(R.string.formatinsulinunits, bolusIob.iob) + "\n" + - rh.gs(R.string.basal) + ": " + rh.gs(R.string.formatinsulinunits, basalIob.basaliob) - } ?: rh.gs(R.string.value_unavailable_short) - } ?: rh.gs(R.string.value_unavailable_short) + fun iobText(iobCobCalculator: IobCobCalculator): String = + rh.gs(R.string.formatinsulinunits, bolusIob(iobCobCalculator).iob + basalIob(iobCobCalculator).basaliob) + + fun iobDialogText(iobCobCalculator: IobCobCalculator): String = + rh.gs(R.string.formatinsulinunits, bolusIob(iobCobCalculator).iob + basalIob(iobCobCalculator).basaliob) + "\n" + + rh.gs(R.string.bolus) + ": " + rh.gs(R.string.formatinsulinunits, bolusIob(iobCobCalculator).iob) + "\n" + + rh.gs(R.string.basal) + ": " + rh.gs(R.string.formatinsulinunits, basalIob(iobCobCalculator).basaliob) /* * TEMP TARGET */ - var temporaryTarget: TemporaryTarget? = null + val temporaryTarget: TemporaryTarget? + get() = + repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet().let { tempTarget -> + if (tempTarget is ValueWrapper.Existing) tempTarget.value + else null + } /* * SENSITIVITY */ - var lastAutosensData: AutosensData? = null + fun lastAutosensData(iobCobCalculator: IobCobCalculator) = iobCobCalculator.ads.getLastAutosensData("Overview", aapsLogger, dateUtil) + /* * Graphs */ @@ -276,6 +263,8 @@ class OverviewData @Inject constructor( var maxTreatmentsValue = 0.0 var treatmentsSeries: PointsWithLabelGraphSeries = PointsWithLabelGraphSeries() + var maxTherapyEventValue = 0.0 + var therapyEventSeries: PointsWithLabelGraphSeries = PointsWithLabelGraphSeries() var maxIobValueFound = Double.MIN_VALUE val iobScale = Scale() @@ -309,532 +298,4 @@ class OverviewData @Inject constructor( val dsMinScale = Scale() var dsMaxSeries: LineGraphSeries = LineGraphSeries() var dsMinSeries: LineGraphSeries = LineGraphSeries() - - @Synchronized - @Suppress("SameParameterValue", "UNUSED_PARAMETER") - fun prepareBgData(from: String) { -// val start = dateUtil.now() - maxBgValue = Double.MIN_VALUE - bgReadingsArray = repository.compatGetBgReadingsDataFromTime(fromTime, toTime, false).blockingGet() - val bgListArray: MutableList = java.util.ArrayList() - for (bg in bgReadingsArray) { - if (bg.timestamp < fromTime || bg.timestamp > toTime) continue - if (bg.value > maxBgValue) maxBgValue = bg.value - bgListArray.add(GlucoseValueDataPoint(bg, defaultValueHelper, profileFunction, rh)) - } - bgListArray.sortWith { o1: DataPointWithLabelInterface, o2: DataPointWithLabelInterface -> o1.x.compareTo(o2.x) } - bgReadingGraphSeries = PointsWithLabelGraphSeries(Array(bgListArray.size) { i -> bgListArray[i] }) - maxBgValue = Profile.fromMgdlToUnits(maxBgValue, profileFunction.getUnits()) - if (defaultValueHelper.determineHighLine() > maxBgValue) maxBgValue = defaultValueHelper.determineHighLine() - maxBgValue = addUpperChartMargin(maxBgValue) -// profiler.log(LTag.UI, "prepareBgData() $from", start) - } - - @Suppress("UNUSED_PARAMETER") - @Synchronized - fun preparePredictions(from: String) { -// val start = dateUtil.now() - val apsResult = if (config.APS) loop.lastRun?.constraintsProcessed else nsDeviceStatus.getAPSResult(injector) - val predictionsAvailable = if (config.APS) loop.lastRun?.request?.hasPredictions == true else config.NSCLIENT - val menuChartSettings = overviewMenus.setting - // align to hours - val calendar = Calendar.getInstance().also { - it.timeInMillis = System.currentTimeMillis() - it[Calendar.MILLISECOND] = 0 - it[Calendar.SECOND] = 0 - it[Calendar.MINUTE] = 0 - it.add(Calendar.HOUR, 1) - } - if (predictionsAvailable && apsResult != null && menuChartSettings[0][OverviewMenus.CharType.PRE.ordinal]) { - var predictionHours = (ceil(apsResult.latestPredictionsTime - System.currentTimeMillis().toDouble()) / (60 * 60 * 1000)).toInt() - predictionHours = min(2, predictionHours) - predictionHours = max(0, predictionHours) - val hoursToFetch = rangeToDisplay - predictionHours - toTime = calendar.timeInMillis + 100000 // little bit more to avoid wrong rounding - GraphView specific - fromTime = toTime - T.hours(hoursToFetch.toLong()).msecs() - endTime = toTime + T.hours(predictionHours.toLong()).msecs() - } else { - toTime = calendar.timeInMillis + 100000 // little bit more to avoid wrong rounding - GraphView specific - fromTime = toTime - T.hours(rangeToDisplay.toLong()).msecs() - endTime = toTime - } - - val bgListArray: MutableList = java.util.ArrayList() - val predictions: MutableList? = apsResult?.predictions - ?.map { bg -> GlucoseValueDataPoint(bg, defaultValueHelper, profileFunction, rh) } - ?.toMutableList() - if (predictions != null) { - predictions.sortWith { o1: GlucoseValueDataPoint, o2: GlucoseValueDataPoint -> o1.x.compareTo(o2.x) } - for (prediction in predictions) if (prediction.data.value >= 40) bgListArray.add(prediction) - } - predictionsGraphSeries = PointsWithLabelGraphSeries(Array(bgListArray.size) { i -> bgListArray[i] }) -// profiler.log(LTag.UI, "preparePredictions() $from", start) - } - - @Synchronized - @Suppress("SameParameterValue", "UNUSED_PARAMETER") - fun prepareBucketedData(from: String) { -// val start = dateUtil.now() - val bucketedData = iobCobCalculator.ads.getBucketedDataTableCopy() ?: return - if (bucketedData.isEmpty()) { - aapsLogger.debug("No bucketed data.") - return - } - val bucketedListArray: MutableList = java.util.ArrayList() - for (inMemoryGlucoseValue in bucketedData) { - if (inMemoryGlucoseValue.timestamp < fromTime || inMemoryGlucoseValue.timestamp > toTime) continue - bucketedListArray.add(InMemoryGlucoseValueDataPoint(inMemoryGlucoseValue, profileFunction, rh)) - } - bucketedListArray.sortWith { o1: DataPointWithLabelInterface, o2: DataPointWithLabelInterface -> o1.x.compareTo(o2.x) } - bucketedGraphSeries = PointsWithLabelGraphSeries(Array(bucketedListArray.size) { i -> bucketedListArray[i] }) -// profiler.log(LTag.UI, "prepareBucketedData() $from", start) - } - - @Suppress("UNUSED_PARAMETER") - @Synchronized - fun prepareBasalData(from: String) { -// val start = dateUtil.now() - maxBasalValueFound = 0.0 - val baseBasalArray: MutableList = java.util.ArrayList() - val tempBasalArray: MutableList = java.util.ArrayList() - val basalLineArray: MutableList = java.util.ArrayList() - val absoluteBasalLineArray: MutableList = java.util.ArrayList() - var lastLineBasal = 0.0 - var lastAbsoluteLineBasal = -1.0 - var lastBaseBasal = 0.0 - var lastTempBasal = 0.0 - var time = fromTime - while (time < toTime) { - val profile = profileFunction.getProfile(time) - if (profile == null) { - time += 60 * 1000L - continue - } - val basalData = iobCobCalculator.getBasalData(profile, time) - val baseBasalValue = basalData.basal - var absoluteLineValue = baseBasalValue - var tempBasalValue = 0.0 - var basal = 0.0 - if (basalData.isTempBasalRunning) { - tempBasalValue = basalData.tempBasalAbsolute - absoluteLineValue = tempBasalValue - if (tempBasalValue != lastTempBasal) { - tempBasalArray.add(ScaledDataPoint(time, lastTempBasal, basalScale)) - tempBasalArray.add(ScaledDataPoint(time, tempBasalValue.also { basal = it }, basalScale)) - } - if (lastBaseBasal != 0.0) { - baseBasalArray.add(ScaledDataPoint(time, lastBaseBasal, basalScale)) - baseBasalArray.add(ScaledDataPoint(time, 0.0, basalScale)) - lastBaseBasal = 0.0 - } - } else { - if (baseBasalValue != lastBaseBasal) { - baseBasalArray.add(ScaledDataPoint(time, lastBaseBasal, basalScale)) - baseBasalArray.add(ScaledDataPoint(time, baseBasalValue.also { basal = it }, basalScale)) - lastBaseBasal = baseBasalValue - } - if (lastTempBasal != 0.0) { - tempBasalArray.add(ScaledDataPoint(time, lastTempBasal, basalScale)) - tempBasalArray.add(ScaledDataPoint(time, 0.0, basalScale)) - } - } - if (baseBasalValue != lastLineBasal) { - basalLineArray.add(ScaledDataPoint(time, lastLineBasal, basalScale)) - basalLineArray.add(ScaledDataPoint(time, baseBasalValue, basalScale)) - } - if (absoluteLineValue != lastAbsoluteLineBasal) { - absoluteBasalLineArray.add(ScaledDataPoint(time, lastAbsoluteLineBasal, basalScale)) - absoluteBasalLineArray.add(ScaledDataPoint(time, basal, basalScale)) - } - lastAbsoluteLineBasal = absoluteLineValue - lastLineBasal = baseBasalValue - lastTempBasal = tempBasalValue - maxBasalValueFound = max(maxBasalValueFound, max(tempBasalValue, baseBasalValue)) - time += 60 * 1000L - } - - // final points - basalLineArray.add(ScaledDataPoint(toTime, lastLineBasal, basalScale)) - baseBasalArray.add(ScaledDataPoint(toTime, lastBaseBasal, basalScale)) - tempBasalArray.add(ScaledDataPoint(toTime, lastTempBasal, basalScale)) - absoluteBasalLineArray.add(ScaledDataPoint(toTime, lastAbsoluteLineBasal, basalScale)) - - // create series - baseBasalGraphSeries = LineGraphSeries(Array(baseBasalArray.size) { i -> baseBasalArray[i] }).also { - it.isDrawBackground = true - it.backgroundColor = rh.gc(R.color.basebasal) - it.thickness = 0 - } - tempBasalGraphSeries = LineGraphSeries(Array(tempBasalArray.size) { i -> tempBasalArray[i] }).also { - it.isDrawBackground = true - it.backgroundColor = rh.gc(R.color.tempbasal) - it.thickness = 0 - } - basalLineGraphSeries = LineGraphSeries(Array(basalLineArray.size) { i -> basalLineArray[i] }).also { - it.setCustomPaint(Paint().also { paint -> - paint.style = Paint.Style.STROKE - paint.strokeWidth = rh.getDisplayMetrics().scaledDensity * 2 - paint.pathEffect = DashPathEffect(floatArrayOf(2f, 4f), 0f) - paint.color = rh.gc(R.color.basal) - }) - } - absoluteBasalGraphSeries = LineGraphSeries(Array(absoluteBasalLineArray.size) { i -> absoluteBasalLineArray[i] }).also { - it.setCustomPaint(Paint().also { absolutePaint -> - absolutePaint.style = Paint.Style.STROKE - absolutePaint.strokeWidth = rh.getDisplayMetrics().scaledDensity * 2 - absolutePaint.color = rh.gc(R.color.basal) - }) - } -// profiler.log(LTag.UI, "prepareBasalData() $from", start) - } - - @Suppress("UNUSED_PARAMETER") - @Synchronized - fun prepareTemporaryTargetData(from: String) { -// val start = dateUtil.now() - val profile = profileFunction.getProfile() ?: return - val units = profileFunction.getUnits() - var toTime = toTime - val targetsSeriesArray: MutableList = java.util.ArrayList() - var lastTarget = -1.0 - loop.lastRun?.constraintsProcessed?.let { toTime = max(it.latestPredictionsTime, toTime) } - var time = fromTime - while (time < toTime) { - val tt = repository.getTemporaryTargetActiveAt(time).blockingGet() - val value: Double = if (tt is ValueWrapper.Existing) { - Profile.fromMgdlToUnits(tt.value.target(), units) - } else { - Profile.fromMgdlToUnits((profile.getTargetLowMgdl(time) + profile.getTargetHighMgdl(time)) / 2, units) - } - if (lastTarget != value) { - if (lastTarget != -1.0) targetsSeriesArray.add(DataPoint(time.toDouble(), lastTarget)) - targetsSeriesArray.add(DataPoint(time.toDouble(), value)) - } - lastTarget = value - time += 5 * 60 * 1000L - } - // final point - targetsSeriesArray.add(DataPoint(toTime.toDouble(), lastTarget)) - // create series - temporaryTargetSeries = LineGraphSeries(Array(targetsSeriesArray.size) { i -> targetsSeriesArray[i] }).also { - it.isDrawBackground = false - it.color = rh.gc(R.color.tempTargetBackground) - it.thickness = 2 - } -// profiler.log(LTag.UI, "prepareTemporaryTargetData() $from", start) - } - - @Suppress("UNUSED_PARAMETER") - @Synchronized - fun prepareTreatmentsData(from: String) { -// val start = dateUtil.now() - maxTreatmentsValue = 0.0 - val filteredTreatments: MutableList = java.util.ArrayList() - repository.getBolusesDataFromTimeToTime(fromTime, endTime, true).blockingGet() - .map { BolusDataPoint(it, rh, activePlugin, defaultValueHelper) } - .filter { it.data.type == Bolus.Type.NORMAL || it.data.type == Bolus.Type.SMB } - .forEach { - it.y = getNearestBg(it.x.toLong()) - filteredTreatments.add(it) - } - repository.getCarbsDataFromTimeToTimeExpanded(fromTime, endTime, true).blockingGet() - .map { CarbsDataPoint(it, rh) } - .forEach { - it.y = getNearestBg(it.x.toLong()) - filteredTreatments.add(it) - } - - // ProfileSwitch - repository.getEffectiveProfileSwitchDataFromTimeToTime(fromTime, endTime, true).blockingGet() - .map { EffectiveProfileSwitchDataPoint(it) } - .forEach(filteredTreatments::add) - - // OfflineEvent - repository.getOfflineEventDataFromTimeToTime(fromTime, endTime, true).blockingGet() - .map { - TherapyEventDataPoint( - TherapyEvent(timestamp = it.timestamp, duration = it.duration, type = TherapyEvent.Type.APS_OFFLINE, glucoseUnit = TherapyEvent.GlucoseUnit.MMOL), - rh, - profileFunction, - translator - ) - } - .forEach(filteredTreatments::add) - - // Extended bolus - if (!activePlugin.activePump.isFakingTempsByExtendedBoluses) { - repository.getExtendedBolusDataFromTimeToTime(fromTime, endTime, true).blockingGet() - .map { ExtendedBolusDataPoint(it) } - .filter { it.duration != 0L } - .forEach { - it.y = getNearestBg(it.x.toLong()) - filteredTreatments.add(it) - } - } - - // Careportal - repository.compatGetTherapyEventDataFromToTime(fromTime - T.hours(6).msecs(), endTime).blockingGet() - .map { TherapyEventDataPoint(it, rh, profileFunction, translator) } - .filterTimeframe(fromTime, endTime) - .forEach { - if (it.y == 0.0) it.y = getNearestBg(it.x.toLong()) - filteredTreatments.add(it) - } - - // increase maxY if a treatment forces it's own height that's higher than a BG value - filteredTreatments.map { it.y } - .maxOrNull() - ?.let(::addUpperChartMargin) - ?.let { maxTreatmentsValue = maxOf(maxTreatmentsValue, it) } - - treatmentsSeries = PointsWithLabelGraphSeries(filteredTreatments.toTypedArray()) -// profiler.log(LTag.UI, "prepareTreatmentsData() $from", start) - } - - @Suppress("UNUSED_PARAMETER") - @Synchronized - fun prepareIobAutosensData(from: String) { -// val start = dateUtil.now() - val iobArray: MutableList = java.util.ArrayList() - val absIobArray: MutableList = java.util.ArrayList() - maxIobValueFound = Double.MIN_VALUE - var lastIob = 0.0 - var absLastIob = 0.0 - var time = fromTime - - val minFailOverActiveList: MutableList = java.util.ArrayList() - val cobArray: MutableList = java.util.ArrayList() - maxCobValueFound = Double.MIN_VALUE - var lastCob = 0 - - val actArrayHist: MutableList = java.util.ArrayList() - val actArrayPrediction: MutableList = java.util.ArrayList() - val now = dateUtil.now().toDouble() - maxIAValue = 0.0 - - val bgiArrayHist: MutableList = java.util.ArrayList() - val bgiArrayPrediction: MutableList = java.util.ArrayList() - maxBGIValue = Double.MIN_VALUE - - val devArray: MutableList = java.util.ArrayList() - maxDevValueFound = Double.MIN_VALUE - - val ratioArray: MutableList = java.util.ArrayList() - maxRatioValueFound = 5.0 //even if sens data equals 0 for all the period, minimum scale is between 95% and 105% - minRatioValueFound = -5.0 - - val dsMaxArray: MutableList = java.util.ArrayList() - val dsMinArray: MutableList = java.util.ArrayList() - maxFromMaxValueFound = Double.MIN_VALUE - maxFromMinValueFound = Double.MIN_VALUE - - val adsData = iobCobCalculator.ads.clone() - - while (time <= toTime) { - val profile = profileFunction.getProfile(time) - if (profile == null) { - time += 5 * 60 * 1000L - continue - } - // IOB - val iob = iobCobCalculator.calculateFromTreatmentsAndTemps(time, profile) - val baseBasalIob = iobCobCalculator.calculateAbsoluteIobFromBaseBasals(time) - val absIob = IobTotal.combine(iob, baseBasalIob) - val autosensData = adsData.getAutosensDataAtTime(time) - if (abs(lastIob - iob.iob) > 0.02) { - if (abs(lastIob - iob.iob) > 0.2) iobArray.add(ScaledDataPoint(time, lastIob, iobScale)) - iobArray.add(ScaledDataPoint(time, iob.iob, iobScale)) - maxIobValueFound = maxOf(maxIobValueFound, abs(iob.iob)) - lastIob = iob.iob - } - if (abs(absLastIob - absIob.iob) > 0.02) { - if (abs(absLastIob - absIob.iob) > 0.2) absIobArray.add(ScaledDataPoint(time, absLastIob, iobScale)) - absIobArray.add(ScaledDataPoint(time, absIob.iob, iobScale)) - maxIobValueFound = maxOf(maxIobValueFound, abs(absIob.iob)) - absLastIob = absIob.iob - } - - // COB - if (autosensData != null) { - val cob = autosensData.cob.toInt() - if (cob != lastCob) { - if (autosensData.carbsFromBolus > 0) cobArray.add(ScaledDataPoint(time, lastCob.toDouble(), cobScale)) - cobArray.add(ScaledDataPoint(time, cob.toDouble(), cobScale)) - maxCobValueFound = max(maxCobValueFound, cob.toDouble()) - lastCob = cob - } - if (autosensData.failOverToMinAbsorptionRate) { - autosensData.scale = cobScale - autosensData.chartTime = time - minFailOverActiveList.add(autosensData) - } - } - - // ACTIVITY - if (time <= now) actArrayHist.add(ScaledDataPoint(time, iob.activity, actScale)) - else actArrayPrediction.add(ScaledDataPoint(time, iob.activity, actScale)) - maxIAValue = max(maxIAValue, abs(iob.activity)) - - // BGI - val devBgiScale = overviewMenus.isEnabledIn(OverviewMenus.CharType.DEV) == overviewMenus.isEnabledIn(OverviewMenus.CharType.BGI) - val deviation = if (devBgiScale) autosensData?.deviation ?: 0.0 else 0.0 - val bgi: Double = iob.activity * profile.getIsfMgdl(time) * 5.0 - if (time <= now) bgiArrayHist.add(ScaledDataPoint(time, bgi, bgiScale)) - else bgiArrayPrediction.add(ScaledDataPoint(time, bgi, bgiScale)) - maxBGIValue = max(maxBGIValue, max(abs(bgi), deviation)) - - // DEVIATIONS - if (autosensData != null) { - var color = rh.gc(R.color.deviationblack) // "=" - if (autosensData.type == "" || autosensData.type == "non-meal") { - if (autosensData.pastSensitivity == "C") color = rh.gc(R.color.deviationgrey) - if (autosensData.pastSensitivity == "+") color = rh.gc(R.color.deviationgreen) - if (autosensData.pastSensitivity == "-") color = rh.gc(R.color.deviationred) - } else if (autosensData.type == "uam") { - color = rh.gc(R.color.uam) - } else if (autosensData.type == "csf") { - color = rh.gc(R.color.deviationgrey) - } - devArray.add(OverviewPlugin.DeviationDataPoint(time.toDouble(), autosensData.deviation, color, devScale)) - maxDevValueFound = maxOf(maxDevValueFound, abs(autosensData.deviation), abs(bgi)) - } - - // RATIO - if (autosensData != null) { - ratioArray.add(ScaledDataPoint(time, 100.0 * (autosensData.autosensResult.ratio - 1), ratioScale)) - maxRatioValueFound = max(maxRatioValueFound, 100.0 * (autosensData.autosensResult.ratio - 1)) - minRatioValueFound = min(minRatioValueFound, 100.0 * (autosensData.autosensResult.ratio - 1)) - } - - // DEV SLOPE - if (autosensData != null) { - dsMaxArray.add(ScaledDataPoint(time, autosensData.slopeFromMaxDeviation, dsMaxScale)) - dsMinArray.add(ScaledDataPoint(time, autosensData.slopeFromMinDeviation, dsMinScale)) - maxFromMaxValueFound = max(maxFromMaxValueFound, abs(autosensData.slopeFromMaxDeviation)) - maxFromMinValueFound = max(maxFromMinValueFound, abs(autosensData.slopeFromMinDeviation)) - } - - time += 5 * 60 * 1000L - } - // IOB - iobSeries = FixedLineGraphSeries(Array(iobArray.size) { i -> iobArray[i] }).also { - it.isDrawBackground = true - it.backgroundColor = -0x7f000001 and rh.gc(R.color.iob) //50% - it.color = rh.gc(R.color.iob) - it.thickness = 3 - } - absIobSeries = FixedLineGraphSeries(Array(absIobArray.size) { i -> absIobArray[i] }).also { - it.isDrawBackground = true - it.backgroundColor = -0x7f000001 and rh.gc(R.color.iob) //50% - it.color = rh.gc(R.color.iob) - it.thickness = 3 - } - - if (overviewMenus.setting[0][OverviewMenus.CharType.PRE.ordinal]) { - val autosensData = adsData.getLastAutosensData("GraphData", aapsLogger, dateUtil) - val lastAutosensResult = autosensData?.autosensResult ?: AutosensResult() - val isTempTarget = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet() is ValueWrapper.Existing - val iobPrediction: MutableList = java.util.ArrayList() - val iobPredictionArray = iobCobCalculator.calculateIobArrayForSMB(lastAutosensResult, SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget) - for (i in iobPredictionArray) { - iobPrediction.add(i.setColor(rh.gc(R.color.iobPredAS))) - maxIobValueFound = max(maxIobValueFound, abs(i.iob)) - } - iobPredictions1Series = PointsWithLabelGraphSeries(Array(iobPrediction.size) { i -> iobPrediction[i] }) - aapsLogger.debug(LTag.AUTOSENS, "IOB prediction for AS=" + DecimalFormatter.to2Decimal(lastAutosensResult.ratio) + ": " + iobCobCalculator.iobArrayToString(iobPredictionArray)) - /* - val iobPrediction2: MutableList = java.util.ArrayList() - val iobPredictionArray2 = iobCobCalculator.calculateIobArrayForSMB(AutosensResult(), SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget) - for (i in iobPredictionArray2) { - iobPrediction2.add(i.setColor(rh.gc(R.color.iobPred))) - maxIobValueFound = max(maxIobValueFound, abs(i.iob)) - } - iobPredictions2Series = PointsWithLabelGraphSeries(Array(iobPrediction2.size) { i -> iobPrediction2[i] }) - aapsLogger.debug(LTag.AUTOSENS, "IOB prediction for AS=" + DecimalFormatter.to2Decimal(1.0) + ": " + iobCobCalculator.iobArrayToString(iobPredictionArray2)) - */ - } else { - iobPredictions1Series = PointsWithLabelGraphSeries() - //iobPredictions2Series = PointsWithLabelGraphSeries() - } - - // COB - cobSeries = FixedLineGraphSeries(Array(cobArray.size) { i -> cobArray[i] }).also { - it.isDrawBackground = true - it.backgroundColor = -0x7f000001 and rh.gc(R.color.cob) //50% - it.color = rh.gc(R.color.cob) - it.thickness = 3 - } - cobMinFailOverSeries = PointsWithLabelGraphSeries(Array(minFailOverActiveList.size) { i -> minFailOverActiveList[i] }) - - // ACTIVITY - activitySeries = FixedLineGraphSeries(Array(actArrayHist.size) { i -> actArrayHist[i] }).also { - it.isDrawBackground = false - it.color = rh.gc(R.color.activity) - it.thickness = 3 - } - activityPredictionSeries = FixedLineGraphSeries(Array(actArrayPrediction.size) { i -> actArrayPrediction[i] }).also { - it.setCustomPaint(Paint().also { paint -> - paint.style = Paint.Style.STROKE - paint.strokeWidth = 3f - paint.pathEffect = DashPathEffect(floatArrayOf(4f, 4f), 0f) - paint.color = rh.gc(R.color.activity) - }) - } - - // BGI - minusBgiSeries = FixedLineGraphSeries(Array(bgiArrayHist.size) { i -> bgiArrayHist[i] }).also { - it.isDrawBackground = false - it.color = rh.gc(R.color.bgi) - it.thickness = 3 - } - minusBgiHistSeries = FixedLineGraphSeries(Array(bgiArrayPrediction.size) { i -> bgiArrayPrediction[i] }).also { - it.setCustomPaint(Paint().also { paint -> - paint.style = Paint.Style.STROKE - paint.strokeWidth = 3f - paint.pathEffect = DashPathEffect(floatArrayOf(4f, 4f), 0f) - paint.color = rh.gc(R.color.bgi) - }) - } - - // DEVIATIONS - deviationsSeries = BarGraphSeries(Array(devArray.size) { i -> devArray[i] }).also { - it.setValueDependentColor { data: OverviewPlugin.DeviationDataPoint -> data.color } - } - - // RATIO - ratioSeries = LineGraphSeries(Array(ratioArray.size) { i -> ratioArray[i] }).also { - it.color = rh.gc(R.color.ratio) - it.thickness = 3 - } - - // DEV SLOPE - dsMaxSeries = LineGraphSeries(Array(dsMaxArray.size) { i -> dsMaxArray[i] }).also { - it.color = rh.gc(R.color.devslopepos) - it.thickness = 3 - } - dsMinSeries = LineGraphSeries(Array(dsMinArray.size) { i -> dsMinArray[i] }).also { - it.color = rh.gc(R.color.devslopeneg) - it.thickness = 3 - } - -// profiler.log(LTag.UI, "prepareIobAutosensData() $from", start) - } - - private fun addUpperChartMargin(maxBgValue: Double) = - if (profileFunction.getUnits() == GlucoseUnit.MGDL) Round.roundTo(maxBgValue, 40.0) + 80 else Round.roundTo(maxBgValue, 2.0) + 4 - - private fun getNearestBg(date: Long): Double { - bgReadingsArray.let { bgReadingsArray -> - for (reading in bgReadingsArray) { - if (reading.timestamp > date) continue - return Profile.fromMgdlToUnits(reading.value, profileFunction.getUnits()) - } - return if (bgReadingsArray.isNotEmpty()) Profile.fromMgdlToUnits(bgReadingsArray[0].value, profileFunction.getUnits()) - else Profile.fromMgdlToUnits(100.0, profileFunction.getUnits()) - } - } - - private fun List.filterTimeframe(fromTime: Long, endTime: Long): List = - filter { it.x + it.duration >= fromTime && it.x <= endTime } - } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.kt index a3b4f11b15..7244b943f5 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.kt @@ -5,8 +5,9 @@ import android.app.NotificationManager import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent -import android.graphics.Color import android.graphics.Paint +import android.graphics.PorterDuff +import android.graphics.PorterDuffColorFilter import android.graphics.drawable.AnimationDrawable import android.os.Build import android.os.Bundle @@ -35,17 +36,12 @@ import info.nightscout.androidaps.database.entities.UserEntry.Sources import info.nightscout.androidaps.database.interfaces.end import info.nightscout.androidaps.databinding.OverviewFragmentBinding import info.nightscout.androidaps.dialogs.* -import info.nightscout.androidaps.events.EventAcceptOpenLoopChange -import info.nightscout.androidaps.events.EventInitializationChanged -import info.nightscout.androidaps.events.EventPreferenceChange -import info.nightscout.androidaps.events.EventPumpStatusChanged -import info.nightscout.androidaps.events.EventRefreshOverview +import info.nightscout.androidaps.events.* import info.nightscout.androidaps.extensions.directionToIcon -import info.nightscout.androidaps.extensions.isInProgress +import info.nightscout.androidaps.extensions.runOnUiThread import info.nightscout.androidaps.extensions.toVisibility import info.nightscout.androidaps.extensions.valueToUnitsString import info.nightscout.androidaps.interfaces.* -import info.nightscout.shared.logging.AAPSLogger import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.plugins.aps.loop.events.EventNewOpenLoopNotification import info.nightscout.androidaps.plugins.aps.openAPSSMB.DetermineBasalResultSMB @@ -61,6 +57,7 @@ import info.nightscout.androidaps.plugins.general.overview.notifications.Notific import info.nightscout.androidaps.plugins.general.wear.events.EventWearInitiateAction import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProvider import info.nightscout.androidaps.plugins.pump.common.defs.PumpType +import info.nightscout.androidaps.plugins.pump.omnipod.eros.OmnipodErosPumpPlugin import info.nightscout.androidaps.plugins.source.DexcomPlugin import info.nightscout.androidaps.plugins.source.XdripPlugin import info.nightscout.androidaps.skins.SkinProvider @@ -74,16 +71,16 @@ import info.nightscout.androidaps.utils.buildHelper.BuildHelper import info.nightscout.androidaps.utils.protection.ProtectionCheck import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.rx.AapsSchedulers -import info.nightscout.shared.sharedPreferences.SP import info.nightscout.androidaps.utils.ui.SingleClickButton import info.nightscout.androidaps.utils.ui.UIRunnable import info.nightscout.androidaps.utils.wizard.QuickWizard +import info.nightscout.shared.logging.AAPSLogger +import info.nightscout.shared.sharedPreferences.SP import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign import java.util.* import java.util.concurrent.TimeUnit import javax.inject.Inject -import kotlin.collections.ArrayList import kotlin.math.abs import kotlin.math.min @@ -150,10 +147,11 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList _binding = it //check screen width dm = DisplayMetrics() - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) + @Suppress("DEPRECATION") + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) activity?.display?.getRealMetrics(dm) else - @Suppress("DEPRECATION") activity?.windowManager?.defaultDisplay?.getMetrics(dm) + activity?.windowManager?.defaultDisplay?.getMetrics(dm) }.root override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -166,13 +164,13 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList smallHeight = screenHeight <= Constants.SMALL_HEIGHT val landscape = screenHeight < screenWidth - skinProvider.activeSkin().preProcessLandscapeOverviewLayout(dm, view, landscape, rh.gb(R.bool.isTablet), smallHeight) + skinProvider.activeSkin().preProcessLandscapeOverviewLayout(dm, binding, landscape, rh.gb(R.bool.isTablet), smallHeight) binding.nsclientLayout.visibility = config.NSCLIENT.toVisibility() binding.notifications.setHasFixedSize(false) binding.notifications.layoutManager = LinearLayoutManager(view.context) axisWidth = if (dm.densityDpi <= 120) 3 else if (dm.densityDpi <= 160) 10 else if (dm.densityDpi <= 320) 35 else if (dm.densityDpi <= 420) 50 else if (dm.densityDpi <= 560) 70 else 80 - binding.graphsLayout.bgGraph.gridLabelRenderer?.gridColor = rh.gc(R.color.graphgrid) + binding.graphsLayout.bgGraph.gridLabelRenderer?.gridColor = rh.gac(context, R.attr.graphgrid) binding.graphsLayout.bgGraph.gridLabelRenderer?.reloadStyles() binding.graphsLayout.bgGraph.gridLabelRenderer?.labelVerticalWidth = axisWidth binding.graphsLayout.bgGraph.layoutParams?.height = rh.dpToPx(skinProvider.activeSkin().mainGraphHeight) @@ -186,16 +184,12 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList overviewData.rangeToDisplay += 6 overviewData.rangeToDisplay = if (overviewData.rangeToDisplay > 24) 6 else overviewData.rangeToDisplay sp.putInt(R.string.key_rangetodisplay, overviewData.rangeToDisplay) - overviewData.initRange() - overviewData.prepareBucketedData("EventBucketedDataCreated") - overviewData.prepareBgData("EventBucketedDataCreated") - updateGraph("rangeChange") rxBus.send(EventPreferenceChange(rh, R.string.key_rangetodisplay)) sp.putBoolean(R.string.key_objectiveusescale, true) false } prepareGraphsIfNeeded(overviewMenus.setting.size) - overviewMenus.setupChartMenu(binding.graphsLayout.chartMenuButton) + context?.let { overviewMenus.setupChartMenu(it, binding.graphsLayout.chartMenuButton) } binding.activeProfile.setOnClickListener(this) binding.activeProfile.setOnLongClickListener(this) @@ -225,113 +219,107 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList @Synchronized override fun onResume() { super.onResume() - disposable += activePlugin.activeOverview.overviewBus - .toObservable(EventUpdateOverviewTime::class.java) - .debounce(1L, TimeUnit.SECONDS) - .observeOn(aapsSchedulers.main) - .subscribe({ updateTime(it.from) }, fabricPrivacy::logException) disposable += activePlugin.activeOverview.overviewBus .toObservable(EventUpdateOverviewCalcProgress::class.java) .observeOn(aapsSchedulers.main) - .subscribe({ updateCalcProgress(it.from) }, fabricPrivacy::logException) - disposable += activePlugin.activeOverview.overviewBus - .toObservable(EventUpdateOverviewProfile::class.java) - .debounce(1L, TimeUnit.SECONDS) - .observeOn(aapsSchedulers.main) - .subscribe({ updateProfile(it.from) }, fabricPrivacy::logException) - disposable += activePlugin.activeOverview.overviewBus - .toObservable(EventUpdateOverviewTemporaryBasal::class.java) - .debounce(1L, TimeUnit.SECONDS) - .observeOn(aapsSchedulers.main) - .subscribe({ updateTemporaryBasal(it.from) }, fabricPrivacy::logException) - disposable += activePlugin.activeOverview.overviewBus - .toObservable(EventUpdateOverviewExtendedBolus::class.java) - .debounce(1L, TimeUnit.SECONDS) - .observeOn(aapsSchedulers.main) - .subscribe({ updateExtendedBolus(it.from) }, fabricPrivacy::logException) - disposable += activePlugin.activeOverview.overviewBus - .toObservable(EventUpdateOverviewTemporaryTarget::class.java) - .debounce(1L, TimeUnit.SECONDS) - .observeOn(aapsSchedulers.main) - .subscribe({ updateTemporaryTarget(it.from) }, fabricPrivacy::logException) - disposable += activePlugin.activeOverview.overviewBus - .toObservable(EventUpdateOverviewBg::class.java) - .debounce(1L, TimeUnit.SECONDS) - .observeOn(aapsSchedulers.main) - .subscribe({ updateBg(it.from) }, fabricPrivacy::logException) + .subscribe({ updateCalcProgress() }, fabricPrivacy::logException) disposable += activePlugin.activeOverview.overviewBus .toObservable(EventUpdateOverviewIobCob::class.java) .debounce(1L, TimeUnit.SECONDS) .observeOn(aapsSchedulers.main) - .subscribe({ updateIobCob(it.from) }, fabricPrivacy::logException) + .subscribe({ updateIobCob() }, fabricPrivacy::logException) disposable += activePlugin.activeOverview.overviewBus .toObservable(EventUpdateOverviewSensitivity::class.java) .debounce(1L, TimeUnit.SECONDS) .observeOn(aapsSchedulers.main) - .subscribe({ updateSensitivity(it.from) }, fabricPrivacy::logException) + .subscribe({ updateSensitivity() }, fabricPrivacy::logException) disposable += activePlugin.activeOverview.overviewBus .toObservable(EventUpdateOverviewGraph::class.java) .debounce(1L, TimeUnit.SECONDS) .observeOn(aapsSchedulers.main) - .subscribe({ updateGraph(it.from) }, fabricPrivacy::logException) + .subscribe({ updateGraph() }, fabricPrivacy::logException) disposable += activePlugin.activeOverview.overviewBus .toObservable(EventUpdateOverviewPumpStatus::class.java) .observeOn(aapsSchedulers.main) - .subscribe({ updatePumpStatus(it.from) }, fabricPrivacy::logException) + .subscribe({ updatePumpStatus() }, fabricPrivacy::logException) disposable += activePlugin.activeOverview.overviewBus .toObservable(EventUpdateOverviewNotification::class.java) .observeOn(aapsSchedulers.main) - .subscribe({ updateNotification(it.from) }, fabricPrivacy::logException) + .subscribe({ updateNotification() }, fabricPrivacy::logException) + disposable += rxBus + .toObservable(EventNewBG::class.java) + .debounce(1L, TimeUnit.SECONDS) + .observeOn(aapsSchedulers.main) + .subscribe({ updateBg() }, fabricPrivacy::logException) disposable += rxBus .toObservable(EventRefreshOverview::class.java) .observeOn(aapsSchedulers.io) .subscribe({ - if (it.now) overviewPlugin.refreshLoop(it.from) - else scheduleUpdateGUI(it.from) + if (it.now) refreshAll() + else scheduleUpdateGUI() }, fabricPrivacy::logException) disposable += rxBus .toObservable(EventAcceptOpenLoopChange::class.java) .observeOn(aapsSchedulers.io) - .subscribe({ scheduleUpdateGUI("EventAcceptOpenLoopChange") }, fabricPrivacy::logException) - disposable += rxBus - .toObservable(EventInitializationChanged::class.java) - .observeOn(aapsSchedulers.main) - .subscribe({ updateTime("EventInitializationChanged") }, fabricPrivacy::logException) + .subscribe({ scheduleUpdateGUI() }, fabricPrivacy::logException) disposable += rxBus .toObservable(EventPreferenceChange::class.java) .observeOn(aapsSchedulers.io) - .subscribe({ scheduleUpdateGUI("EventPreferenceChange") }, fabricPrivacy::logException) + .subscribe({ scheduleUpdateGUI() }, fabricPrivacy::logException) disposable += rxBus .toObservable(EventNewOpenLoopNotification::class.java) .observeOn(aapsSchedulers.io) - .subscribe({ scheduleUpdateGUI("EventNewOpenLoopNotification") }, fabricPrivacy::logException) + .subscribe({ scheduleUpdateGUI() }, fabricPrivacy::logException) disposable += rxBus .toObservable(EventPumpStatusChanged::class.java) .observeOn(aapsSchedulers.main) .delay(30, TimeUnit.MILLISECONDS, aapsSchedulers.main) .subscribe({ overviewData.pumpStatus = it.getStatus(rh) - updatePumpStatus("EventPumpStatusChanged") + updatePumpStatus() }, fabricPrivacy::logException) + disposable += rxBus + .toObservable(EventEffectiveProfileSwitchChanged::class.java) + .observeOn(aapsSchedulers.main) + .subscribe({ updateProfile() }, fabricPrivacy::logException) + disposable += rxBus + .toObservable(EventTempTargetChange::class.java) + .observeOn(aapsSchedulers.main) + .subscribe({ updateTemporaryTarget() }, fabricPrivacy::logException) + disposable += rxBus + .toObservable(EventExtendedBolusChange::class.java) + .observeOn(aapsSchedulers.main) + .subscribe({ updateExtendedBolus() }, fabricPrivacy::logException) + disposable += rxBus + .toObservable(EventTempBasalChange::class.java) + .observeOn(aapsSchedulers.main) + .subscribe({ updateTemporaryBasal() }, fabricPrivacy::logException) refreshLoop = Runnable { - overviewPlugin.refreshLoop("refreshLoop") + refreshAll() handler.postDelayed(refreshLoop, 60 * 1000L) } handler.postDelayed(refreshLoop, 60 * 1000L) - updateTime("onResume") - updateCalcProgress("onResume") - updateProfile("onResume") - updateTemporaryBasal("onResume") - updateExtendedBolus("onResume") - updateTemporaryTarget("onResume") - updateBg("onResume") - updateIobCob("onResume") - updateSensitivity("onResume") - updateGraph("onResume") - updatePumpStatus("onResume") - updateNotification("onResume") + refreshAll() + updatePumpStatus() + updateCalcProgress() + } + + fun refreshAll() { + runOnUiThread { + _binding ?: return@runOnUiThread + updateBg() + updateTime() + updateProfile() + updateTemporaryBasal() + updateExtendedBolus() + updateTemporaryTarget() + updateIobCob() + updateSensitivity() + updateGraph() + updateNotification() + } } @Synchronized @@ -415,14 +403,15 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList loop.invoke("Accept temp button", false) if (lastRun?.lastAPSRun != null && lastRun.constraintsProcessed?.isChangeRequested == true) { protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, UIRunnable { - OKDialog.showConfirmation(activity, rh.gs(R.string.tempbasal_label), lastRun.constraintsProcessed?.toSpanned() - ?: "".toSpanned(), { - uel.log(Action.ACCEPTS_TEMP_BASAL, Sources.Overview) - (context?.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager?)?.cancel(Constants.notificationID) - rxBus.send(EventWearInitiateAction("cancelChangeRequest")) - Thread { loop.acceptChangeRequest() }.run() - binding.buttonsLayout.acceptTempButton.visibility = View.GONE - }) + if (isAdded) + OKDialog.showConfirmation(activity, rh.gs(R.string.tempbasal_label), lastRun.constraintsProcessed?.toSpanned() + ?: "".toSpanned(), { + uel.log(Action.ACCEPTS_TEMP_BASAL, Sources.Overview) + (context?.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager?)?.cancel(Constants.notificationID) + rxBus.send(EventWearInitiateAction("cancelChangeRequest")) + Thread { loop.acceptChangeRequest() }.run() + binding.buttonsLayout.acceptTempButton.visibility = View.GONE + }) }) } } @@ -558,10 +547,18 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList binding.buttonsLayout.calibrationButton.visibility = (xDripIsBgSource && actualBG != null && sp.getBoolean(R.string.key_show_calibration_button, true)).toVisibility() if (dexcomIsSource) { binding.buttonsLayout.cgmButton.setCompoundDrawablesWithIntrinsicBounds(null, rh.gd(R.drawable.ic_byoda), null, null) - binding.buttonsLayout.cgmButton.setTextColor(rh.gc(R.color.colorLightGray)) + for (drawable in binding.buttonsLayout.cgmButton.compoundDrawables) { + drawable?.mutate() + drawable?.colorFilter = PorterDuffColorFilter(rh.gac(context, R.attr.cgmdexColor), PorterDuff.Mode.SRC_IN) + } + binding.buttonsLayout.cgmButton.setTextColor(rh.gac(context, R.attr.cgmdexColor)) } else if (xDripIsBgSource) { binding.buttonsLayout.cgmButton.setCompoundDrawablesWithIntrinsicBounds(null, rh.gd(R.drawable.ic_xdrip), null, null) - binding.buttonsLayout.cgmButton.setTextColor(rh.gc(R.color.colorCalibrationButton)) + for (drawable in binding.buttonsLayout.cgmButton.compoundDrawables) { + drawable?.mutate() + drawable?.colorFilter = PorterDuffColorFilter(rh.gac(context, R.attr.cgmxdripColor), PorterDuff.Mode.SRC_IN) + } + binding.buttonsLayout.cgmButton.setTextColor(rh.gac(context, R.attr.cgmxdripColor)) } binding.buttonsLayout.cgmButton.visibility = (sp.getBoolean(R.string.key_show_cgm_button, false) && (xDripIsBgSource || dexcomIsSource)).toVisibility() @@ -573,7 +570,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList if (event.isEnabled && event.trigger.shouldRun()) context?.let { context -> SingleClickButton(context).also { - it.setTextColor(rh.gc(R.color.colorTreatmentButton)) + it.setTextColor(rh.gac(context, R.attr.treatmentButton)) it.setTextSize(TypedValue.COMPLEX_UNIT_SP, 10f) it.layoutParams = LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 0.5f).also { l -> l.setMargins(0, 0, rh.dpToPx(-4), 0) @@ -582,11 +579,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList it.text = event.title it.setOnClickListener { - OKDialog.showConfirmation( - context, - rh.gs(R.string.run_question, event.title), - { handler.post { automationPlugin.processEvent(event) } } - ) + OKDialog.showConfirmation(context, rh.gs(R.string.run_question, event.title), { handler.post { automationPlugin.processEvent(event) } }) } binding.buttonsLayout.userButtonsLayout.addView(it) } @@ -721,12 +714,12 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList val graph = GraphView(context) graph.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, rh.dpToPx(skinProvider.activeSkin().secondaryGraphHeight)).also { it.setMargins(0, rh.dpToPx(15), 0, rh.dpToPx(10)) } - graph.gridLabelRenderer?.gridColor = rh.gc(R.color.graphgrid) + graph.gridLabelRenderer?.gridColor = rh.gac(context, R.attr.graphgrid) graph.gridLabelRenderer?.reloadStyles() graph.gridLabelRenderer?.isHorizontalLabelsVisible = false graph.gridLabelRenderer?.labelVerticalWidth = axisWidth graph.gridLabelRenderer?.numVerticalLabels = 3 - graph.viewport.backgroundColor = Color.argb(20, 255, 255, 255) // 8% of gray + graph.viewport.backgroundColor = rh.gac(context, R.attr.viewPortbackgroundColor) relativeLayout.addView(graph) val label = TextView(context) @@ -745,11 +738,11 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList var task: Runnable? = null - private fun scheduleUpdateGUI(from: String) { + private fun scheduleUpdateGUI() { class UpdateRunnable : Runnable { override fun run() { - overviewPlugin.refreshLoop(from) + refreshAll() task = null } } @@ -759,20 +752,20 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList } @SuppressLint("SetTextI18n") - @Suppress("UNUSED_PARAMETER") - fun updateBg(from: String) { + fun updateBg() { + _binding ?: return val units = profileFunction.getUnits() binding.infoLayout.bg.text = overviewData.lastBg?.valueToUnitsString(units) ?: rh.gs(R.string.notavailable) - binding.infoLayout.bg.setTextColor(overviewData.lastBgColor) + binding.infoLayout.bg.setTextColor(overviewData.lastBgColor(context)) binding.infoLayout.arrow.setImageResource(trendCalculator.getTrendArrow(overviewData.lastBg).directionToIcon()) - binding.infoLayout.arrow.setColorFilter(overviewData.lastBgColor) + binding.infoLayout.arrow.setColorFilter(overviewData.lastBgColor(context)) binding.infoLayout.arrow.contentDescription = overviewData.lastBgDescription + " " + rh.gs(R.string.and) + " " + trendCalculator.getTrendDescription(overviewData.lastBg) val glucoseStatus = glucoseStatusProvider.glucoseStatusData if (glucoseStatus != null) { binding.infoLayout.deltaLarge.text = Profile.toSignedUnitsString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units) - binding.infoLayout.deltaLarge.setTextColor(overviewData.lastBgColor) + binding.infoLayout.deltaLarge.setTextColor(overviewData.lastBgColor(context)) binding.infoLayout.delta.text = Profile.toSignedUnitsString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units) binding.infoLayout.avgDelta.text = Profile.toSignedUnitsString(glucoseStatus.shortAvgDelta, glucoseStatus.shortAvgDelta * Constants.MGDL_TO_MMOLL, units) binding.infoLayout.longAvgDelta.text = Profile.toSignedUnitsString(glucoseStatus.longAvgDelta, glucoseStatus.longAvgDelta * Constants.MGDL_TO_MMOLL, units) @@ -809,70 +802,76 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList } } - @Suppress("UNUSED_PARAMETER") - fun updateProfile(from: String) { + fun updateProfile() { + _binding ?: return val profileBackgroundColor = profileFunction.getProfile()?.let { if (it is ProfileSealed.EPS) { if (it.value.originalPercentage != 100 || it.value.originalTimeshift != 0L || it.value.originalDuration != 0L) - rh.gc(R.color.ribbonWarning) - else rh.gc(R.color.ribbonDefault) + rh.gac(context, R.attr.ribbonWarningColor) + else rh.gac(context, R.attr.ribbonDefaultColor) } else if (it is ProfileSealed.PS) { - rh.gc(R.color.ribbonDefault) + rh.gac(context, R.attr.ribbonDefaultColor) } else { - rh.gc(R.color.ribbonDefault) + rh.gac(context, R.attr.ribbonDefaultColor) } - } ?: rh.gc(R.color.ribbonCritical) + } ?: rh.gac(context, R.attr.ribbonCriticalColor) val profileTextColor = profileFunction.getProfile()?.let { if (it is ProfileSealed.EPS) { if (it.value.originalPercentage != 100 || it.value.originalTimeshift != 0L || it.value.originalDuration != 0L) - rh.gc(R.color.ribbonTextWarning) - else rh.gc(R.color.ribbonTextDefault) + rh.gac(context, R.attr.ribbonTextWarningColor) + else rh.gac(context, R.attr.ribbonTextDefaultColor) } else if (it is ProfileSealed.PS) { - rh.gc(R.color.ribbonTextDefault) + rh.gac(context, R.attr.ribbonTextDefaultColor) } else { - rh.gc(R.color.ribbonTextDefault) + rh.gac(context, R.attr.ribbonTextDefaultColor) } - } ?: rh.gc(R.color.ribbonTextDefault) + } ?: rh.gac(context, R.attr.ribbonTextDefaultColor) binding.activeProfile.text = profileFunction.getProfileNameWithRemainingTime() binding.activeProfile.setBackgroundColor(profileBackgroundColor) binding.activeProfile.setTextColor(profileTextColor) } - @Suppress("UNUSED_PARAMETER") - fun updateTemporaryBasal(from: String) { - binding.infoLayout.baseBasal.text = overviewData.temporaryBasalText - binding.infoLayout.baseBasal.setTextColor(overviewData.temporaryBasalColor) - binding.infoLayout.baseBasalIcon.setImageResource(overviewData.temporaryBasalIcon) + private fun updateTemporaryBasal() { + _binding ?: return + binding.infoLayout.baseBasal.text = overviewData.temporaryBasalText(iobCobCalculator) + binding.infoLayout.baseBasal.setTextColor(overviewData.temporaryBasalColor(context, iobCobCalculator)) + binding.infoLayout.baseBasalIcon.setImageResource(overviewData.temporaryBasalIcon(iobCobCalculator)) binding.infoLayout.basalLayout.setOnClickListener { - activity?.let { OKDialog.show(it, rh.gs(R.string.basal), overviewData.temporaryBasalDialogText) } + activity?.let { OKDialog.show(it, rh.gs(R.string.basal), overviewData.temporaryBasalDialogText(iobCobCalculator)) } } } - @Suppress("UNUSED_PARAMETER") - fun updateExtendedBolus(from: String) { + private fun updateExtendedBolus() { + _binding ?: return val pump = activePlugin.activePump - binding.infoLayout.extendedBolus.text = overviewData.extendedBolusText + binding.infoLayout.extendedBolus.text = overviewData.extendedBolusText(iobCobCalculator) binding.infoLayout.extendedLayout.setOnClickListener { - activity?.let { OKDialog.show(it, rh.gs(R.string.extended_bolus), overviewData.extendedBolusDialogText) } + activity?.let { OKDialog.show(it, rh.gs(R.string.extended_bolus), overviewData.extendedBolusDialogText(iobCobCalculator)) } } binding.infoLayout.extendedLayout.visibility = (iobCobCalculator.getExtendedBolus(dateUtil.now()) != null && !pump.isFakingTempsByExtendedBoluses).toVisibility() } - @Suppress("UNUSED_PARAMETER") - fun updateTime(from: String) { + fun updateTime() { + _binding ?: return binding.infoLayout.time.text = dateUtil.timeString(dateUtil.now()) // Status lights - val isPatchPump = activePlugin.activePump.pumpDescription.isPatchPump + val pump = activePlugin.activePump + val isPatchPump = pump.pumpDescription.isPatchPump binding.statusLightsLayout.apply { cannulaOrPatch.setImageResource(if (isPatchPump) R.drawable.ic_patch_pump_outline else R.drawable.ic_cp_age_cannula) cannulaOrPatch.contentDescription = rh.gs(if (isPatchPump) R.string.statuslights_patch_pump_age else R.string.statuslights_cannula_age) cannulaOrPatch.scaleX = if (isPatchPump) 1.4f else 2f cannulaOrPatch.scaleY = cannulaOrPatch.scaleX insulinAge.visibility = isPatchPump.not().toVisibility() + batteryLayout.visibility = (!isPatchPump || pump.pumpDescription.useHardwareLink).toVisibility() + pbAge.visibility = (pump.pumpDescription.isBatteryReplaceable || pump.isBatteryChangeLoggingEnabled()).toVisibility() + val useBatteryLevel = (pump.model() == PumpType.OMNIPOD_EROS && pump is OmnipodErosPumpPlugin) + || (pump.model() != PumpType.ACCU_CHEK_COMBO && pump.model() != PumpType.OMNIPOD_DASH) + batteryLevel.visibility = useBatteryLevel.toVisibility() statusLights.visibility = (sp.getBoolean(R.string.key_show_statuslights, true) || config.NSCLIENT).toVisibility() } statusLightHandler.updateStatusLights( @@ -888,14 +887,14 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList processAps() } - @Suppress("UNUSED_PARAMETER") - fun updateIobCob(from: String) { - binding.infoLayout.iob.text = overviewData.iobText + fun updateIobCob() { + _binding ?: return + binding.infoLayout.iob.text = overviewData.iobText(iobCobCalculator) binding.infoLayout.iobLayout.setOnClickListener { - activity?.let { OKDialog.show(it, rh.gs(R.string.iob), overviewData.iobDialogText) } + activity?.let { OKDialog.show(it, rh.gs(R.string.iob), overviewData.iobDialogText(iobCobCalculator)) } } // cob - var cobText = overviewData.cobInfo?.displayText(rh, dateUtil, buildHelper.isEngineeringMode()) ?: rh.gs(R.string.value_unavailable_short) + var cobText = overviewData.cobInfo(iobCobCalculator).displayText(rh, dateUtil, buildHelper.isEngineeringMode()) ?: rh.gs(R.string.value_unavailable_short) val constraintsProcessed = loop.lastRun?.constraintsProcessed val lastRun = loop.lastRun @@ -916,14 +915,13 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList } @SuppressLint("SetTextI18n") - @Suppress("UNUSED_PARAMETER") - fun updateTemporaryTarget(from: String) { + fun updateTemporaryTarget() { + _binding ?: return val units = profileFunction.getUnits() - if (overviewData.temporaryTarget?.isInProgress(dateUtil) == false) overviewData.temporaryTarget = null val tempTarget = overviewData.temporaryTarget if (tempTarget != null) { - binding.tempTarget.setTextColor(rh.gc(R.color.ribbonTextWarning)) - binding.tempTarget.setBackgroundColor(rh.gc(R.color.ribbonWarning)) + binding.tempTarget.setTextColor(rh.gac(context, R.attr.ribbonTextWarningColor)) + binding.tempTarget.setBackgroundColor(rh.gac(context, R.attr.ribbonWarningColor)) binding.tempTarget.text = Profile.toTargetRangeString(tempTarget.lowTarget, tempTarget.highTarget, GlucoseUnit.MGDL, units) + " " + dateUtil.untilString(tempTarget.end, rh) } else { // If the target is not the same as set in the profile then oref has overridden it @@ -933,19 +931,19 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList if (targetUsed != 0.0 && abs(profile.getTargetMgdl() - targetUsed) > 0.01) { aapsLogger.debug("Adjusted target. Profile: ${profile.getTargetMgdl()} APS: $targetUsed") binding.tempTarget.text = Profile.toTargetRangeString(targetUsed, targetUsed, GlucoseUnit.MGDL, units) - binding.tempTarget.setTextColor(rh.gc(R.color.ribbonTextWarning)) - binding.tempTarget.setBackgroundColor(rh.gc(R.color.tempTargetBackground)) + binding.tempTarget.setTextColor(rh.gac(context, R.attr.ribbonTextWarningColor)) + binding.tempTarget.setBackgroundColor(rh.gac(context, R.attr.tempTargetBackgroundColor)) } else { - binding.tempTarget.setTextColor(rh.gc(R.color.ribbonTextDefault)) - binding.tempTarget.setBackgroundColor(rh.gc(R.color.ribbonDefault)) + binding.tempTarget.setTextColor(rh.gac(context, R.attr.ribbonTextDefaultColor)) + binding.tempTarget.setBackgroundColor(rh.gac(context, R.attr.ribbonDefaultColor)) binding.tempTarget.text = Profile.toTargetRangeString(profile.getTargetLowMgdl(), profile.getTargetHighMgdl(), GlucoseUnit.MGDL, units) } } } } - @Suppress("UNUSED_PARAMETER") - fun updateGraph(from: String) { + private fun updateGraph() { + _binding ?: return val pump = activePlugin.activePump val graphData = GraphData(injector, binding.graphsLayout.bgGraph, overviewData) val menuChartSettings = overviewMenus.setting @@ -953,6 +951,8 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList graphData.addBgReadings(menuChartSettings[0][OverviewMenus.CharType.PRE.ordinal]) if (buildHelper.isDev()) graphData.addBucketedData() graphData.addTreatments() + if (menuChartSettings[0][OverviewMenus.CharType.TREAT.ordinal]) + graphData.addTherapyEvents() if (menuChartSettings[0][OverviewMenus.CharType.ACT.ordinal]) graphData.addActivity(0.8) if ((pump.pumpDescription.isTempBasalCapable || config.NSCLIENT) && menuChartSettings[0][OverviewMenus.CharType.BAS.ordinal]) @@ -1023,13 +1023,14 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList } } - @Suppress("UNUSED_PARAMETER") - fun updateCalcProgress(from: String) { - binding.graphsLayout.iobCalculationProgress.text = overviewData.calcProgress + private fun updateCalcProgress() { + _binding ?: return + binding.progressBar.progress = overviewData.calcProgressPct + binding.progressBar.visibility = (overviewData.calcProgressPct != 100).toVisibility() } - @Suppress("UNUSED_PARAMETER") - fun updateSensitivity(from: String) { + private fun updateSensitivity() { + _binding ?: return if (sp.getBoolean(R.string.key_openapsama_useautosens, false) && constraintChecker.isAutosensModeEnabled().value()) { binding.infoLayout.sensitivityIcon.setImageResource(R.drawable.ic_swap_vert_black_48dp_green) } else { @@ -1037,20 +1038,20 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList } binding.infoLayout.sensitivity.text = - overviewData.lastAutosensData?.let { autosensData -> + overviewData.lastAutosensData(iobCobCalculator)?.let { autosensData -> String.format(Locale.ENGLISH, "%.0f%%", autosensData.autosensResult.ratio * 100) } ?: "" } - @Suppress("UNUSED_PARAMETER") - fun updatePumpStatus(from: String) { + private fun updatePumpStatus() { + _binding ?: return val status = overviewData.pumpStatus binding.pumpStatus.text = status binding.pumpStatusLayout.visibility = (status != "").toVisibility() } - @Suppress("UNUSED_PARAMETER") - fun updateNotification(from: String) { + private fun updateNotification() { + _binding ?: return binding.notifications.let { notificationStore.updateNotifications(it) } } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewMenus.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewMenus.kt index d1838947b2..78990dce99 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewMenus.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewMenus.kt @@ -1,10 +1,13 @@ package info.nightscout.androidaps.plugins.general.overview +import android.content.Context import android.text.SpannableString +import android.text.style.BackgroundColorSpan import android.text.style.ForegroundColorSpan import android.view.Menu import android.view.View import android.widget.ImageButton +import androidx.annotation.AttrRes import androidx.annotation.ColorRes import androidx.annotation.StringRes import androidx.appcompat.widget.PopupMenu @@ -31,18 +34,18 @@ class OverviewMenus @Inject constructor( private val loop: Loop, private val config: Config ) { - - enum class CharType(@StringRes val nameId: Int, @ColorRes val colorId: Int, val primary: Boolean, val secondary: Boolean, @StringRes val shortnameId: Int) { - PRE(R.string.overview_show_predictions, R.color.prediction, primary = true, secondary = false, shortnameId = R.string.prediction_shortname), - BAS(R.string.overview_show_basals, R.color.basal, primary = true, secondary = false, shortnameId = R.string.basal_shortname), - ABS(R.string.overview_show_absinsulin, R.color.iob, primary = false, secondary = true, shortnameId = R.string.abs_insulin_shortname), - IOB(R.string.overview_show_iob, R.color.iob, primary = false, secondary = true, shortnameId = R.string.iob), - COB(R.string.overview_show_cob, R.color.cob, primary = false, secondary = true, shortnameId = R.string.cob), - DEV(R.string.overview_show_deviations, R.color.bgi, primary = false, secondary = true, shortnameId = R.string.deviation_shortname), - BGI(R.string.overview_show_bgi, R.color.bgi, primary = false, secondary = true, shortnameId = R.string.bgi_shortname), - SEN(R.string.overview_show_sensitivity, R.color.ratio, primary = false, secondary = true, shortnameId = R.string.sensitivity_shortname), - ACT(R.string.overview_show_activity, R.color.activity, primary = true, secondary = false, shortnameId = R.string.activity_shortname), - DEVSLOPE(R.string.overview_show_deviationslope, R.color.devslopepos, primary = false, secondary = true, shortnameId = R.string.devslope_shortname) + enum class CharType(@StringRes val nameId: Int, @AttrRes val attrId: Int, @AttrRes val attrTextId: Int, val primary: Boolean, val secondary: Boolean, @StringRes val shortnameId: Int) { + PRE(R.string.overview_show_predictions, R.attr.predictionColor, R.attr.menuTextColor, primary = true, secondary = false, shortnameId = R.string.prediction_shortname), + TREAT(R.string.overview_show_treatments, R.attr.predictionColor, R.attr.menuTextColor, primary = true, secondary = false, shortnameId = R.string.treatments_shortname), + BAS(R.string.overview_show_basals, R.attr.basal, R.attr.menuTextColor, primary = true, secondary = false,shortnameId = R.string.basal_shortname), + ABS(R.string.overview_show_absinsulin, R.attr.iobColor, R.attr.menuTextColor, primary = false, secondary = true,shortnameId = R.string.abs_insulin_shortname), + IOB(R.string.overview_show_iob, R.attr.iobColor, R.attr.menuTextColor, primary = false, secondary = true,shortnameId = R.string.iob), + COB(R.string.overview_show_cob, R.attr.cobColor, R.attr.menuTextColor, primary = false, secondary = true,shortnameId = R.string.cob), + DEV(R.string.overview_show_deviations, R.attr.bgiColor, R.attr.menuTextColor, primary = false, secondary = true,shortnameId = R.string.deviation_shortname), + BGI(R.string.overview_show_bgi, R.attr.bgiColor, R.attr.menuTextColor, primary = false, secondary = true,shortnameId = R.string.bgi_shortname), + SEN(R.string.overview_show_sensitivity, R.attr.ratioColor, R.attr.menuTextColorInverse, primary = false, secondary = true,shortnameId = R.string.sensitivity_shortname), + ACT(R.string.overview_show_activity, R.attr.activityColor, R.attr.menuTextColor, primary = true, secondary = false,shortnameId = R.string.activity_shortname), + DEVSLOPE(R.string.overview_show_deviationslope, R.attr.devslopeposColor, R.attr.menuTextColor, primary = false, secondary = true,shortnameId = R.string.devslope_shortname) } companion object { @@ -86,7 +89,7 @@ class OverviewMenus @Inject constructor( } } - fun setupChartMenu(chartButton: ImageButton) { + fun setupChartMenu(context: Context, chartButton: ImageButton) { val settingsCopy = setting val numOfGraphs = settingsCopy.size // 1 main + x secondary @@ -119,8 +122,9 @@ class OverviewMenus @Inject constructor( if (insert) { val item = popup.menu.add(Menu.NONE, m.ordinal + 100 * (g + 1), Menu.NONE, rh.gs(m.nameId)) val title = item.title - val s = SpannableString(title) - s.setSpan(ForegroundColorSpan(rh.gc(m.colorId)), 0, s.length, 0) + val s = SpannableString(" " + title + " ") + s.setSpan(ForegroundColorSpan(rh.gac(context, m.attrTextId)), 0, s.length, 0) + s.setSpan(BackgroundColorSpan(rh.gac(context, m.attrId)), 0, s.length, 0) item.title = s item.isCheckable = true item.isChecked = settingsCopy[g][m.ordinal] @@ -152,7 +156,7 @@ class OverviewMenus @Inject constructor( } } storeGraphConfig() - setupChartMenu(chartButton) + setupChartMenu(context, chartButton) rxBus.send(EventRefreshOverview("OnMenuItemClickListener", now = true)) return@setOnMenuItemClickListener true } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewPlugin.kt index 8685abdbe3..86469e8434 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewPlugin.kt @@ -4,25 +4,26 @@ import androidx.preference.PreferenceFragmentCompat import androidx.preference.SwitchPreference import dagger.android.HasAndroidInjector import info.nightscout.androidaps.R -import info.nightscout.androidaps.database.AppRepository -import info.nightscout.androidaps.database.ValueWrapper -import info.nightscout.androidaps.events.* +import info.nightscout.androidaps.events.EventPumpStatusChanged import info.nightscout.androidaps.extensions.* -import info.nightscout.androidaps.interfaces.* -import info.nightscout.shared.logging.AAPSLogger -import info.nightscout.shared.logging.LTag -import info.nightscout.androidaps.plugins.aps.events.EventLoopInvoked +import info.nightscout.androidaps.interfaces.Config +import info.nightscout.androidaps.interfaces.Overview +import info.nightscout.androidaps.interfaces.PluginBase +import info.nightscout.androidaps.interfaces.PluginDescription +import info.nightscout.androidaps.interfaces.PluginType import info.nightscout.androidaps.plugins.bus.RxBus -import info.nightscout.androidaps.plugins.general.overview.events.* +import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification +import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification +import info.nightscout.androidaps.plugins.general.overview.events.EventUpdateOverviewCalcProgress +import info.nightscout.androidaps.plugins.general.overview.events.EventUpdateOverviewNotification import info.nightscout.androidaps.plugins.general.overview.graphExtensions.Scale import info.nightscout.androidaps.plugins.general.overview.graphExtensions.ScaledDataPoint import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationStore -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventBucketedDataCreated import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress -import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.rx.AapsSchedulers +import info.nightscout.shared.logging.AAPSLogger import info.nightscout.shared.sharedPreferences.SP import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign @@ -41,9 +42,6 @@ class OverviewPlugin @Inject constructor( private val aapsSchedulers: AapsSchedulers, rh: ResourceHelper, private val config: Config, - private val dateUtil: DateUtil, - private val iobCobCalculator: IobCobCalculator, - private val repository: AppRepository, private val overviewData: OverviewData, private val overviewMenus: OverviewMenus ) : PluginBase( @@ -89,62 +87,9 @@ class OverviewPlugin @Inject constructor( disposable += rxBus .toObservable(EventIobCalculationProgress::class.java) .observeOn(aapsSchedulers.io) - .subscribe({ overviewData.calcProgress = it.progress; overviewBus.send(EventUpdateOverviewCalcProgress("EventIobCalculationProgress")) }, fabricPrivacy::logException) - disposable += rxBus - .toObservable(EventTempBasalChange::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ overviewBus.send(EventUpdateOverviewTemporaryBasal("EventTempBasalChange")) }, fabricPrivacy::logException) - disposable += rxBus - .toObservable(EventExtendedBolusChange::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ overviewBus.send(EventUpdateOverviewExtendedBolus("EventExtendedBolusChange")) }, fabricPrivacy::logException) - disposable += rxBus - .toObservable(EventNewBG::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ loadBg("EventNewBG") }, fabricPrivacy::logException) - disposable += rxBus - .toObservable(EventTempTargetChange::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ loadTemporaryTarget("EventTempTargetChange") }, fabricPrivacy::logException) - disposable += rxBus - .toObservable(EventTreatmentChange::class.java) - .observeOn(aapsSchedulers.io) .subscribe({ - loadIobCobResults("EventTreatmentChange") - overviewData.prepareTreatmentsData("EventTreatmentChange") - overviewBus.send(EventUpdateOverviewGraph("EventTreatmentChange")) - }, fabricPrivacy::logException) - disposable += rxBus - .toObservable(EventTherapyEventChange::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ - overviewData.prepareTreatmentsData("EventTherapyEventChange") - overviewBus.send(EventUpdateOverviewGraph("EventTherapyEventChange")) - }, fabricPrivacy::logException) - disposable += rxBus - .toObservable(EventBucketedDataCreated::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ - overviewData.prepareBucketedData("EventBucketedDataCreated") - overviewData.prepareBgData("EventBucketedDataCreated") - overviewBus.send(EventUpdateOverviewGraph("EventBucketedDataCreated")) - }, fabricPrivacy::logException) - disposable += rxBus - .toObservable(EventLoopInvoked::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ overviewData.preparePredictions("EventLoopInvoked") }, fabricPrivacy::logException) - disposable += rxBus - .toObservable(EventEffectiveProfileSwitchChanged::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ - loadProfile("EventEffectiveProfileSwitchChanged") - overviewData.prepareBasalData("EventEffectiveProfileSwitchChanged") - }, fabricPrivacy::logException) - disposable += rxBus - .toObservable(EventAutosensCalculationFinished::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ - if (it.cause !is EventCustomCalculationFinished) refreshLoop("EventAutosensCalculationFinished") + overviewData.calcProgressPct = it.pass.finalPercent(it.progressPct) + overviewBus.send(EventUpdateOverviewCalcProgress("EventIobCalculationProgress")) }, fabricPrivacy::logException) disposable += rxBus .toObservable(EventPumpStatusChanged::class.java) @@ -152,20 +97,7 @@ class OverviewPlugin @Inject constructor( .subscribe({ overviewData.pumpStatus = it.getStatus(rh) }, fabricPrivacy::logException) - disposable += rxBus - .toObservable(EventPreferenceChange::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ event -> - if (event.isChanged(rh, R.string.key_units)) { - overviewData.reset() - overviewData.prepareBucketedData("EventBucketedDataCreated") - overviewData.prepareBgData("EventBucketedDataCreated") - overviewBus.send(EventUpdateOverviewGraph("EventBucketedDataCreated")) - loadAll("EventPreferenceChange") - } - }, fabricPrivacy::logException) - Thread { loadAll("onResume") }.start() } override fun onStop() { @@ -243,7 +175,7 @@ class OverviewPlugin @Inject constructor( .storeDouble(R.string.key_statuslights_bat_critical, sp, rh) .storeInt(R.string.key_boluswizard_percentage, sp, rh) } - +/* @Volatile var runningRefresh = false override fun refreshLoop(from: String) { @@ -284,38 +216,5 @@ class OverviewPlugin @Inject constructor( overviewBus.send(EventUpdateOverviewGraph(from)) aapsLogger.debug(LTag.UI, "loadAll finished") } - - private fun loadProfile(from: String) { - overviewBus.send(EventUpdateOverviewProfile(from)) - } - - private fun loadTemporaryTarget(from: String) { - val tempTarget = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet() - if (tempTarget is ValueWrapper.Existing) overviewData.temporaryTarget = tempTarget.value - else overviewData.temporaryTarget = null - overviewBus.send(EventUpdateOverviewTemporaryTarget(from)) - } - - private fun loadAsData(from: String) { - overviewData.lastAutosensData = iobCobCalculator.ads.getLastAutosensData("Overview", aapsLogger, dateUtil) - overviewBus.send(EventUpdateOverviewSensitivity(from)) - } - - private fun loadBg(from: String) { - val gvWrapped = repository.getLastGlucoseValueWrapped().blockingGet() - if (gvWrapped is ValueWrapper.Existing) overviewData.lastBg = gvWrapped.value - else overviewData.lastBg = null - overviewBus.send(EventUpdateOverviewBg(from)) - } - - private fun loadIobCobResults(from: String) { - overviewData.bolusIob = iobCobCalculator.calculateIobFromBolus().round() - overviewData.basalIob = iobCobCalculator.calculateIobFromTempBasalsIncludingConvertedExtended().round() - overviewData.cobInfo = iobCobCalculator.getCobInfo(true, "Overview COB") - val lastCarbs = repository.getLastCarbsRecordWrapped().blockingGet() - overviewData.lastCarbsTime = if (lastCarbs is ValueWrapper.Existing) lastCarbs.value.timestamp else 0L - - overviewBus.send(EventUpdateOverviewIobCob(from)) - } - +*/ } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/StatusLightHandler.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/StatusLightHandler.kt index ba73fab429..f3455580ee 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/StatusLightHandler.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/StatusLightHandler.kt @@ -1,7 +1,6 @@ package info.nightscout.androidaps.plugins.general.overview import android.graphics.Color -import android.view.View import android.widget.TextView import androidx.annotation.StringRes import info.nightscout.androidaps.R @@ -42,7 +41,7 @@ class StatusLightHandler @Inject constructor( handleAge(careportal_cannula_age, TherapyEvent.Type.CANNULA_CHANGE, R.string.key_statuslights_cage_warning, 48.0, R.string.key_statuslights_cage_critical, 72.0) handleAge(careportal_insulin_age, TherapyEvent.Type.INSULIN_CHANGE, R.string.key_statuslights_iage_warning, 72.0, R.string.key_statuslights_iage_critical, 144.0) handleAge(careportal_sensor_age, TherapyEvent.Type.SENSOR_CHANGE, R.string.key_statuslights_sage_warning, 216.0, R.string.key_statuslights_sage_critical, 240.0) - if (pump.pumpDescription.isBatteryReplaceable || (pump is OmnipodErosPumpPlugin && pump.isUseRileyLinkBatteryLevel && pump.isBatteryChangeLoggingEnabled)) { + if (pump.pumpDescription.isBatteryReplaceable || pump.isBatteryChangeLoggingEnabled()) { handleAge(careportal_pb_age, TherapyEvent.Type.PUMP_BATTERY_CHANGE, R.string.key_statuslights_bage_warning, 216.0, R.string.key_statuslights_bage_critical, 240.0) } if (!config.NSCLIENT) { @@ -58,16 +57,16 @@ class StatusLightHandler @Inject constructor( } if (!config.NSCLIENT) { - if (pump.model() == PumpType.OMNIPOD_DASH) { - // Omnipod Dash does not report its battery level - careportal_battery_level?.text = rh.gs(R.string.notavailable) - careportal_battery_level?.setTextColor(Color.WHITE) - } else if (pump.model() == PumpType.OMNIPOD_EROS && pump is OmnipodErosPumpPlugin) { // instance of check is needed because at startup, pump can still be VirtualPumpPlugin and that will cause a crash because of the class cast below - // The Omnipod Eros does not report its battery level. However, some RileyLink alternatives do. - // Depending on the user's configuration, we will either show the battery level reported by the RileyLink or "n/a" - handleOmnipodErosBatteryLevel(careportal_battery_level, R.string.key_statuslights_bat_critical, 26.0, R.string.key_statuslights_bat_warning, 51.0, pump.batteryLevel.toDouble(), "%", pump.isUseRileyLinkBatteryLevel) - } else if (pump.model() != PumpType.ACCU_CHEK_COMBO) { + // The Omnipod Eros does not report its battery level. However, some RileyLink alternatives do. + // Depending on the user's configuration, we will either show the battery level reported by the RileyLink or "n/a" + // Pump instance check is needed because at startup, the pump can still be VirtualPumpPlugin and that will cause a crash + val erosBatteryLinkAvailable = pump.model() == PumpType.OMNIPOD_EROS && pump is OmnipodErosPumpPlugin && pump.isUseRileyLinkBatteryLevel + + if (pump.model().supportBatteryLevel || erosBatteryLinkAvailable) { handleLevel(careportal_battery_level, R.string.key_statuslights_bat_critical, 26.0, R.string.key_statuslights_bat_warning, 51.0, pump.batteryLevel.toDouble(), "%") + } else { + careportal_battery_level?.text = rh.gs(R.string.notavailable) + careportal_battery_level?.setTextColor(rh.gac(careportal_battery_level.context, R.attr.defaultTextColor)) } } } @@ -104,13 +103,4 @@ class StatusLightHandler @Inject constructor( } } - @Suppress("SameParameterValue") - private fun handleOmnipodErosBatteryLevel(view: TextView?, criticalSetting: Int, criticalDefaultValue: Double, warnSetting: Int, warnDefaultValue: Double, level: Double, units: String, useRileyLinkBatteryLevel: Boolean) { - if (useRileyLinkBatteryLevel) { - handleLevel(view, criticalSetting, criticalDefaultValue, warnSetting, warnDefaultValue, level, units) - } else { - view?.text = rh.gs(R.string.notavailable) - view?.setTextColor(Color.WHITE) - } - } -} \ No newline at end of file +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/activities/QuickWizardListActivity.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/activities/QuickWizardListActivity.kt index c3e3fd1b9f..128f6c6613 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/activities/QuickWizardListActivity.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/activities/QuickWizardListActivity.kt @@ -2,32 +2,28 @@ package info.nightscout.androidaps.plugins.general.overview.activities import android.annotation.SuppressLint import android.os.Bundle -import android.util.Log -import android.view.LayoutInflater -import android.view.MenuItem -import android.view.MotionEvent -import android.view.View -import android.view.ViewGroup -import android.widget.Button -import android.widget.ImageView -import android.widget.TextView +import android.util.SparseArray +import android.view.* +import androidx.core.util.forEach import androidx.fragment.app.FragmentManager import androidx.recyclerview.widget.ItemTouchHelper -import androidx.recyclerview.widget.ItemTouchHelper.ACTION_STATE_DRAG -import androidx.recyclerview.widget.ItemTouchHelper.DOWN -import androidx.recyclerview.widget.ItemTouchHelper.END -import androidx.recyclerview.widget.ItemTouchHelper.START -import androidx.recyclerview.widget.ItemTouchHelper.UP import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import info.nightscout.androidaps.R import info.nightscout.androidaps.activities.DaggerAppCompatActivityWithResult import info.nightscout.androidaps.databinding.OverviewQuickwizardlistActivityBinding +import info.nightscout.androidaps.databinding.OverviewQuickwizardlistItemBinding +import info.nightscout.androidaps.extensions.toVisibility import info.nightscout.androidaps.plugins.bus.RxBus +import info.nightscout.androidaps.utils.dragHelpers.ItemTouchHelperAdapter +import info.nightscout.androidaps.utils.dragHelpers.OnStartDragListener +import info.nightscout.androidaps.utils.dragHelpers.SimpleItemTouchHelperCallback import info.nightscout.androidaps.plugins.general.overview.dialogs.EditQuickWizardDialog import info.nightscout.androidaps.plugins.general.overview.events.EventQuickWizardChange +import info.nightscout.androidaps.utils.ActionModeHelper import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.FabricPrivacy +import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.utils.rx.AapsSchedulers import info.nightscout.androidaps.utils.wizard.QuickWizard import info.nightscout.androidaps.utils.wizard.QuickWizardEntry @@ -36,7 +32,7 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign import javax.inject.Inject -class QuickWizardListActivity : DaggerAppCompatActivityWithResult() { +class QuickWizardListActivity : DaggerAppCompatActivityWithResult(), OnStartDragListener { @Inject lateinit var aapsSchedulers: AapsSchedulers @Inject lateinit var rxBus: RxBus @@ -46,134 +42,78 @@ class QuickWizardListActivity : DaggerAppCompatActivityWithResult() { @Inject lateinit var sp: SP private var disposable: CompositeDisposable = CompositeDisposable() - + private lateinit var actionHelper: ActionModeHelper + private val itemTouchHelper = ItemTouchHelper(SimpleItemTouchHelperCallback()) private lateinit var binding: OverviewQuickwizardlistActivityBinding - private val itemTouchHelper by lazy { - val simpleItemTouchCallback = object : ItemTouchHelper.SimpleCallback(UP or DOWN or START or END, 0) { - - override fun onMove( - recyclerView: RecyclerView, - viewHolder: RecyclerView.ViewHolder, - target: RecyclerView.ViewHolder - ): Boolean { - val adapter = recyclerView.adapter as RecyclerViewAdapter - val from = viewHolder.layoutPosition - val to = target.layoutPosition - adapter.moveItem(from, to) - adapter.notifyItemMoved(from, to) - - return true - } - - override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { - } - - override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) { - super.onSelectedChanged(viewHolder, actionState) - - if (actionState == ACTION_STATE_DRAG) { - viewHolder?.itemView?.alpha = 0.5f - } - } - - override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { - super.clearView(recyclerView, viewHolder) - - viewHolder.itemView.alpha = 1.0f - - val adapter = recyclerView.adapter as RecyclerViewAdapter - adapter.onDrop() - } - } - - ItemTouchHelper(simpleItemTouchCallback) - } - - fun startDragging(viewHolder: RecyclerView.ViewHolder) { + override fun onStartDrag(viewHolder: RecyclerView.ViewHolder) { itemTouchHelper.startDrag(viewHolder) } - private inner class RecyclerViewAdapter(var fragmentManager: FragmentManager) : RecyclerView.Adapter() { + private inner class RecyclerViewAdapter(var fragmentManager: FragmentManager) : RecyclerView.Adapter(), ItemTouchHelperAdapter { + + private inner class QuickWizardEntryViewHolder(val binding: OverviewQuickwizardlistItemBinding, val fragmentManager: FragmentManager) : RecyclerView.ViewHolder(binding.root) - @SuppressLint("ClickableViewAccessibility") override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QuickWizardEntryViewHolder { - val itemView = LayoutInflater.from(parent.context).inflate(R.layout.overview_quickwizardlist_item, parent, false) - val viewHolder = QuickWizardEntryViewHolder(itemView, fragmentManager) - - viewHolder.handleView.setOnTouchListener { _, event -> - if (event.actionMasked == MotionEvent.ACTION_DOWN) { - startDragging(viewHolder) - } - return@setOnTouchListener true - } - - return viewHolder + val binding = OverviewQuickwizardlistItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return QuickWizardEntryViewHolder(binding, fragmentManager) } + @SuppressLint("ClickableViewAccessibility") override fun onBindViewHolder(holder: QuickWizardEntryViewHolder, position: Int) { - holder.from.text = dateUtil.timeString(quickWizard[position].validFromDate()) - holder.to.text = dateUtil.timeString(quickWizard[position].validToDate()) - val wearControl = sp.getBoolean(R.string.key_wear_control, false) - - if (wearControl) { - holder.handleView.visibility = View.VISIBLE + val entry = quickWizard[position] + holder.binding.from.text = dateUtil.timeString(entry.validFromDate()) + holder.binding.to.text = dateUtil.timeString(entry.validToDate()) + holder.binding.buttonText.text = entry.buttonText() + holder.binding.carbs.text = rh.gs(R.string.format_carbs, entry.carbs()) + if (entry.device() == QuickWizardEntry.DEVICE_ALL) { + holder.binding.device.visibility = View.GONE } else { - holder.handleView.visibility = View.GONE - } - if (quickWizard[position].device() == QuickWizardEntry.DEVICE_ALL) { - holder.device.visibility = View.GONE - } else { - holder.device.visibility = View.VISIBLE - holder.device.setImageResource( + holder.binding.device.visibility = View.VISIBLE + holder.binding.device.setImageResource( when (quickWizard[position].device()) { QuickWizardEntry.DEVICE_WATCH -> R.drawable.ic_watch else -> R.drawable.ic_smartphone } ) } - holder.buttonText.text = quickWizard[position].buttonText() - holder.carbs.text = rh.gs(R.string.format_carbs, quickWizard[position].carbs()) - } - - override fun getItemCount(): Int = quickWizard.size() - - private inner class QuickWizardEntryViewHolder(itemView: View, var fragmentManager: FragmentManager) : RecyclerView.ViewHolder(itemView) { - - val buttonText: TextView = itemView.findViewById(R.id.overview_quickwizard_item_buttonText) - val carbs: TextView = itemView.findViewById(R.id.overview_quickwizard_item_carbs) - val from: TextView = itemView.findViewById(R.id.overview_quickwizard_item_from) - val handleView: ImageView = itemView.findViewById(R.id.handleView) - val device: ImageView = itemView.findViewById(R.id.overview_quickwizard_item_device) - val to: TextView = itemView.findViewById(R.id.overview_quickwizard_item_to) - private val editButton: Button = itemView.findViewById(R.id.overview_quickwizard_item_edit_button) - private val removeButton: Button = itemView.findViewById(R.id.overview_quickwizard_item_remove_button) - - init { - editButton.setOnClickListener { + holder.binding.root.setOnClickListener { + if (actionHelper.isNoAction) { val manager = fragmentManager val editQuickWizardDialog = EditQuickWizardDialog() val bundle = Bundle() - bundle.putInt("position", bindingAdapterPosition) + bundle.putInt("position", position) editQuickWizardDialog.arguments = bundle editQuickWizardDialog.show(manager, "EditQuickWizardDialog") - } - removeButton.setOnClickListener { - quickWizard.remove(bindingAdapterPosition) - rxBus.send(EventQuickWizardChange()) + } else if (actionHelper.isRemoving) { + holder.binding.cbRemove.toggle() + actionHelper.updateSelection(position, entry, holder.binding.cbRemove.isChecked) } } + holder.binding.sortHandle.setOnTouchListener { _, event -> + if (event.actionMasked == MotionEvent.ACTION_DOWN) { + onStartDrag(holder) + return@setOnTouchListener true + } + return@setOnTouchListener false + } + holder.binding.cbRemove.isChecked = actionHelper.isSelected(position) + holder.binding.cbRemove.setOnCheckedChangeListener { _, value -> + actionHelper.updateSelection(position, entry, value) + } + holder.binding.sortHandle.visibility = actionHelper.isSorting.toVisibility() + holder.binding.cbRemove.visibility = actionHelper.isRemoving.toVisibility() } - fun moveItem(from: Int, to: Int) { - Log.i("QuickWizard", "moveItem") - quickWizard.move(from, to) + override fun getItemCount() = quickWizard.size() + + override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean { + binding.recyclerview.adapter?.notifyItemMoved(fromPosition, toPosition) + quickWizard.move(fromPosition, toPosition) + return true } - fun onDrop() { - Log.i("QuickWizard", "onDrop") - rxBus.send(EventQuickWizardChange()) - } + override fun onDrop() = rxBus.send(EventQuickWizardChange()) } override fun onCreate(savedInstanceState: Bundle?) { @@ -181,6 +121,11 @@ class QuickWizardListActivity : DaggerAppCompatActivityWithResult() { binding = OverviewQuickwizardlistActivityBinding.inflate(layoutInflater) setContentView(binding.root) + actionHelper = ActionModeHelper(rh, this) + actionHelper.setUpdateListHandler { binding.recyclerview.adapter?.notifyDataSetChanged() } + actionHelper.setOnRemoveHandler { removeSelected(it) } + actionHelper.enableSort = true + title = rh.gs(R.string.quickwizard) supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayShowHomeEnabled(true) @@ -191,6 +136,7 @@ class QuickWizardListActivity : DaggerAppCompatActivityWithResult() { itemTouchHelper.attachToRecyclerView(binding.recyclerview) binding.addButton.setOnClickListener { + actionHelper.finish() val manager = supportFragmentManager val editQuickWizardDialog = EditQuickWizardDialog() editQuickWizardDialog.show(manager, "EditQuickWizardDialog") @@ -210,9 +156,25 @@ class QuickWizardListActivity : DaggerAppCompatActivityWithResult() { override fun onPause() { disposable.clear() + actionHelper.finish() super.onPause() } + private fun removeSelected(selectedItems: SparseArray) { + OKDialog.showConfirmation(this, rh.gs(R.string.removerecord), getConfirmationText(selectedItems), Runnable { + selectedItems.forEach { _, item -> + quickWizard.remove(item.position) + rxBus.send(EventQuickWizardChange()) + } + actionHelper.finish() + }) + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.menu_actions, menu) + return super.onCreateOptionsMenu(menu) + } + override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) { android.R.id.home -> { @@ -220,6 +182,17 @@ class QuickWizardListActivity : DaggerAppCompatActivityWithResult() { true } - else -> false + else -> actionHelper.onOptionsItemSelected(item) + } + + private fun getConfirmationText(selectedItems: SparseArray): String { + if (selectedItems.size() == 1) { + val entry = selectedItems.valueAt(0) + return "${rh.gs(R.string.remove_button)} ${entry.buttonText()} ${rh.gs(R.string.format_carbs, entry.carbs())}\n" + + "${dateUtil.timeString(entry.validFromDate())} - ${dateUtil.timeString(entry.validToDate())}" + } + return rh.gs(R.string.confirm_remove_multiple_items, selectedItems.size()) + } + } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/EditQuickWizardDialog.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/EditQuickWizardDialog.kt index af5593e3ca..41f210e7eb 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/EditQuickWizardDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/EditQuickWizardDialog.kt @@ -105,7 +105,7 @@ class EditQuickWizardDialog : DaggerDialogFragment(), View.OnClickListener { binding.from.setOnClickListener { context?.let { TimePickerDialog( - it, R.style.MaterialPickerTheme, + it, fromTimeSetListener, T.secs(fromSeconds.toLong()).hours().toInt(), T.secs((fromSeconds % 3600).toLong()).mins().toInt(), @@ -124,7 +124,7 @@ class EditQuickWizardDialog : DaggerDialogFragment(), View.OnClickListener { binding.to.setOnClickListener { context?.let { TimePickerDialog( - it, R.style.MaterialPickerTheme, + it, toTimeSetListener, T.secs(toSeconds.toLong()).hours().toInt(), T.secs((toSeconds % 3600).toLong()).mins().toInt(), diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewBg.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewBg.kt deleted file mode 100644 index 2bbfb7813c..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewBg.kt +++ /dev/null @@ -1,5 +0,0 @@ -package info.nightscout.androidaps.plugins.general.overview.events - -import info.nightscout.androidaps.events.Event - -class EventUpdateOverviewBg(val from: String) : Event() \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewExtendedBolus.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewExtendedBolus.kt deleted file mode 100644 index 51b493acf4..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewExtendedBolus.kt +++ /dev/null @@ -1,5 +0,0 @@ -package info.nightscout.androidaps.plugins.general.overview.events - -import info.nightscout.androidaps.events.Event - -class EventUpdateOverviewExtendedBolus(val from: String) : Event() \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewProfile.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewProfile.kt deleted file mode 100644 index 6e4bb95490..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewProfile.kt +++ /dev/null @@ -1,5 +0,0 @@ -package info.nightscout.androidaps.plugins.general.overview.events - -import info.nightscout.androidaps.events.Event - -class EventUpdateOverviewProfile(val from: String) : Event() \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewTemporaryBasal.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewTemporaryBasal.kt deleted file mode 100644 index 315bf8d651..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewTemporaryBasal.kt +++ /dev/null @@ -1,5 +0,0 @@ -package info.nightscout.androidaps.plugins.general.overview.events - -import info.nightscout.androidaps.events.Event - -class EventUpdateOverviewTemporaryBasal(val from: String) : Event() \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewTemporaryTarget.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewTemporaryTarget.kt deleted file mode 100644 index 83cafcd664..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewTemporaryTarget.kt +++ /dev/null @@ -1,5 +0,0 @@ -package info.nightscout.androidaps.plugins.general.overview.events - -import info.nightscout.androidaps.events.Event - -class EventUpdateOverviewTemporaryTarget(val from: String) : Event() \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewTime.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewTime.kt deleted file mode 100644 index 5da5b72136..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverviewTime.kt +++ /dev/null @@ -1,5 +0,0 @@ -package info.nightscout.androidaps.plugins.general.overview.events - -import info.nightscout.androidaps.events.Event - -class EventUpdateOverviewTime(val from: String) : Event() \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphData/GraphData.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphData/GraphData.kt index 4f6096fbcf..b63425970b 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphData/GraphData.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphData/GraphData.kt @@ -66,7 +66,7 @@ class GraphData( addSeries(AreaGraphSeries(inRangeAreaDataPoints).also { it.color = 0 it.isDrawBackground = true - it.backgroundColor = rh.gc(R.color.inrangebackground) + it.backgroundColor = rh.gac(graph.context,R.attr.inrangeBackground) }) } @@ -88,6 +88,11 @@ class GraphData( addSeries(overviewData.treatmentsSeries) } + fun addTherapyEvents() { + maxY = maxOf(maxY, overviewData.maxTherapyEventValue) + addSeries(overviewData.therapyEventSeries) + } + fun addActivity(scale: Double) { addSeries(overviewData.activitySeries) addSeries(overviewData.activityPredictionSeries) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/BolusDataPoint.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/BolusDataPoint.kt index a5f9bbed50..b9bbb79809 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/BolusDataPoint.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/BolusDataPoint.kt @@ -1,5 +1,6 @@ package info.nightscout.androidaps.plugins.general.overview.graphExtensions +import android.content.Context import android.graphics.Color import info.nightscout.androidaps.core.R import info.nightscout.androidaps.database.entities.Bolus @@ -28,13 +29,11 @@ class BolusDataPoint @Inject constructor( override val shape get() = if (data.type == Bolus.Type.SMB) PointsWithLabelGraphSeries.Shape.SMB else PointsWithLabelGraphSeries.Shape.BOLUS - override val color - get() = - when { - data.type == Bolus.Type.SMB -> rh.gc(R.color.tempbasal) - data.isValid -> Color.CYAN - else -> rh.gc(android.R.color.holo_red_light) - } + override fun color(context: Context?): Int = + if (data.type == Bolus.Type.SMB) rh.gac(context, R.attr.smbColor) + else if (data.isValid) rh.gac(context, R.attr.bolusDataPointColor) + else rh.gac(context, R.attr.alarmColor) + override fun setY(y: Double) { yValue = y diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/CarbsDataPoint.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/CarbsDataPoint.kt index e8dd54b2cf..e5b9ca9973 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/CarbsDataPoint.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/CarbsDataPoint.kt @@ -1,5 +1,6 @@ package info.nightscout.androidaps.plugins.general.overview.graphExtensions +import android.content.Context import info.nightscout.androidaps.core.R import info.nightscout.androidaps.database.entities.Carbs import info.nightscout.androidaps.utils.resources.ResourceHelper @@ -18,7 +19,10 @@ class CarbsDataPoint @Inject constructor( override val duration = 0L override val size = 2f override val shape = PointsWithLabelGraphSeries.Shape.CARBS - override val color get() = if (data.isValid) rh.gc(R.color.carbs) else rh.gc(android.R.color.holo_red_light) + + override fun color(context: Context?): Int { + return if (data.isValid) rh.gac(context, R.attr.cobColor) else rh.gac(context, R.attr.alarmColor) + } override fun setY(y: Double) { yValue = y diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/EffectiveProfileSwitchDataPoint.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/EffectiveProfileSwitchDataPoint.kt index 11ebc2aa11..58287aaaad 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/EffectiveProfileSwitchDataPoint.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/EffectiveProfileSwitchDataPoint.kt @@ -1,11 +1,15 @@ package info.nightscout.androidaps.plugins.general.overview.graphExtensions +import android.content.Context import android.graphics.Color +import info.nightscout.androidaps.core.R import info.nightscout.androidaps.database.entities.EffectiveProfileSwitch +import info.nightscout.androidaps.utils.resources.ResourceHelper import javax.inject.Inject class EffectiveProfileSwitchDataPoint @Inject constructor( - val data: EffectiveProfileSwitch + val data: EffectiveProfileSwitch, + private val rh: ResourceHelper ) : DataPointWithLabelInterface { private var yValue = 0.0 @@ -21,5 +25,7 @@ class EffectiveProfileSwitchDataPoint @Inject constructor( override val duration = 0L override val shape = PointsWithLabelGraphSeries.Shape.PROFILE override val size = 10f - override val color = Color.CYAN + override fun color(context: Context?): Int { + return rh.gac(context, R.attr.profileSwitchColor) + } } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/ExtendedBolusDataPoint.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/ExtendedBolusDataPoint.kt index 4352f3fb7b..1d30ca23d9 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/ExtendedBolusDataPoint.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/ExtendedBolusDataPoint.kt @@ -1,12 +1,16 @@ package info.nightscout.androidaps.plugins.general.overview.graphExtensions +import android.content.Context import android.graphics.Color +import info.nightscout.androidaps.core.R import info.nightscout.androidaps.database.entities.ExtendedBolus import info.nightscout.androidaps.extensions.toStringTotal +import info.nightscout.androidaps.utils.resources.ResourceHelper import javax.inject.Inject class ExtendedBolusDataPoint @Inject constructor( - val data: ExtendedBolus + val data: ExtendedBolus, + private val rh: ResourceHelper ) : DataPointWithLabelInterface { private var yValue = 0.0 @@ -17,7 +21,9 @@ class ExtendedBolusDataPoint @Inject constructor( override val duration get() = data.duration override val size = 10f override val shape = PointsWithLabelGraphSeries.Shape.EXTENDEDBOLUS - override val color = Color.CYAN + override fun color(context: Context?): Int { + return rh.gac(context, R.attr.extBolusColor) + } override fun setY(y: Double) { yValue = y diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/FixedLineGraphSeries.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/FixedLineGraphSeries.java index 4cfb27edcd..b938d02b45 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/FixedLineGraphSeries.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/FixedLineGraphSeries.java @@ -261,7 +261,7 @@ public class FixedLineGraphSeries extends BaseSeri //fix: last value not drawn as datapoint. Draw first point here, and then on every step the end values (above) // float first_X = (float) x + (graphLeft + 1); // float first_Y = (float) (graphTop - y) + graphHeight; - //TODO canvas.drawCircle(first_X, first_Y, dataPointsRadius, mPaint); + // canvas.drawCircle(first_X, first_Y, dataPointsRadius, mPaint); } lastEndY = orgY; lastEndX = orgX; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/GlucoseValueDataPoint.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/GlucoseValueDataPoint.kt index bd5ba6feef..bb31301a84 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/GlucoseValueDataPoint.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/GlucoseValueDataPoint.kt @@ -1,5 +1,6 @@ package info.nightscout.androidaps.plugins.general.overview.graphExtensions +import android.content.Context import info.nightscout.androidaps.Constants import info.nightscout.androidaps.core.R import info.nightscout.androidaps.database.entities.GlucoseValue @@ -27,30 +28,28 @@ class GlucoseValueDataPoint @Inject constructor( override val duration = 0L override val shape get() = if (isPrediction) PointsWithLabelGraphSeries.Shape.PREDICTION else PointsWithLabelGraphSeries.Shape.BG override val size = 1f - override val color: Int - get() { - val units = profileFunction.getUnits() - val lowLine = defaultValueHelper.determineLowLine() - val highLine = defaultValueHelper.determineHighLine() - return when { - isPrediction -> predictionColor - valueToUnits(units) < lowLine -> rh.gc(R.color.low) - valueToUnits(units) > highLine -> rh.gc(R.color.high) - else -> rh.gc(R.color.inrange) - } + override fun color(context: Context?): Int { + val units = profileFunction.getUnits() + val lowLine = defaultValueHelper.determineLowLine() + val highLine = defaultValueHelper.determineHighLine() + return when { + isPrediction -> predictionColor(context) + valueToUnits(units) < lowLine -> rh.gac(context, R.attr.bgLow) + valueToUnits(units) > highLine -> rh.gac(context, R.attr.highColor) + else -> rh.gac(context, R.attr.bgInRange) } + } - val predictionColor: Int - get() { + private fun predictionColor (context: Context?): Int { return when (data.sourceSensor) { - GlucoseValue.SourceSensor.IOB_PREDICTION -> rh.gc(R.color.iob) - GlucoseValue.SourceSensor.COB_PREDICTION -> rh.gc(R.color.cob) - GlucoseValue.SourceSensor.A_COB_PREDICTION -> -0x7f000001 and rh.gc(R.color.cob) - GlucoseValue.SourceSensor.UAM_PREDICTION -> rh.gc(R.color.uam) - GlucoseValue.SourceSensor.ZT_PREDICTION -> rh.gc(R.color.zt) - else -> R.color.white + GlucoseValue.SourceSensor.IOB_PREDICTION -> rh.gac(context, R.attr.iobColor) + GlucoseValue.SourceSensor.COB_PREDICTION -> rh.gac(context, R.attr.cobColor) + GlucoseValue.SourceSensor.A_COB_PREDICTION -> -0x7f000001 and rh.gac(context, R.attr.cobColor) + GlucoseValue.SourceSensor.UAM_PREDICTION -> rh.gac(context, R.attr.uamColor) + GlucoseValue.SourceSensor.ZT_PREDICTION -> rh.gac(context, R.attr.ztColor) + else -> rh.gac( context,R.attr.defaultTextColor) } - } + } private val isPrediction: Boolean get() = data.sourceSensor == GlucoseValue.SourceSensor.IOB_PREDICTION || diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/InMemoryGlucoseValueDataPoint.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/InMemoryGlucoseValueDataPoint.kt index 4882f73450..7849c38318 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/InMemoryGlucoseValueDataPoint.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/InMemoryGlucoseValueDataPoint.kt @@ -1,5 +1,6 @@ package info.nightscout.androidaps.plugins.general.overview.graphExtensions +import android.content.Context import info.nightscout.androidaps.Constants import info.nightscout.androidaps.core.R import info.nightscout.androidaps.data.InMemoryGlucoseValue @@ -24,5 +25,7 @@ class InMemoryGlucoseValueDataPoint @Inject constructor( override val duration = 0L override val shape = PointsWithLabelGraphSeries.Shape.BUCKETED_BG override val size = 0.3f - override val color get() = rh.gc(R.color.white) + override fun color(context: Context?): Int { + return rh.gac(context, R.attr.inMemoryColor) + } } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/TherapyEventDataPoint.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/TherapyEventDataPoint.kt index d6ae2afb0d..7577062961 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/TherapyEventDataPoint.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/TherapyEventDataPoint.kt @@ -1,5 +1,6 @@ package info.nightscout.androidaps.plugins.general.overview.graphExtensions +import android.content.Context import android.graphics.Color import info.nightscout.androidaps.Constants import info.nightscout.androidaps.core.R @@ -59,14 +60,14 @@ class TherapyEventDataPoint @Inject constructor( } override val size get() = if (rh.gb(R.bool.isTablet)) 12.0f else 10.0f - override val color - get() = - when (data.type) { - TherapyEvent.Type.ANNOUNCEMENT -> rh.gc(R.color.notificationAnnouncement) - TherapyEvent.Type.NS_MBG -> Color.RED - TherapyEvent.Type.FINGER_STICK_BG_VALUE -> Color.RED - TherapyEvent.Type.EXERCISE -> Color.BLUE - TherapyEvent.Type.APS_OFFLINE -> Color.GRAY and -0x7f000001 - else -> Color.GRAY - } + override fun color(context: Context?): Int { + return when (data.type) { + TherapyEvent.Type.ANNOUNCEMENT -> rh.gac(context, R.attr.notificationAnnouncement) + TherapyEvent.Type.NS_MBG -> rh.gac(context, R.attr.therapyEvent_NS_MBG) + TherapyEvent.Type.FINGER_STICK_BG_VALUE -> rh.gac(context, R.attr.therapyEvent_FINGER_STICK_BG_VALUE) + TherapyEvent.Type.EXERCISE -> rh.gac(context, R.attr.therapyEvent_EXERCISE) + TherapyEvent.Type.APS_OFFLINE -> rh.gac(context, R.attr.therapyEvent_APS_OFFLINE) and -0x7f000001 + else -> rh.gac(context, R.attr.therapyEvent_Default) + } + } } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationStore.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationStore.kt index 5e66ac8260..087ec2f2ee 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationStore.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationStore.kt @@ -164,11 +164,11 @@ class NotificationStore @Inject constructor( @Suppress("SetTextI18n") holder.binding.text.text = dateUtil.timeString(notification.date) + " " + notification.text when (notification.level) { - Notification.URGENT -> holder.binding.cv.setBackgroundColor(rh.gc(R.color.notificationUrgent)) - Notification.NORMAL -> holder.binding.cv.setBackgroundColor(rh.gc(R.color.notificationNormal)) - Notification.LOW -> holder.binding.cv.setBackgroundColor(rh.gc(R.color.notificationLow)) - Notification.INFO -> holder.binding.cv.setBackgroundColor(rh.gc(R.color.notificationInfo)) - Notification.ANNOUNCEMENT -> holder.binding.cv.setBackgroundColor(rh.gc(R.color.notificationAnnouncement)) + Notification.URGENT -> holder.binding.cv.setBackgroundColor(rh.gac(R.attr.notificationUrgent)) + Notification.NORMAL -> holder.binding.cv.setBackgroundColor(rh.gac(R.attr.notificationNormal)) + Notification.LOW -> holder.binding.cv.setBackgroundColor(rh.gac(R.attr.notificationLow)) + Notification.INFO -> holder.binding.cv.setBackgroundColor(rh.gac(R.attr.notificationInfo)) + Notification.ANNOUNCEMENT -> holder.binding.cv.setBackgroundColor(rh.gac(R.attr.notificationAnnouncement)) } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/persistentNotification/DummyService.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/persistentNotification/DummyService.kt index e6651412ae..402daed73e 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/persistentNotification/DummyService.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/persistentNotification/DummyService.kt @@ -39,8 +39,6 @@ class DummyService : DaggerService() { override fun onCreate() { super.onCreate() - // TODO: I guess this was moved here in order to adhere to the 5 seconds rule to call "startForeground" after a Service was called as Foreground service? - // As onCreate() is not called every time a service is started, copied to onStartCommand(). try { aapsLogger.debug("Starting DummyService with ID ${notificationHolder.notificationID} notification ${notificationHolder.notification}") startForeground(notificationHolder.notificationID, notificationHolder.notification) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/persistentNotification/PersistentNotificationPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/persistentNotification/PersistentNotificationPlugin.kt index 3bd76dab56..17afcbebd0 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/persistentNotification/PersistentNotificationPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/persistentNotification/PersistentNotificationPlugin.kt @@ -9,17 +9,20 @@ import androidx.core.app.RemoteInput import dagger.android.HasAndroidInjector import info.nightscout.androidaps.Constants import info.nightscout.androidaps.R -import info.nightscout.androidaps.events.* +import info.nightscout.androidaps.events.EventAutosensCalculationFinished +import info.nightscout.androidaps.events.EventInitializationChanged +import info.nightscout.androidaps.events.EventPreferenceChange +import info.nightscout.androidaps.events.EventRefreshOverview import info.nightscout.androidaps.extensions.toStringShort import info.nightscout.androidaps.extensions.valueToUnitsString import info.nightscout.androidaps.interfaces.* -import info.nightscout.shared.logging.AAPSLogger import info.nightscout.androidaps.plugins.bus.RxBus import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProvider import info.nightscout.androidaps.utils.DecimalFormatter import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.rx.AapsSchedulers +import info.nightscout.shared.logging.AAPSLogger import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign import javax.inject.Inject @@ -72,26 +75,10 @@ class PersistentNotificationPlugin @Inject constructor( .toObservable(EventRefreshOverview::class.java) .observeOn(aapsSchedulers.io) .subscribe({ triggerNotificationUpdate() }, fabricPrivacy::logException) - disposable += rxBus - .toObservable(EventExtendedBolusChange::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ triggerNotificationUpdate() }, fabricPrivacy::logException) - disposable += rxBus - .toObservable(EventTempBasalChange::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ triggerNotificationUpdate() }, fabricPrivacy::logException) - disposable += rxBus - .toObservable(EventTreatmentChange::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ triggerNotificationUpdate() }, fabricPrivacy::logException) disposable += rxBus .toObservable(EventInitializationChanged::class.java) .observeOn(aapsSchedulers.io) .subscribe({ triggerNotificationUpdate() }, fabricPrivacy::logException) - disposable += rxBus - .toObservable(EventEffectiveProfileSwitchChanged::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ triggerNotificationUpdate() }, fabricPrivacy::logException) disposable += rxBus .toObservable(EventAutosensCalculationFinished::class.java) .observeOn(aapsSchedulers.io) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorPlugin.kt index d2c12037ce..6952ea186e 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorPlugin.kt @@ -328,7 +328,7 @@ class SmsCommunicatorPlugin @Inject constructor( } else if (lastBG != null) { val agoMilliseconds = dateUtil.now() - lastBG.timestamp val agoMin = (agoMilliseconds / 60.0 / 1000.0).toInt() - reply = rh.gs(R.string.sms_lastbg) + " " + lastBG.valueToUnitsString(units) + " " + String.format(rh.gs(R.string.sms_minago), agoMin) + ", " + reply = rh.gs(R.string.sms_lastbg) + " " + lastBG.valueToUnitsString(units) + " " + rh.gs(R.string.sms_minago, agoMin) + ", " } val glucoseStatus = glucoseStatusProvider.glucoseStatusData if (glucoseStatus != null) reply += rh.gs(R.string.sms_delta) + " " + Profile.toUnitsString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units) + " " + units + ", " @@ -348,7 +348,7 @@ class SmsCommunicatorPlugin @Inject constructor( "DISABLE", "STOP" -> { if (loop.enabled) { val passCode = generatePassCode() - val reply = String.format(rh.gs(R.string.smscommunicator_loopdisablereplywithcode), passCode) + val reply = rh.gs(R.string.smscommunicator_loopdisablereplywithcode, passCode) receivedSms.processed = true messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = false) { override fun run() { @@ -372,7 +372,7 @@ class SmsCommunicatorPlugin @Inject constructor( "ENABLE", "START" -> { if (!loop.enabled) { val passCode = generatePassCode() - val reply = String.format(rh.gs(R.string.smscommunicator_loopenablereplywithcode), passCode) + val reply = rh.gs(R.string.smscommunicator_loopenablereplywithcode, passCode) receivedSms.processed = true messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = false) { override fun run() { @@ -389,7 +389,7 @@ class SmsCommunicatorPlugin @Inject constructor( "STATUS" -> { val reply = if (loop.enabled) { - if (loop.isSuspended) String.format(rh.gs(R.string.loopsuspendedfor), loop.minutesToEndOfSuspend()) + if (loop.isSuspended) rh.gs(R.string.loopsuspendedfor, loop.minutesToEndOfSuspend()) else rh.gs(R.string.smscommunicator_loopisenabled) } else rh.gs(R.string.loopisdisabled) @@ -399,7 +399,7 @@ class SmsCommunicatorPlugin @Inject constructor( "RESUME" -> { val passCode = generatePassCode() - val reply = String.format(rh.gs(R.string.smscommunicator_loopresumereplywithcode), passCode) + val reply = rh.gs(R.string.smscommunicator_loopresumereplywithcode, passCode) receivedSms.processed = true messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = true) { override fun run() { @@ -436,7 +436,7 @@ class SmsCommunicatorPlugin @Inject constructor( return } else { val passCode = generatePassCode() - val reply = String.format(rh.gs(R.string.smscommunicator_suspendreplywithcode), duration, passCode) + val reply = rh.gs(R.string.smscommunicator_suspendreplywithcode, duration, passCode) receivedSms.processed = true messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = true, duration) { override fun run() { @@ -515,7 +515,7 @@ class SmsCommunicatorPlugin @Inject constructor( receivedSms.processed = true } else if ((divided.size == 2) && (divided[1].equals("CONNECT", ignoreCase = true))) { val passCode = generatePassCode() - val reply = String.format(rh.gs(R.string.smscommunicator_pumpconnectwithcode), passCode) + val reply = rh.gs(R.string.smscommunicator_pumpconnectwithcode, passCode) receivedSms.processed = true messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = true) { override fun run() { @@ -548,7 +548,7 @@ class SmsCommunicatorPlugin @Inject constructor( return } else { val passCode = generatePassCode() - val reply = String.format(rh.gs(R.string.smscommunicator_pumpdisconnectwithcode), duration, passCode) + val reply = rh.gs(R.string.smscommunicator_pumpdisconnectwithcode, duration, passCode) receivedSms.processed = true messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = true) { override fun run() { @@ -601,7 +601,7 @@ class SmsCommunicatorPlugin @Inject constructor( if (profile == null) sendSMS(Sms(receivedSms.phoneNumber, rh.gs(R.string.noprofile))) else { val passCode = generatePassCode() - val reply = String.format(rh.gs(R.string.smscommunicator_profilereplywithcode), list[pIndex - 1], percentage, passCode) + val reply = rh.gs(R.string.smscommunicator_profilereplywithcode, list[pIndex - 1], percentage, passCode) receivedSms.processed = true val finalPercentage = percentage messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = true, list[pIndex - 1] as String, finalPercentage) { @@ -627,7 +627,7 @@ class SmsCommunicatorPlugin @Inject constructor( private fun processBASAL(divided: Array, receivedSms: Sms) { if (divided[1].uppercase(Locale.getDefault()) == "CANCEL" || divided[1].uppercase(Locale.getDefault()) == "STOP") { val passCode = generatePassCode() - val reply = String.format(rh.gs(R.string.smscommunicator_basalstopreplywithcode), passCode) + val reply = rh.gs(R.string.smscommunicator_basalstopreplywithcode, passCode) receivedSms.processed = true messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = true) { override fun run() { @@ -662,14 +662,14 @@ class SmsCommunicatorPlugin @Inject constructor( else { tempBasalPct = constraintChecker.applyBasalPercentConstraints(Constraint(tempBasalPct), profile).value() val passCode = generatePassCode() - val reply = String.format(rh.gs(R.string.smscommunicator_basalpctreplywithcode), tempBasalPct, duration, passCode) + val reply = rh.gs(R.string.smscommunicator_basalpctreplywithcode, tempBasalPct, duration, passCode) receivedSms.processed = true messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = true, tempBasalPct, duration) { override fun run() { commandQueue.tempBasalPercent(anInteger(), secondInteger(), true, profile, PumpSync.TemporaryBasalType.NORMAL, object : Callback() { override fun run() { if (result.success) { - var replyText = if (result.isPercent) String.format(rh.gs(R.string.smscommunicator_tempbasalset_percent), result.percent, result.duration) else String.format(rh.gs(R.string.smscommunicator_tempbasalset), result.absolute, result.duration) + var replyText = if (result.isPercent) rh.gs(R.string.smscommunicator_tempbasalset_percent, result.percent, result.duration) else rh.gs(R.string.smscommunicator_tempbasalset, result.absolute, result.duration) replyText += "\n" + activePlugin.activePump.shortStatus(true) sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, replyText)) if (result.isPercent) @@ -706,15 +706,15 @@ class SmsCommunicatorPlugin @Inject constructor( else { tempBasal = constraintChecker.applyBasalConstraints(Constraint(tempBasal), profile).value() val passCode = generatePassCode() - val reply = String.format(rh.gs(R.string.smscommunicator_basalreplywithcode), tempBasal, duration, passCode) + val reply = rh.gs(R.string.smscommunicator_basalreplywithcode, tempBasal, duration, passCode) receivedSms.processed = true messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = true, tempBasal, duration) { override fun run() { commandQueue.tempBasalAbsolute(aDouble(), secondInteger(), true, profile, PumpSync.TemporaryBasalType.NORMAL, object : Callback() { override fun run() { if (result.success) { - var replyText = if (result.isPercent) String.format(rh.gs(R.string.smscommunicator_tempbasalset_percent), result.percent, result.duration) - else String.format(rh.gs(R.string.smscommunicator_tempbasalset), result.absolute, result.duration) + var replyText = if (result.isPercent) rh.gs(R.string.smscommunicator_tempbasalset_percent, result.percent, result.duration) + else rh.gs(R.string.smscommunicator_tempbasalset, result.absolute, result.duration) replyText += "\n" + activePlugin.activePump.shortStatus(true) sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, replyText)) if (result.isPercent) @@ -743,7 +743,7 @@ class SmsCommunicatorPlugin @Inject constructor( private fun processEXTENDED(divided: Array, receivedSms: Sms) { if (divided[1].uppercase(Locale.getDefault()) == "CANCEL" || divided[1].uppercase(Locale.getDefault()) == "STOP") { val passCode = generatePassCode() - val reply = String.format(rh.gs(R.string.smscommunicator_extendedstopreplywithcode), passCode) + val reply = rh.gs(R.string.smscommunicator_extendedstopreplywithcode, passCode) receivedSms.processed = true messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = true) { override fun run() { @@ -773,14 +773,14 @@ class SmsCommunicatorPlugin @Inject constructor( if (extended == 0.0 || duration == 0) sendSMS(Sms(receivedSms.phoneNumber, rh.gs(R.string.wrongformat))) else { val passCode = generatePassCode() - val reply = String.format(rh.gs(R.string.smscommunicator_extendedreplywithcode), extended, duration, passCode) + val reply = rh.gs(R.string.smscommunicator_extendedreplywithcode, extended, duration, passCode) receivedSms.processed = true messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = true, extended, duration) { override fun run() { commandQueue.extendedBolus(aDouble(), secondInteger(), object : Callback() { override fun run() { if (result.success) { - var replyText = String.format(rh.gs(R.string.smscommunicator_extendedset), aDouble, duration) + var replyText = rh.gs(R.string.smscommunicator_extendedset, aDouble, duration) if (config.APS) replyText += "\n" + rh.gs(R.string.loopsuspended) replyText += "\n" + activePlugin.activePump.shortStatus(true) sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, replyText)) @@ -817,9 +817,9 @@ class SmsCommunicatorPlugin @Inject constructor( } else if (bolus > 0.0) { val passCode = generatePassCode() val reply = if (isMeal) - String.format(rh.gs(R.string.smscommunicator_mealbolusreplywithcode), bolus, passCode) + rh.gs(R.string.smscommunicator_mealbolusreplywithcode, bolus, passCode) else - String.format(rh.gs(R.string.smscommunicator_bolusreplywithcode), bolus, passCode) + rh.gs(R.string.smscommunicator_bolusreplywithcode, bolus, passCode) receivedSms.processed = true messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = true, bolus) { override fun run() { @@ -833,9 +833,9 @@ class SmsCommunicatorPlugin @Inject constructor( override fun run() { if (resultSuccess) { var replyText = if (isMeal) - String.format(rh.gs(R.string.smscommunicator_mealbolusdelivered), resultBolusDelivered) + rh.gs(R.string.smscommunicator_mealbolusdelivered, resultBolusDelivered) else - String.format(rh.gs(R.string.smscommunicator_bolusdelivered), resultBolusDelivered) + rh.gs(R.string.smscommunicator_bolusdelivered, resultBolusDelivered) replyText += "\n" + activePlugin.activePump.shortStatus(true) lastRemoteBolusTime = dateUtil.now() if (isMeal) { @@ -866,7 +866,7 @@ class SmsCommunicatorPlugin @Inject constructor( val tt = if (currentProfile.units == GlucoseUnit.MMOL) { DecimalFormatter.to1Decimal(eatingSoonTT) } else DecimalFormatter.to0Decimal(eatingSoonTT) - replyText += "\n" + String.format(rh.gs(R.string.smscommunicator_mealbolusdelivered_tt), tt, eatingSoonTTDuration) + replyText += "\n" + rh.gs(R.string.smscommunicator_mealbolusdelivered_tt, tt, eatingSoonTTDuration) } } sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, replyText)) @@ -920,7 +920,7 @@ class SmsCommunicatorPlugin @Inject constructor( if (grams == 0) sendSMS(Sms(receivedSms.phoneNumber, rh.gs(R.string.wrongformat))) else { val passCode = generatePassCode() - val reply = String.format(rh.gs(R.string.smscommunicator_carbsreplywithcode), grams, dateUtil.timeString(time), passCode) + val reply = rh.gs(R.string.smscommunicator_carbsreplywithcode, grams, dateUtil.timeString(time), passCode) receivedSms.processed = true messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = true, grams, time) { override fun run() { @@ -930,7 +930,7 @@ class SmsCommunicatorPlugin @Inject constructor( commandQueue.bolus(detailedBolusInfo, object : Callback() { override fun run() { if (result.success) { - var replyText = String.format(rh.gs(R.string.smscommunicator_carbsset), anInteger) + var replyText = rh.gs(R.string.smscommunicator_carbsset, anInteger) replyText += "\n" + activePlugin.activePump.shortStatus(true) sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, replyText)) uel.log(Action.CARBS, Sources.SMS, activePlugin.activePump.shortStatus(true) + ": " + rh.gs(R.string.smscommunicator_carbsset, anInteger), @@ -956,7 +956,7 @@ class SmsCommunicatorPlugin @Inject constructor( val isStop = divided[1].equals("STOP", ignoreCase = true) || divided[1].equals("CANCEL", ignoreCase = true) if (isMeal || isActivity || isHypo) { val passCode = generatePassCode() - val reply = String.format(rh.gs(R.string.smscommunicator_temptargetwithcode), divided[1].uppercase(Locale.getDefault()), passCode) + val reply = rh.gs(R.string.smscommunicator_temptargetwithcode, divided[1].uppercase(Locale.getDefault()), passCode) receivedSms.processed = true messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = false) { override fun run() { @@ -1009,7 +1009,7 @@ class SmsCommunicatorPlugin @Inject constructor( aapsLogger.error(LTag.DATABASE, "Error while saving temporary target", it) }) val ttString = if (units == GlucoseUnit.MMOL) DecimalFormatter.to1Decimal(tt) else DecimalFormatter.to0Decimal(tt) - val replyText = String.format(rh.gs(R.string.smscommunicator_tt_set), ttString, ttDuration) + val replyText = rh.gs(R.string.smscommunicator_tt_set, ttString, ttDuration) sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, replyText)) uel.log(Action.TT, Sources.SMS, ValueWithUnit.fromGlucoseUnit(tt, units.asText), @@ -1018,7 +1018,7 @@ class SmsCommunicatorPlugin @Inject constructor( }) } else if (isStop) { val passCode = generatePassCode() - val reply = String.format(rh.gs(R.string.smscommunicator_temptargetcancel), passCode) + val reply = rh.gs(R.string.smscommunicator_temptargetcancel, passCode) receivedSms.processed = true messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = false) { override fun run() { @@ -1028,7 +1028,7 @@ class SmsCommunicatorPlugin @Inject constructor( }, { aapsLogger.error(LTag.DATABASE, "Error while saving temporary target", it) }) - val replyText = String.format(rh.gs(R.string.smscommunicator_tt_canceled)) + val replyText = rh.gs(R.string.smscommunicator_tt_canceled) sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, replyText)) uel.log(Action.CANCEL_TT, Sources.SMS, rh.gs(R.string.smscommunicator_tt_canceled), ValueWithUnit.SimpleString(rh.gsNotLocalised(R.string.smscommunicator_tt_canceled))) @@ -1043,12 +1043,12 @@ class SmsCommunicatorPlugin @Inject constructor( || divided[1].equals("DISABLE", ignoreCase = true)) if (isStop) { val passCode = generatePassCode() - val reply = String.format(rh.gs(R.string.smscommunicator_stopsmswithcode), passCode) + val reply = rh.gs(R.string.smscommunicator_stopsmswithcode, passCode) receivedSms.processed = true messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = false) { override fun run() { sp.putBoolean(R.string.key_smscommunicator_remotecommandsallowed, false) - val replyText = String.format(rh.gs(R.string.smscommunicator_stoppedsms)) + val replyText = rh.gs(R.string.smscommunicator_stoppedsms) sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, replyText)) uel.log(Action.STOP_SMS, Sources.SMS, rh.gs(R.string.smscommunicator_stoppedsms), ValueWithUnit.SimpleString(rh.gsNotLocalised(R.string.smscommunicator_stoppedsms))) @@ -1061,7 +1061,7 @@ class SmsCommunicatorPlugin @Inject constructor( val cal = SafeParse.stringToDouble(divided[1]) if (cal > 0.0) { val passCode = generatePassCode() - val reply = String.format(rh.gs(R.string.smscommunicator_calibrationreplywithcode), cal, passCode) + val reply = rh.gs(R.string.smscommunicator_calibrationreplywithcode, cal, passCode) receivedSms.processed = true messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction(pumpCommand = false, cal) { override fun run() { @@ -1127,7 +1127,7 @@ class SmsCommunicatorPlugin @Inject constructor( } private fun generatePassCode(): String = - String.format(rh.gs(R.string.smscommunicator_code_from_authenticator_for), otp.name()) + rh.gs(R.string.smscommunicator_code_from_authenticator_for, otp.name()) private fun stripAccents(str: String): String { var s = str diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/activities/SmsCommunicatorOtpActivity.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/activities/SmsCommunicatorOtpActivity.kt index f3f7b150bb..680bbd2727 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/activities/SmsCommunicatorOtpActivity.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/activities/SmsCommunicatorOtpActivity.kt @@ -31,7 +31,6 @@ import javax.inject.Inject class SmsCommunicatorOtpActivity : NoSplashAppCompatActivity() { @Inject lateinit var fabricPrivacy: FabricPrivacy - @Inject lateinit var rxBus: RxBus @Inject lateinit var smsCommunicatorPlugin: SmsCommunicatorPlugin @Inject lateinit var otp: OneTimePassword @Inject lateinit var uel: UserEntryLogger diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/themes/ThemeSwitcherPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/themes/ThemeSwitcherPlugin.kt new file mode 100644 index 0000000000..0523fde640 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/themes/ThemeSwitcherPlugin.kt @@ -0,0 +1,61 @@ +package info.nightscout.androidaps.plugins.general.themes + +import androidx.appcompat.app.AppCompatDelegate +import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM +import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES +import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.R +import info.nightscout.androidaps.events.EventPreferenceChange +import info.nightscout.androidaps.events.EventThemeSwitch +import info.nightscout.androidaps.interfaces.PluginBase +import info.nightscout.androidaps.interfaces.PluginDescription +import info.nightscout.androidaps.interfaces.PluginType +import info.nightscout.shared.logging.AAPSLogger +import info.nightscout.androidaps.plugins.bus.RxBus +import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.shared.sharedPreferences.SP +import io.reactivex.rxjava3.disposables.CompositeDisposable +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ThemeSwitcherPlugin @Inject constructor( + injector: HasAndroidInjector, + aapsLogger: AAPSLogger, + rh: ResourceHelper, + private val sp: SP, + private val rxBus: RxBus, +) : PluginBase(PluginDescription() + .mainType(PluginType.GENERAL) + .neverVisible(true) + .alwaysEnabled(true) + .showInList(false) + .pluginName(R.string.dst_plugin_name), + aapsLogger, rh, injector +) { + + private val compositeDisposable = CompositeDisposable() + + override fun onStart() { + compositeDisposable.add(rxBus.toObservable(EventPreferenceChange::class.java).subscribe { + if (it.isChanged(rh, id = R.string.key_use_dark_mode)) { + setThemeMode() + rxBus.send(EventThemeSwitch()) + } + }) + } + + fun setThemeMode() { + val mode = when (sp.getString(R.string.key_use_dark_mode, "dark")) { + sp.getString(R.string.value_dark_theme, "dark") -> MODE_NIGHT_YES + sp.getString(R.string.value_light_theme, "light") -> MODE_NIGHT_NO + else -> MODE_NIGHT_FOLLOW_SYSTEM + } + AppCompatDelegate.setDefaultNightMode(mode) + } + + override fun onStop() { + compositeDisposable.dispose() + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/ActionStringHandler.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/ActionStringHandler.kt index 481e904868..d4eb8a30e1 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/ActionStringHandler.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/ActionStringHandler.kt @@ -111,7 +111,7 @@ class ActionStringHandler @Inject constructor( @Synchronized private fun handleInitiate(actionString: String) { //TODO: i18n - Log.i("ActionStringHandler", "handleInitiate actionString=" + actionString) + Log.i("ActionStringHandler", "handleInitiate actionString=$actionString") if (!sp.getBoolean(R.string.key_wear_control, false)) return lastBolusWizard = null var rTitle = rh.gs(R.string.confirm).uppercase() @@ -640,10 +640,10 @@ class ActionStringHandler @Inject constructor( var msg = "" //check for validity if (percentage < Constants.CPP_MIN_PERCENTAGE || percentage > Constants.CPP_MAX_PERCENTAGE) { - msg += String.format(rh.gs(R.string.valueoutofrange), "Profile-Percentage") + "\n" + msg += rh.gs(R.string.valueoutofrange, "Profile-Percentage") + "\n" } if (timeshift < 0 || timeshift > 23) { - msg += String.format(rh.gs(R.string.valueoutofrange), "Profile-Timeshift") + "\n" + msg += rh.gs(R.string.valueoutofrange, "Profile-Timeshift") + "\n" } val profile = profileFunction.getProfile() if (profile == null) { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/WearPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/WearPlugin.kt index d6be8f54cd..43b4130778 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/WearPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/WearPlugin.kt @@ -95,7 +95,7 @@ class WearPlugin @Inject constructor( .toObservable(EventBolusRequested::class.java) .observeOn(aapsSchedulers.io) .subscribe({ event: EventBolusRequested -> - val status = String.format(rh.gs(R.string.bolusrequested), event.amount) + val status = rh.gs(R.string.bolusrequested, event.amount) val intent = Intent(ctx, WatchUpdaterService::class.java).setAction(WatchUpdaterService.ACTION_SEND_BOLUSPROGRESS) intent.putExtra("progresspercent", 0) intent.putExtra("progressstatus", status) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/SendToDataLayerThread.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/SendToDataLayerThread.java index ee4c30e0d1..0852b0a6aa 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/SendToDataLayerThread.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/SendToDataLayerThread.java @@ -120,7 +120,7 @@ class SendToDataLayerThread extends AsyncTask { } state = 0; } catch (Exception e) { - Log.e(TAG, logPrefix + "Got exception in sendToWear: " + e.toString()); + Log.e(TAG, logPrefix + "Got exception in sendToWear: " + e); } finally { lastlock = 0; lock.unlock(); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/WatchUpdaterService.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/WatchUpdaterService.java index 95cf6128a2..85d08c142c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/WatchUpdaterService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/WatchUpdaterService.java @@ -46,9 +46,6 @@ import info.nightscout.androidaps.interfaces.Loop; import info.nightscout.androidaps.interfaces.PluginBase; import info.nightscout.androidaps.interfaces.Profile; import info.nightscout.androidaps.interfaces.ProfileFunction; -import info.nightscout.androidaps.utils.wizard.QuickWizardEntry; -import info.nightscout.shared.logging.AAPSLogger; -import info.nightscout.shared.logging.LTag; import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin; import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus; @@ -64,7 +61,11 @@ import info.nightscout.androidaps.utils.DefaultValueHelper; import info.nightscout.androidaps.utils.TrendCalculator; import info.nightscout.androidaps.utils.resources.ResourceHelper; import info.nightscout.androidaps.utils.wizard.QuickWizard; +import info.nightscout.androidaps.utils.wizard.QuickWizardEntry; +import info.nightscout.shared.logging.AAPSLogger; +import info.nightscout.shared.logging.LTag; import info.nightscout.shared.sharedPreferences.SP; +import info.nightscout.shared.weardata.WearUris; public class WatchUpdaterService extends WearableListenerService implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener { @Inject public GlucoseStatusProvider glucoseStatusProvider; @@ -95,21 +96,6 @@ public class WatchUpdaterService extends WearableListenerService implements Goog public static final String ACTION_CANCEL_NOTIFICATION = WatchUpdaterService.class.getName().concat(".CancelNotification"); private GoogleApiClient googleApiClient; - public static final String WEARABLE_DATA_PATH = "/nightscout_watch_data"; - public static final String WEARABLE_RESEND_PATH = "/nightscout_watch_data_resend"; - private static final String WEARABLE_CANCELBOLUS_PATH = "/nightscout_watch_cancel_bolus"; - public static final String WEARABLE_CONFIRM_ACTIONSTRING_PATH = "/nightscout_watch_confirmactionstring"; - public static final String WEARABLE_INITIATE_ACTIONSTRING_PATH = "/nightscout_watch_initiateactionstring"; - - private static final String OPEN_SETTINGS_PATH = "/openwearsettings"; - private static final String NEW_STATUS_PATH = "/sendstatustowear"; - private static final String NEW_PREFERENCES_PATH = "/sendpreferencestowear"; - private static final String QUICK_WIZARD_PATH = "/send_quick_wizard"; - public static final String BASAL_DATA_PATH = "/nightscout_watch_basal"; - public static final String BOLUS_PROGRESS_PATH = "/nightscout_watch_bolusprogress"; - public static final String ACTION_CONFIRMATION_REQUEST_PATH = "/nightscout_watch_actionconfirmationrequest"; - public static final String ACTION_CHANGECONFIRMATION_REQUEST_PATH = "/nightscout_watch_changeconfirmationrequest"; - public static final String ACTION_CANCELNOTIFICATION_REQUEST_PATH = "/nightscout_watch_cancelnotificationrequest"; String TAG = "WatchUpdateService"; @@ -259,21 +245,21 @@ public class WatchUpdaterService extends WearableListenerService implements Goog // Log.d(TAG, "onMessageRecieved: " + event); if (wearIntegration()) { - if (event != null && event.getPath().equals(WEARABLE_RESEND_PATH)) { + if (event != null && event.getPath().equals(WearUris.WEARABLE_RESEND_PATH)) { resendData(); } - if (event != null && event.getPath().equals(WEARABLE_CANCELBOLUS_PATH)) { + if (event != null && event.getPath().equals(WearUris.WEARABLE_CANCELBOLUS_PATH)) { cancelBolus(); } - if (event != null && event.getPath().equals(WEARABLE_INITIATE_ACTIONSTRING_PATH)) { + if (event != null && event.getPath().equals(WearUris.WEARABLE_INITIATE_ACTIONSTRING_PATH)) { String actionstring = new String(event.getData()); aapsLogger.debug(LTag.WEAR, "Wear: " + actionstring); rxBus.send(new EventWearInitiateAction(actionstring)); } - if (event != null && event.getPath().equals(WEARABLE_CONFIRM_ACTIONSTRING_PATH)) { + if (event != null && event.getPath().equals(WearUris.WEARABLE_CONFIRM_ACTIONSTRING_PATH)) { String actionstring = new String(event.getData()); aapsLogger.debug(LTag.WEAR, "Wear Confirm: " + actionstring); rxBus.send(new EventWearConfirmAction(actionstring)); @@ -299,7 +285,7 @@ public class WatchUpdaterService extends WearableListenerService implements Goog final DataMap dataMap = dataMapSingleBG(lastBG, glucoseStatus); - (new SendToDataLayerThread(WEARABLE_DATA_PATH, googleApiClient)).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, dataMap); + (new SendToDataLayerThread(WearUris.WEARABLE_DATA_PATH, googleApiClient)).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, dataMap); } } } @@ -389,7 +375,7 @@ public class WatchUpdaterService extends WearableListenerService implements Goog dataMaps.add(dataMap); } entries.putDataMapArrayList("entries", dataMaps); - (new SendToDataLayerThread(WEARABLE_DATA_PATH, googleApiClient)).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, entries); + (new SendToDataLayerThread(WearUris.WEARABLE_DATA_PATH, googleApiClient)).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, entries); } sendBasals(); sendStatus(); @@ -423,13 +409,12 @@ public class WatchUpdaterService extends WearableListenerService implements Goog double endBasalValue = beginBasalValue; TemporaryBasal tb1 = iobCobCalculator.getTempBasalIncludingConvertedExtended(runningTime); - TemporaryBasal tb2 = iobCobCalculator.getTempBasalIncludingConvertedExtended(runningTime); //TODO for Adrian ... what's the meaning? + TemporaryBasal tb2; double tb_before = beginBasalValue; double tb_amount = beginBasalValue; long tb_start = runningTime; if (tb1 != null) { - tb_before = beginBasalValue; Profile profileTB = profileFunction.getProfile(runningTime); if (profileTB != null) { tb_amount = TemporaryBasalExtensionKt.convertedToAbsolute(tb1, runningTime, profileTB); @@ -457,7 +442,7 @@ public class WatchUpdaterService extends WearableListenerService implements Goog tb2 = iobCobCalculator.getTempBasalIncludingConvertedExtended(runningTime); if (tb1 == null && tb2 == null) { - //no temp stays no temp + ; //no temp stays no temp } else if (tb1 != null && tb2 == null) { //temp is over -> push it @@ -529,7 +514,8 @@ public class WatchUpdaterService extends WearableListenerService implements Goog if (!predArray.isEmpty()) { for (GlucoseValueDataPoint bg : predArray) { if (bg.getData().getValue() < 40) continue; - predictions.add(predictionMap(bg.getData().getTimestamp(), bg.getData().getValue(), bg.getPredictionColor())); + predictions.add(predictionMap(bg.getData().getTimestamp(), + bg.getData().getValue(), bg.color(null))); } } } @@ -540,7 +526,7 @@ public class WatchUpdaterService extends WearableListenerService implements Goog dm.putDataMapArrayList("temps", temps); dm.putDataMapArrayList("boluses", boluses); dm.putDataMapArrayList("predictions", predictions); - (new SendToDataLayerThread(BASAL_DATA_PATH, googleApiClient)).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, dm); + (new SendToDataLayerThread(WearUris.BASAL_DATA_PATH, googleApiClient)).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, dm); } private DataMap tempDatamap(long startTime, double startBasal, long to, double toBasal, double amount) { @@ -582,7 +568,7 @@ public class WatchUpdaterService extends WearableListenerService implements Goog private void sendNotification() { if (googleApiClient != null && googleApiClient.isConnected()) { - PutDataMapRequest dataMapRequest = PutDataMapRequest.create(OPEN_SETTINGS_PATH); + PutDataMapRequest dataMapRequest = PutDataMapRequest.create(WearUris.OPEN_SETTINGS_PATH); //unique content dataMapRequest.getDataMap().putLong("timestamp", System.currentTimeMillis()); dataMapRequest.getDataMap().putString("openSettings", "openSettings"); @@ -595,7 +581,7 @@ public class WatchUpdaterService extends WearableListenerService implements Goog private void sendBolusProgress(int progresspercent, String status) { if (googleApiClient != null && googleApiClient.isConnected()) { - PutDataMapRequest dataMapRequest = PutDataMapRequest.create(BOLUS_PROGRESS_PATH); + PutDataMapRequest dataMapRequest = PutDataMapRequest.create(WearUris.BOLUS_PROGRESS_PATH); //unique content dataMapRequest.getDataMap().putLong("timestamp", System.currentTimeMillis()); dataMapRequest.getDataMap().putString("bolusProgress", "bolusProgress"); @@ -610,7 +596,7 @@ public class WatchUpdaterService extends WearableListenerService implements Goog private void sendActionConfirmationRequest(String title, String message, String actionstring) { if (googleApiClient != null && googleApiClient.isConnected()) { - PutDataMapRequest dataMapRequest = PutDataMapRequest.create(ACTION_CONFIRMATION_REQUEST_PATH); + PutDataMapRequest dataMapRequest = PutDataMapRequest.create(WearUris.ACTION_CONFIRMATION_REQUEST_PATH); //unique content dataMapRequest.getDataMap().putLong("timestamp", System.currentTimeMillis()); dataMapRequest.getDataMap().putString("actionConfirmationRequest", "actionConfirmationRequest"); @@ -629,7 +615,7 @@ public class WatchUpdaterService extends WearableListenerService implements Goog private void sendChangeConfirmationRequest(String title, String message, String actionstring) { if (googleApiClient != null && googleApiClient.isConnected()) { - PutDataMapRequest dataMapRequest = PutDataMapRequest.create(ACTION_CHANGECONFIRMATION_REQUEST_PATH); + PutDataMapRequest dataMapRequest = PutDataMapRequest.create(WearUris.ACTION_CHANGECONFIRMATION_REQUEST_PATH); //unique content dataMapRequest.getDataMap().putLong("timestamp", System.currentTimeMillis()); dataMapRequest.getDataMap().putString("changeConfirmationRequest", "changeConfirmationRequest"); @@ -648,7 +634,7 @@ public class WatchUpdaterService extends WearableListenerService implements Goog private void sendCancelNotificationRequest(String actionstring) { if (googleApiClient != null && googleApiClient.isConnected()) { - PutDataMapRequest dataMapRequest = PutDataMapRequest.create(ACTION_CANCELNOTIFICATION_REQUEST_PATH); + PutDataMapRequest dataMapRequest = PutDataMapRequest.create(WearUris.ACTION_CANCELNOTIFICATION_REQUEST_PATH); //unique content dataMapRequest.getDataMap().putLong("timestamp", System.currentTimeMillis()); dataMapRequest.getDataMap().putString("cancelNotificationRequest", "cancelNotificationRequest"); @@ -704,7 +690,7 @@ public class WatchUpdaterService extends WearableListenerService implements Goog openApsStatus = nsDeviceStatus.getOpenApsTimestamp(); } - PutDataMapRequest dataMapRequest = PutDataMapRequest.create(NEW_STATUS_PATH); + PutDataMapRequest dataMapRequest = PutDataMapRequest.create(WearUris.NEW_STATUS_PATH); //unique content dataMapRequest.getDataMap().putString("externalStatusString", status); dataMapRequest.getDataMap().putString("iobSum", iobSum); @@ -734,14 +720,14 @@ public class WatchUpdaterService extends WearableListenerService implements Goog int percentage = sp.getInt(R.string.key_boluswizard_percentage, 100); int maxCarbs = sp.getInt(R.string.key_treatmentssafety_maxcarbs, 48); double maxBolus = sp.getDouble(R.string.key_treatmentssafety_maxbolus, 3.0); - PutDataMapRequest dataMapRequest = PutDataMapRequest.create(NEW_PREFERENCES_PATH); + PutDataMapRequest dataMapRequest = PutDataMapRequest.create(WearUris.NEW_PREFERENCES_PATH); //unique content dataMapRequest.getDataMap().putLong("timestamp", System.currentTimeMillis()); dataMapRequest.getDataMap().putBoolean(rh.gs(R.string.key_wear_control), wearcontrol); dataMapRequest.getDataMap().putBoolean(rh.gs(R.string.key_units_mgdl), mgdl); dataMapRequest.getDataMap().putInt(rh.gs(R.string.key_boluswizard_percentage), percentage); dataMapRequest.getDataMap().putInt(rh.gs(R.string.key_treatmentssafety_maxcarbs), maxCarbs); - dataMapRequest.getDataMap().putDouble(rh.gs(R.string.key_treatmentssafety_maxbolus),maxBolus); + dataMapRequest.getDataMap().putDouble(rh.gs(R.string.key_treatmentssafety_maxbolus), maxBolus); PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest(); Wearable.DataApi.putDataItem(googleApiClient, putDataRequest); } else { @@ -753,14 +739,14 @@ public class WatchUpdaterService extends WearableListenerService implements Goog if (googleApiClient != null && googleApiClient.isConnected()) { int size = quickWizard.size(); ArrayList entities = new ArrayList<>(); - for(int i=0; i < size; i++) { + for (int i = 0; i < size; i++) { QuickWizardEntry q = quickWizard.get(i); if (q.forDevice(QuickWizardEntry.DEVICE_WATCH)) { entities.add(quickMap(q)); } } - PutDataMapRequest dataMapRequest = PutDataMapRequest.create(QUICK_WIZARD_PATH); + PutDataMapRequest dataMapRequest = PutDataMapRequest.create(WearUris.QUICK_WIZARD_PATH); DataMap dm = dataMapRequest.getDataMap(); dm.putLong("timestamp", System.currentTimeMillis()); @@ -794,7 +780,7 @@ public class WatchUpdaterService extends WearableListenerService implements Goog return status; } - if (!((PluginBase)loop).isEnabled()) { + if (!((PluginBase) loop).isEnabled()) { status += rh.gs(R.string.disabledloop) + "\n"; lastLoopStatus = false; } else { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/insulin/ActivityGraph.kt b/app/src/main/java/info/nightscout/androidaps/plugins/insulin/ActivityGraph.kt index c7f66ef4eb..7662100e49 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/insulin/ActivityGraph.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/insulin/ActivityGraph.kt @@ -44,6 +44,9 @@ class ActivityGraph : GraphView { viewport.isXAxisBoundsManual = true viewport.setMinX(0.0) viewport.setMaxX((hours * 60).toDouble()) + viewport.isYAxisBoundsManual = true + viewport.setMinY(0.0) + viewport.setMaxY(0.01) gridLabelRenderer.numHorizontalLabels = (hours + 1).toInt() gridLabelRenderer.horizontalAxisTitle = "[min]" secondScale.addSeries(LineGraphSeries(Array(iobArray.size) { i -> iobArray[i] }).also { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPlugin.kt index d7991a6707..cf38b3e857 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPlugin.kt @@ -1,6 +1,5 @@ package info.nightscout.androidaps.plugins.iob.iobCobCalculator -import android.os.SystemClock import androidx.collection.LongSparseArray import dagger.android.HasAndroidInjector import info.nightscout.androidaps.Constants @@ -19,20 +18,19 @@ import info.nightscout.androidaps.extensions.convertedToAbsolute import info.nightscout.androidaps.extensions.iobCalc import info.nightscout.androidaps.extensions.toTemporaryBasal import info.nightscout.androidaps.interfaces.* -import info.nightscout.shared.logging.AAPSLogger -import info.nightscout.shared.logging.LTag import info.nightscout.androidaps.plugins.bus.RxBus +import info.nightscout.androidaps.plugins.general.overview.OverviewData import info.nightscout.androidaps.plugins.iob.iobCobCalculator.data.AutosensData import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventNewHistoryData -import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin -import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref1Plugin -import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.DecimalFormatter import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.T import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.rx.AapsSchedulers +import info.nightscout.androidaps.workflow.CalculationWorkflow +import info.nightscout.shared.logging.AAPSLogger +import info.nightscout.shared.logging.LTag import info.nightscout.shared.sharedPreferences.SP import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign @@ -57,12 +55,11 @@ class IobCobCalculatorPlugin @Inject constructor( rh: ResourceHelper, private val profileFunction: ProfileFunction, private val activePlugin: ActivePlugin, - private val sensitivityOref1Plugin: SensitivityOref1Plugin, - private val sensitivityAAPSPlugin: SensitivityAAPSPlugin, - private val sensitivityWeightedAveragePlugin: SensitivityWeightedAveragePlugin, private val fabricPrivacy: FabricPrivacy, private val dateUtil: DateUtil, - private val repository: AppRepository + private val repository: AppRepository, + val overviewData: OverviewData, + private val calculationWorkflow: CalculationWorkflow ) : PluginBase( PluginDescription() .mainType(PluginType.GENERAL) @@ -81,7 +78,6 @@ class IobCobCalculatorPlugin @Inject constructor( override var ads: AutosensDataStore = AutosensDataStore() private val dataLock = Any() - var stopCalculationTrigger = false private var thread: Thread? = null override fun onStart() { @@ -117,14 +113,6 @@ class IobCobCalculatorPlugin @Inject constructor( resetDataAndRunCalculation("onEventPreferenceChange", event) } }, fabricPrivacy::logException) - // EventAppInitialized - disposable += rxBus - .toObservable(EventAppInitialized::class.java) - .observeOn(aapsSchedulers.io) - .subscribe( - { event -> runCalculation("onEventAppInitialized", System.currentTimeMillis(), bgDataReload = true, limitDataToOldestAvailable = true, cause = event) }, - fabricPrivacy::logException - ) // EventNewHistoryData disposable += rxBus .toObservable(EventNewHistoryData::class.java) @@ -138,10 +126,10 @@ class IobCobCalculatorPlugin @Inject constructor( } private fun resetDataAndRunCalculation(reason: String, event: Event?) { - stopCalculation(reason) + calculationWorkflow.stopCalculation(CalculationWorkflow.MAIN_CALCULATION,reason) clearCache() ads.reset() - runCalculation(reason, System.currentTimeMillis(), bgDataReload = false, limitDataToOldestAvailable = true, cause = event) + calculationWorkflow.runCalculation(CalculationWorkflow.MAIN_CALCULATION,this, overviewData, reason, System.currentTimeMillis(), bgDataReload = false, limitDataToOldestAvailable = true, cause = event, runLoop = true) } override fun clearCache() { @@ -168,10 +156,9 @@ class IobCobCalculatorPlugin @Inject constructor( return oldestTime } - fun calculateDetectionStart(from: Long, limitDataToOldestAvailable: Boolean): Long { + override fun calculateDetectionStart(from: Long, limitDataToOldestAvailable: Boolean): Long { val profile = profileFunction.getProfile(from) - var dia = Constants.defaultDIA - if (profile != null) dia = profile.dia + val dia = profile?.dia ?: Constants.defaultDIA val oldestDataAvailable = oldestDataAvailable() val getBGDataFrom: Long if (limitDataToOldestAvailable) { @@ -302,11 +289,7 @@ class IobCobCalculatorPlugin @Inject constructor( override fun getMealDataWithWaitingForCalculationFinish(): MealData { val result = MealData() val now = System.currentTimeMillis() - val maxAbsorptionHours: Double = if (sensitivityAAPSPlugin.isEnabled() || sensitivityWeightedAveragePlugin.isEnabled()) { - sp.getDouble(R.string.key_absorption_maxtime, Constants.DEFAULT_MAX_ABSORPTION_TIME) - } else { - sp.getDouble(R.string.key_absorption_cutoff, Constants.DEFAULT_MAX_ABSORPTION_TIME) - } + val maxAbsorptionHours: Double = activePlugin.activeSensitivity.maxAbsorptionHours() val absorptionTimeAgo = now - (maxAbsorptionHours * T.hours(1).msecs()).toLong() repository.getCarbsDataFromTimeToTimeExpanded(absorptionTimeAgo + 1, now, true) .blockingGet() @@ -366,27 +349,6 @@ class IobCobCalculatorPlugin @Inject constructor( return sb.toString() } - fun stopCalculation(from: String) { - if (thread?.state != Thread.State.TERMINATED) { - stopCalculationTrigger = true - aapsLogger.debug(LTag.AUTOSENS, "Stopping calculation thread: $from") - while (thread != null && thread?.state != Thread.State.TERMINATED) { - SystemClock.sleep(100) - } - aapsLogger.debug(LTag.AUTOSENS, "Calculation thread stopped: $from") - } - } - - fun runCalculation(from: String, end: Long, bgDataReload: Boolean, limitDataToOldestAvailable: Boolean, cause: Event?) { - aapsLogger.debug(LTag.AUTOSENS, "Starting calculation thread: " + from + " to " + dateUtil.dateAndTimeAndSecondsString(end)) - if (thread == null || thread?.state == Thread.State.TERMINATED) { - thread = - if (sensitivityOref1Plugin.isEnabled()) IobCobOref1Thread(injector, this, from, end, bgDataReload, limitDataToOldestAvailable, cause) - else IobCobThread(injector, this, from, end, bgDataReload, limitDataToOldestAvailable, cause) - thread?.start() - } - } - // Limit rate of EventNewHistoryData private val historyWorker = Executors.newSingleThreadScheduledExecutor() private var scheduledHistoryPost: ScheduledFuture<*>? = null @@ -428,7 +390,7 @@ class IobCobCalculatorPlugin @Inject constructor( // When historical data is changed (coming from NS etc) finished calculations after this date must be invalidated private fun newHistoryData(oldDataTimestamp: Long, bgDataReload: Boolean, event: Event) { //log.debug("Locking onNewHistoryData"); - stopCalculation("onEventNewHistoryData") + calculationWorkflow.stopCalculation(CalculationWorkflow.MAIN_CALCULATION,"onEventNewHistoryData") synchronized(dataLock) { // clear up 5 min back for proper COB calculation @@ -452,7 +414,7 @@ class IobCobCalculatorPlugin @Inject constructor( } ads.newHistoryData(time, aapsLogger, dateUtil) } - runCalculation(event.javaClass.simpleName, System.currentTimeMillis(), bgDataReload, true, event) + calculationWorkflow.runCalculation(CalculationWorkflow.MAIN_CALCULATION,this, overviewData, event.javaClass.simpleName, System.currentTimeMillis(), bgDataReload, true, event, runLoop = true) //log.debug("Releasing onNewHistoryData"); } @@ -574,17 +536,17 @@ class IobCobCalculatorPlugin @Inject constructor( override fun getTempBasalIncludingConvertedExtended(timestamp: Long): TemporaryBasal? { val tb = repository.getTemporaryBasalActiveAt(timestamp).blockingGet() if (tb is ValueWrapper.Existing) return tb.value - return getConvertedExtended(timestamp); + return getConvertedExtended(timestamp) } override fun getTempBasalIncludingConvertedExtendedForRange(startTime: Long, endTime: Long, calculationStep: Long): Map { - val tempBasals = HashMap(); + val tempBasals = HashMap() val tbs = repository.getTemporaryBasalsDataActiveBetweenTimeAndTime(startTime, endTime).blockingGet() for (t in startTime until endTime step calculationStep) { val tb = tbs.firstOrNull { basal -> basal.timestamp <= t && (basal.timestamp + basal.duration) > t } tempBasals[t] = tb ?: getConvertedExtended(t) } - return tempBasals; + return tempBasals } override fun calculateAbsoluteIobFromBaseBasals(toTime: Long): IobTotal { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.kt b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Worker.kt similarity index 85% rename from app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.kt rename to app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Worker.kt index 1e0abcd34c..69a3b58d5c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Worker.kt @@ -1,8 +1,10 @@ package info.nightscout.androidaps.plugins.iob.iobCobCalculator import android.content.Context -import android.os.PowerManager import android.os.SystemClock +import androidx.work.Worker +import androidx.work.WorkerParameters +import androidx.work.workDataOf import dagger.android.HasAndroidInjector import info.nightscout.androidaps.Constants import info.nightscout.androidaps.R @@ -12,9 +14,8 @@ import info.nightscout.androidaps.events.Event import info.nightscout.androidaps.events.EventAutosensCalculationFinished import info.nightscout.androidaps.extensions.target import info.nightscout.androidaps.interfaces.ActivePlugin +import info.nightscout.androidaps.interfaces.IobCobCalculator import info.nightscout.androidaps.interfaces.ProfileFunction -import info.nightscout.shared.logging.AAPSLogger -import info.nightscout.shared.logging.LTag import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults import info.nightscout.androidaps.plugins.bus.RxBus import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification @@ -23,6 +24,7 @@ import info.nightscout.androidaps.plugins.iob.iobCobCalculator.data.AutosensData import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin +import info.nightscout.androidaps.receivers.DataWorker import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.DecimalFormatter import info.nightscout.androidaps.utils.FabricPrivacy @@ -30,6 +32,9 @@ import info.nightscout.androidaps.utils.Profiler import info.nightscout.androidaps.utils.T import info.nightscout.androidaps.utils.buildHelper.BuildHelper import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.androidaps.workflow.CalculationWorkflow +import info.nightscout.shared.logging.AAPSLogger +import info.nightscout.shared.logging.LTag import info.nightscout.shared.sharedPreferences.SP import java.util.* import javax.inject.Inject @@ -38,15 +43,10 @@ import kotlin.math.max import kotlin.math.min import kotlin.math.roundToLong -class IobCobOref1Thread internal constructor( - private val injector: HasAndroidInjector, - private val iobCobCalculatorPlugin: IobCobCalculatorPlugin, // cannot be injected : HistoryBrowser uses different instance - private val from: String, - private val end: Long, - private val bgDataReload: Boolean, - private val limitDataToOldestAvailable: Boolean, - private val cause: Event? -) : Thread() { +class IobCobOref1Worker( + context: Context, + params: WorkerParameters +) : Worker(context, params) { @Inject lateinit var aapsLogger: AAPSLogger @Inject lateinit var sp: SP @@ -62,48 +62,53 @@ class IobCobOref1Thread internal constructor( @Inject lateinit var fabricPrivacy: FabricPrivacy @Inject lateinit var dateUtil: DateUtil @Inject lateinit var repository: AppRepository - - private var mWakeLock: PowerManager.WakeLock? = null + @Inject lateinit var dataWorker: DataWorker + @Inject lateinit var calculationWorkflow: CalculationWorkflow init { - injector.androidInjector().inject(this) - mWakeLock = (context.applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, rh.gs(R.string.app_name) + ":iobCobThread") + (context.applicationContext as HasAndroidInjector).androidInjector().inject(this) } - override fun run() { + class IobCobOref1WorkerData( + val injector: HasAndroidInjector, + val iobCobCalculator: IobCobCalculator, // cannot be injected : HistoryBrowser uses different instance + val from: String, + val end: Long, + val limitDataToOldestAvailable: Boolean, + val cause: Event? + ) + + override fun doWork(): Result { + + val data = dataWorker.pickupObject(inputData.getLong(DataWorker.STORE_KEY, -1)) as IobCobOref1WorkerData? + ?: return Result.failure(workDataOf("Error" to "missing input data")) + val start = dateUtil.now() - mWakeLock?.acquire(T.mins(10).msecs()) try { - aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread started: $from") + aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread started: ${data.from}") if (!profileFunction.isProfileValid("IobCobThread")) { - aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (No profile): $from") - return // app still initializing + aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (No profile): ${data.from}") + return Result.failure(workDataOf("Error" to "app still initializing")) } //log.debug("Locking calculateSensitivityData"); - val oldestTimeWithData = iobCobCalculatorPlugin.calculateDetectionStart(end, limitDataToOldestAvailable) - if (bgDataReload) { - iobCobCalculatorPlugin.ads.loadBgData(end, repository, aapsLogger, dateUtil, rxBus) - iobCobCalculatorPlugin.clearCache() - } + val oldestTimeWithData = data.iobCobCalculator.calculateDetectionStart(data.end, data.limitDataToOldestAvailable) // work on local copy and set back when finished - val ads = iobCobCalculatorPlugin.ads.clone() + val ads = data.iobCobCalculator.ads.clone() val bucketedData = ads.bucketedData val autosensDataTable = ads.autosensDataTable if (bucketedData == null || bucketedData.size < 3) { - aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (No bucketed data available): $from") - return + aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (No bucketed data available): ${data.from}") + return Result.failure(workDataOf("Error" to "Aborting calculation thread (No bucketed data available): ${data.from}")) } val prevDataTime = ads.roundUpTime(bucketedData[bucketedData.size - 3].timestamp) aapsLogger.debug(LTag.AUTOSENS, "Prev data time: " + dateUtil.dateAndTimeString(prevDataTime)) var previous = autosensDataTable[prevDataTime] // start from oldest to be able sub cob for (i in bucketedData.size - 4 downTo 0) { - val progress = i.toString() + if (buildHelper.isDev()) " ($from)" else "" - rxBus.send(EventIobCalculationProgress(progress, cause)) - if (iobCobCalculatorPlugin.stopCalculationTrigger) { - iobCobCalculatorPlugin.stopCalculationTrigger = false - aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (trigger): $from") - return + rxBus.send(EventIobCalculationProgress(CalculationWorkflow.ProgressData.IOB_COB_OREF, 100 - (100.0 * i / bucketedData.size).toInt(), data.cause)) + if (isStopped) { + aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (trigger): ${data.from}") + return Result.failure(workDataOf("Error" to "Aborting calculation thread (trigger): ${data.from}")) } // check if data already exists var bgTime = bucketedData[i].timestamp @@ -116,12 +121,12 @@ class IobCobOref1Thread internal constructor( } val profile = profileFunction.getProfile(bgTime) if (profile == null) { - aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (no profile): $from") + aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (no profile): ${data.from}") continue // profile not set yet } - aapsLogger.debug(LTag.AUTOSENS, "Processing calculation thread: " + from + " (" + i + "/" + bucketedData.size + ")") + aapsLogger.debug(LTag.AUTOSENS, "Processing calculation thread: ${data.from} ($i/${bucketedData.size})") val sens = profile.getIsfMgdl(bgTime) - val autosensData = AutosensData(injector) + val autosensData = AutosensData(data.injector) autosensData.time = bgTime if (previous != null) autosensData.activeCarbsList = previous.cloneCarbsList() else autosensData.activeCarbsList = ArrayList() @@ -136,7 +141,7 @@ class IobCobOref1Thread internal constructor( autosensData.bg = bg delta = bg - bucketedData[i + 1].value avgDelta = (bg - bucketedData[i + 3].value) / 3 - val iob = iobCobCalculatorPlugin.calculateFromTreatmentsAndTemps(bgTime, profile) + val iob = data.iobCobCalculator.calculateFromTreatmentsAndTemps(bgTime, profile) val bgi = -iob.activity * sens * 5 val deviation = delta - bgi val avgDeviation = ((avgDelta - bgi) * 1000).roundToLong() / 1000.0 @@ -312,22 +317,27 @@ class IobCobOref1Thread internal constructor( if (min in 0..4 && hours % 2 == 0) autosensData.extraDeviation.add(0.0) previous = autosensData if (bgTime < dateUtil.now()) autosensDataTable.put(bgTime, autosensData) - aapsLogger.debug(LTag.AUTOSENS, "Running detectSensitivity from: " + dateUtil.dateAndTimeString(oldestTimeWithData) + " to: " + dateUtil.dateAndTimeString(bgTime) + " lastDataTime:" + ads.lastDataTime(dateUtil)) + aapsLogger.debug( + LTag.AUTOSENS, + "Running detectSensitivity from: " + dateUtil.dateAndTimeString(oldestTimeWithData) + " to: " + dateUtil.dateAndTimeString(bgTime) + " lastDataTime:" + ads.lastDataTime( + dateUtil + ) + ) val sensitivity = activePlugin.activeSensitivity.detectSensitivity(ads, oldestTimeWithData, bgTime) aapsLogger.debug(LTag.AUTOSENS, "Sensitivity result: $sensitivity") autosensData.autosensResult = sensitivity aapsLogger.debug(LTag.AUTOSENS, autosensData.toString()) } - iobCobCalculatorPlugin.ads = ads + data.iobCobCalculator.ads = ads Thread { SystemClock.sleep(1000) - rxBus.send(EventAutosensCalculationFinished(cause)) + rxBus.send(EventAutosensCalculationFinished(data.cause)) }.start() } finally { - mWakeLock?.release() - rxBus.send(EventIobCalculationProgress("", cause)) - aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread ended: $from") + rxBus.send(EventIobCalculationProgress(CalculationWorkflow.ProgressData.IOB_COB_OREF, 100, data.cause)) + aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread ended: ${data.from}") profiler.log(LTag.AUTOSENS, "IobCobOref1Thread", start) } + return Result.success() } } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.kt b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOrefWorker.kt similarity index 81% rename from app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.kt rename to app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOrefWorker.kt index dd0309d03b..832de3fe58 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOrefWorker.kt @@ -1,8 +1,10 @@ package info.nightscout.androidaps.plugins.iob.iobCobCalculator import android.content.Context -import android.os.PowerManager import android.os.SystemClock +import androidx.work.Worker +import androidx.work.WorkerParameters +import androidx.work.workDataOf import dagger.android.HasAndroidInjector import info.nightscout.androidaps.Constants import info.nightscout.androidaps.R @@ -10,9 +12,8 @@ import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.events.Event import info.nightscout.androidaps.events.EventAutosensCalculationFinished import info.nightscout.androidaps.interfaces.ActivePlugin +import info.nightscout.androidaps.interfaces.IobCobCalculator import info.nightscout.androidaps.interfaces.ProfileFunction -import info.nightscout.shared.logging.AAPSLogger -import info.nightscout.shared.logging.LTag import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults import info.nightscout.androidaps.plugins.bus.RxBus import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification @@ -21,6 +22,7 @@ import info.nightscout.androidaps.plugins.iob.iobCobCalculator.data.AutosensData import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin +import info.nightscout.androidaps.receivers.DataWorker import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.DecimalFormatter import info.nightscout.androidaps.utils.FabricPrivacy @@ -28,23 +30,20 @@ import info.nightscout.androidaps.utils.Profiler import info.nightscout.androidaps.utils.T import info.nightscout.androidaps.utils.buildHelper.BuildHelper import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.androidaps.workflow.CalculationWorkflow +import info.nightscout.shared.logging.AAPSLogger +import info.nightscout.shared.logging.LTag import info.nightscout.shared.sharedPreferences.SP -import java.util.* import javax.inject.Inject import kotlin.math.abs import kotlin.math.max import kotlin.math.min import kotlin.math.roundToLong -class IobCobThread @Inject internal constructor( - private val injector: HasAndroidInjector, - private val iobCobCalculatorPlugin: IobCobCalculatorPlugin, // cannot be injected : HistoryBrowser uses different instance - private val from: String, - private val end: Long, - private val bgDataReload: Boolean, - private val limitDataToOldestAvailable: Boolean, - private val cause: Event? -) : Thread() { +class IobCobOrefWorker @Inject internal constructor( + context: Context, + params: WorkerParameters +) : Worker(context, params) { @Inject lateinit var aapsLogger: AAPSLogger @Inject lateinit var sp: SP @@ -60,48 +59,51 @@ class IobCobThread @Inject internal constructor( @Inject lateinit var fabricPrivacy: FabricPrivacy @Inject lateinit var dateUtil: DateUtil @Inject lateinit var repository: AppRepository - - private var mWakeLock: PowerManager.WakeLock? = null + @Inject lateinit var dataWorker: DataWorker init { - injector.androidInjector().inject(this) - mWakeLock = (context.applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, rh.gs(R.string.app_name) + ":iobCobThread") + (context.applicationContext as HasAndroidInjector).androidInjector().inject(this) } - override fun run() { + class IobCobOrefWorkerData( + val injector: HasAndroidInjector, + val iobCobCalculatorPlugin: IobCobCalculator, // cannot be injected : HistoryBrowser uses different instance + val from: String, + val end: Long, + val limitDataToOldestAvailable: Boolean, + val cause: Event? + ) + + override fun doWork(): Result { + val data = dataWorker.pickupObject(inputData.getLong(DataWorker.STORE_KEY, -1)) as IobCobOrefWorkerData? + ?: return Result.failure(workDataOf("Error" to "missing input data")) + val start = dateUtil.now() - mWakeLock?.acquire(T.mins(10).msecs()) try { - aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread started: $from") + aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread started: ${data.from}") if (!profileFunction.isProfileValid("IobCobThread")) { - aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (No profile): $from") - return // app still initializing + aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (No profile): ${data.from}") + return Result.failure(workDataOf("Error" to "app still initializing")) } //log.debug("Locking calculateSensitivityData"); - val oldestTimeWithData = iobCobCalculatorPlugin.calculateDetectionStart(end, limitDataToOldestAvailable) - if (bgDataReload) { - iobCobCalculatorPlugin.ads.loadBgData(end, repository, aapsLogger, dateUtil, rxBus) - iobCobCalculatorPlugin.clearCache() - } + val oldestTimeWithData = data.iobCobCalculatorPlugin.calculateDetectionStart(data.end, data.limitDataToOldestAvailable) // work on local copy and set back when finished - val ads = iobCobCalculatorPlugin.ads.clone() + val ads = data.iobCobCalculatorPlugin.ads.clone() val bucketedData = ads.bucketedData val autosensDataTable = ads.autosensDataTable if (bucketedData == null || bucketedData.size < 3) { - aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (No bucketed data available): $from") - return + aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (No bucketed data available): ${data.from}") + return Result.failure(workDataOf("Error" to "Aborting calculation thread (No bucketed data available): ${data.from}")) } val prevDataTime = ads.roundUpTime(bucketedData[bucketedData.size - 3].timestamp) aapsLogger.debug(LTag.AUTOSENS, "Prev data time: " + dateUtil.dateAndTimeString(prevDataTime)) var previous = autosensDataTable[prevDataTime] // start from oldest to be able sub cob for (i in bucketedData.size - 4 downTo 0) { - val progress = i.toString() + if (buildHelper.isDev()) " ($from)" else "" - rxBus.send(EventIobCalculationProgress(progress, cause)) - if (iobCobCalculatorPlugin.stopCalculationTrigger) { - iobCobCalculatorPlugin.stopCalculationTrigger = false - aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (trigger): $from") - return + rxBus.send(EventIobCalculationProgress(CalculationWorkflow.ProgressData.IOB_COB_OREF, 100 - (100.0 * i / bucketedData.size).toInt(), data.cause)) + if (isStopped) { + aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (trigger): ${data.from}") + return Result.failure(workDataOf("Error" to "Aborting calculation thread (trigger): ${data.from}")) } // check if data already exists var bgTime = bucketedData[i].timestamp @@ -114,12 +116,12 @@ class IobCobThread @Inject internal constructor( } val profile = profileFunction.getProfile(bgTime) if (profile == null) { - aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (no profile): $from") + aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (no profile): ${data.from}") continue // profile not set yet } - aapsLogger.debug(LTag.AUTOSENS, "Processing calculation thread: " + from + " (" + i + "/" + bucketedData.size + ")") + aapsLogger.debug(LTag.AUTOSENS, "Processing calculation thread: ${data.from} ($i/${bucketedData.size})") val sens = profile.getIsfMgdl(bgTime) - val autosensData = AutosensData(injector) + val autosensData = AutosensData(data.injector) autosensData.time = bgTime if (previous != null) autosensData.activeCarbsList = previous.cloneCarbsList() else autosensData.activeCarbsList = ArrayList() @@ -134,7 +136,7 @@ class IobCobThread @Inject internal constructor( autosensData.bg = bg delta = bg - bucketedData[i + 1].value avgDelta = (bg - bucketedData[i + 3].value) / 3 - val iob = iobCobCalculatorPlugin.calculateFromTreatmentsAndTemps(bgTime, profile) + val iob = data.iobCobCalculatorPlugin.calculateFromTreatmentsAndTemps(bgTime, profile) val bgi = -iob.activity * sens * 5 val deviation = delta - bgi val avgDeviation = ((avgDelta - bgi) * 1000).roundToLong() / 1000.0 @@ -158,7 +160,7 @@ class IobCobThread @Inject internal constructor( if (ad == null) { aapsLogger.debug(LTag.AUTOSENS, autosensDataTable.toString()) aapsLogger.debug(LTag.AUTOSENS, bucketedData.toString()) - //aapsLogger.debug(LTag.AUTOSENS, iobCobCalculatorPlugin.getBgReadingsDataTable().toString()) + //aapsLogger.debug(LTag.AUTOSENS, data.iobCobCalculatorPlugin.getBgReadingsDataTable().toString()) val notification = Notification(Notification.SEND_LOGFILES, rh.gs(R.string.sendlogfiles), Notification.LOW) rxBus.send(EventNewNotification(notification)) sp.putBoolean("log_AUTOSENS", true) @@ -181,7 +183,7 @@ class IobCobThread @Inject internal constructor( fabricPrivacy.logException(e) aapsLogger.debug(autosensDataTable.toString()) aapsLogger.debug(bucketedData.toString()) - //aapsLogger.debug(iobCobCalculatorPlugin.getBgReadingsDataTable().toString()) + //aapsLogger.debug(data.iobCobCalculatorPlugin.getBgReadingsDataTable().toString()) val notification = Notification(Notification.SEND_LOGFILES, rh.gs(R.string.sendlogfiles), Notification.LOW) rxBus.send(EventNewNotification(notification)) sp.putBoolean("log_AUTOSENS", true) @@ -258,22 +260,27 @@ class IobCobThread @Inject internal constructor( } previous = autosensData if (bgTime < dateUtil.now()) autosensDataTable.put(bgTime, autosensData) - aapsLogger.debug(LTag.AUTOSENS, "Running detectSensitivity from: " + dateUtil.dateAndTimeString(oldestTimeWithData) + " to: " + dateUtil.dateAndTimeString(bgTime) + " lastDataTime:" + ads.lastDataTime(dateUtil)) + aapsLogger.debug( + LTag.AUTOSENS, + "Running detectSensitivity from: " + dateUtil.dateAndTimeString(oldestTimeWithData) + " to: " + dateUtil.dateAndTimeString(bgTime) + " lastDataTime:" + ads.lastDataTime( + dateUtil + ) + ) val sensitivity = activePlugin.activeSensitivity.detectSensitivity(ads, oldestTimeWithData, bgTime) aapsLogger.debug(LTag.AUTOSENS, "Sensitivity result: $sensitivity") autosensData.autosensResult = sensitivity aapsLogger.debug(LTag.AUTOSENS, autosensData.toString()) } - iobCobCalculatorPlugin.ads = ads + data.iobCobCalculatorPlugin.ads = ads Thread { SystemClock.sleep(1000) - rxBus.send(EventAutosensCalculationFinished(cause)) + rxBus.send(EventAutosensCalculationFinished(data.cause)) }.start() } finally { - mWakeLock?.release() - rxBus.send(EventIobCalculationProgress("", cause)) - aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread ended: $from") + rxBus.send(EventIobCalculationProgress(CalculationWorkflow.ProgressData.IOB_COB_OREF, 100, data.cause)) + aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread ended: ${data.from}") profiler.log(LTag.AUTOSENS, "IobCobThread", start) } + return Result.success() } } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/events/EventIobCalculationProgress.kt b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/events/EventIobCalculationProgress.kt index a5326e4ad3..3da3b8e3bd 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/events/EventIobCalculationProgress.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/events/EventIobCalculationProgress.kt @@ -1,5 +1,6 @@ package info.nightscout.androidaps.plugins.iob.iobCobCalculator.events import info.nightscout.androidaps.events.Event +import info.nightscout.androidaps.workflow.CalculationWorkflow -class EventIobCalculationProgress(val progress: String, val cause: Event?) : Event() \ No newline at end of file +class EventIobCalculationProgress(val pass: CalculationWorkflow.ProgressData, val progressPct: Int, val cause: Event?) : Event() \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/profile/local/LocalProfileFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/profile/local/LocalProfileFragment.kt index 3a81af67d3..63312f8df9 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/profile/local/LocalProfileFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/profile/local/LocalProfileFragment.kt @@ -9,15 +9,18 @@ import android.view.View import android.view.ViewGroup import android.widget.AdapterView import android.widget.ArrayAdapter +import com.google.android.material.tabs.TabLayout import dagger.android.support.DaggerFragment import info.nightscout.androidaps.Constants import info.nightscout.androidaps.R +import info.nightscout.androidaps.activities.SingleFragmentActivity import info.nightscout.androidaps.data.ProfileSealed import info.nightscout.androidaps.database.entities.UserEntry.Action import info.nightscout.androidaps.database.entities.UserEntry.Sources import info.nightscout.androidaps.database.entities.ValueWithUnit import info.nightscout.androidaps.databinding.LocalprofileFragmentBinding import info.nightscout.androidaps.dialogs.ProfileSwitchDialog +import info.nightscout.androidaps.extensions.toVisibility import info.nightscout.androidaps.interfaces.ActivePlugin import info.nightscout.androidaps.interfaces.GlucoseUnit import info.nightscout.androidaps.interfaces.Profile @@ -29,6 +32,7 @@ import info.nightscout.androidaps.utils.DecimalFormatter import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.HardLimits import info.nightscout.androidaps.utils.alertDialogs.OKDialog +import info.nightscout.androidaps.utils.protection.ProtectionCheck import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.rx.AapsSchedulers import info.nightscout.androidaps.utils.ui.TimeListEdit @@ -49,14 +53,15 @@ class LocalProfileFragment : DaggerFragment() { @Inject lateinit var fabricPrivacy: FabricPrivacy @Inject lateinit var localProfilePlugin: LocalProfilePlugin @Inject lateinit var hardLimits: HardLimits + @Inject lateinit var protectionCheck: ProtectionCheck @Inject lateinit var dateUtil: DateUtil @Inject lateinit var aapsSchedulers: AapsSchedulers @Inject lateinit var uel: UserEntryLogger private var disposable: CompositeDisposable = CompositeDisposable() - + private var inMenu = false + private var queryingProtection = false private var basalView: TimeListEdit? = null -// private var spinner: SpinnerHelper? = null private val save = Runnable { doEdit() @@ -88,8 +93,7 @@ class LocalProfileFragment : DaggerFragment() { private var _binding: LocalprofileFragmentBinding? = null - // This property is only valid between onCreateView and - // onDestroyView. + // This property is only valid between onCreateView and onDestroyView. private val binding get() = _binding!! override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { @@ -99,31 +103,20 @@ class LocalProfileFragment : DaggerFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - // activate DIA tab - processVisibilityOnClick(binding.diaTab) - binding.diaPlaceholder.visibility = View.VISIBLE - // setup listeners - binding.diaTab.setOnClickListener { - processVisibilityOnClick(it) - binding.diaPlaceholder.visibility = View.VISIBLE - } - binding.icTab.setOnClickListener { - processVisibilityOnClick(it) - binding.ic.visibility = View.VISIBLE - } - binding.isfTab.setOnClickListener { - processVisibilityOnClick(it) - binding.isf.visibility = View.VISIBLE - } - binding.basalTab.setOnClickListener { - processVisibilityOnClick(it) - binding.basal.visibility = View.VISIBLE - } - binding.targetTab.setOnClickListener { - processVisibilityOnClick(it) - binding.target.visibility = View.VISIBLE - } - binding.dia.editText?.id?.let { binding.diaLabel.labelFor = it } + val parentClass = this.activity?.let { it::class.java } + inMenu = parentClass == SingleFragmentActivity::class.java + updateProtectedUi() + processVisibility(0) + binding.tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { + override fun onTabSelected(tab: TabLayout.Tab) { + processVisibility(tab.position) + } + + override fun onTabUnselected(tab: TabLayout.Tab) {} + override fun onTabReselected(tab: TabLayout.Tab) {} + }) + binding.diaLabel.labelFor = binding.dia.editTextId + binding.unlock.setOnClickListener { queryProtection() } } fun build() { @@ -222,7 +215,6 @@ class LocalProfileFragment : DaggerFragment() { ) } - // Spinner context?.let { context -> val profileList: ArrayList = localProfilePlugin.profile?.getProfileList() ?: ArrayList() binding.profileList.setAdapter(ArrayAdapter(context, R.layout.spinner_centered, profileList)) @@ -301,7 +293,7 @@ class LocalProfileFragment : DaggerFragment() { binding.profileswitch.setOnClickListener { ProfileSwitchDialog() - .also { it.arguments = Bundle().also { bundle -> bundle.putInt("profileIndex", localProfilePlugin.currentProfileIndex) } } + .also { it.arguments = Bundle().also { bundle -> bundle.putString("profileName", localProfilePlugin.currentProfile()?.name) } } .show(childFragmentManager, "ProfileSwitchDialog") } @@ -329,6 +321,7 @@ class LocalProfileFragment : DaggerFragment() { @Synchronized override fun onResume() { super.onResume() + if (inMenu) queryProtection() else updateProtectedUi() disposable += rxBus .toObservable(EventLocalProfileChanged::class.java) .observeOn(aapsSchedulers.main) @@ -366,7 +359,7 @@ class LocalProfileFragment : DaggerFragment() { val isValid = localProfilePlugin.isValidEditState(activity) val isEdited = localProfilePlugin.isEdited if (isValid) { - this.view?.setBackgroundColor(rh.gc(R.color.ok_background)) + this.view?.setBackgroundColor(rh.gac(context, R.attr.okBackgroundColor)) binding.profileList.isEnabled = true if (isEdited) { @@ -378,7 +371,7 @@ class LocalProfileFragment : DaggerFragment() { binding.save.visibility = View.GONE } } else { - this.view?.setBackgroundColor(rh.gc(R.color.error_background)) + this.view?.setBackgroundColor(rh.gac(context, R.attr.errorBackgroundColor)) binding.profileList.isEnabled = false binding.profileswitch.visibility = View.GONE binding.save.visibility = View.GONE //don't save an invalid profile @@ -392,17 +385,28 @@ class LocalProfileFragment : DaggerFragment() { } } - private fun processVisibilityOnClick(selected: View) { - binding.diaTab.setBackgroundColor(rh.gc(R.color.defaultbackground)) - binding.icTab.setBackgroundColor(rh.gc(R.color.defaultbackground)) - binding.isfTab.setBackgroundColor(rh.gc(R.color.defaultbackground)) - binding.basalTab.setBackgroundColor(rh.gc(R.color.defaultbackground)) - binding.targetTab.setBackgroundColor(rh.gc(R.color.defaultbackground)) - selected.setBackgroundColor(rh.gc(R.color.tabBgColorSelected)) - binding.diaPlaceholder.visibility = View.GONE - binding.ic.visibility = View.GONE - binding.isf.visibility = View.GONE - binding.basal.visibility = View.GONE - binding.target.visibility = View.GONE + private fun processVisibility(position: Int) { + binding.diaPlaceholder.visibility = (position == 0).toVisibility() + binding.ic.visibility = (position == 1).toVisibility() + binding.isf.visibility = (position == 2).toVisibility() + binding.basal.visibility = (position == 3).toVisibility() + binding.target.visibility = (position == 4).toVisibility() + } + + private fun updateProtectedUi() { + val isLocked = protectionCheck.isLocked(ProtectionCheck.Protection.PREFERENCES) + binding.mainLayout.visibility = isLocked.not().toVisibility() + binding.unlock.visibility = isLocked.toVisibility() + } + + private fun queryProtection() { + val isLocked = protectionCheck.isLocked(ProtectionCheck.Protection.PREFERENCES) + if (isLocked && !queryingProtection) { + activity?.let { activity -> + queryingProtection = true + val doUpdate = { activity.runOnUiThread { queryingProtection = false; updateProtectedUi() } } + protectionCheck.queryProtection(activity, ProtectionCheck.Protection.PREFERENCES, doUpdate, doUpdate, doUpdate) + } + } } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpFragment.kt index 1eb807dbfc..ea85e983b5 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpFragment.kt @@ -52,10 +52,6 @@ class VirtualPumpFragment : DaggerFragment() { return binding.root } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - } - @Synchronized override fun onResume() { super.onResume() diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityAAPSPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityAAPSPlugin.kt index 723905f31f..4902118b8d 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityAAPSPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityAAPSPlugin.kt @@ -137,6 +137,8 @@ class SensitivityAAPSPlugin @Inject constructor( return output } + override fun maxAbsorptionHours(): Double = sp.getDouble(R.string.key_absorption_maxtime, Constants.DEFAULT_MAX_ABSORPTION_TIME) + override val id: SensitivityType get() = SensitivityType.SENSITIVITY_AAPS diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityOref1Plugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityOref1Plugin.kt index 35230bb10b..df5dbe68f5 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityOref1Plugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityOref1Plugin.kt @@ -53,8 +53,6 @@ class SensitivityOref1Plugin @Inject constructor( ) { override fun detectSensitivity(ads: AutosensDataStore, fromTime: Long, toTime: Long): AutosensResult { - // todo this method is called from the IobCobCalculatorPlugin, which leads to a circular - // dependency, this should be avoided val profile = profileFunction.getProfile() if (profile == null) { aapsLogger.error("No profile") @@ -204,6 +202,8 @@ class SensitivityOref1Plugin @Inject constructor( return output } + override fun maxAbsorptionHours(): Double = sp.getDouble(R.string.key_absorption_cutoff, Constants.DEFAULT_MAX_ABSORPTION_TIME) + override fun configuration(): JSONObject { val c = JSONObject() try { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityWeightedAveragePlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityWeightedAveragePlugin.kt index 639cde7acc..868ac4ac7a 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityWeightedAveragePlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityWeightedAveragePlugin.kt @@ -157,6 +157,8 @@ class SensitivityWeightedAveragePlugin @Inject constructor( return output } + override fun maxAbsorptionHours(): Double = sp.getDouble(R.string.key_absorption_maxtime, Constants.DEFAULT_MAX_ABSORPTION_TIME) + override val id: SensitivityType get() = SensitivityType.SENSITIVITY_WEIGHTED diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/AidexPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/source/AidexPlugin.kt index 05e795cfcb..d4cb82ce83 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/source/AidexPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/AidexPlugin.kt @@ -96,7 +96,7 @@ class AidexPlugin @Inject constructor( ) repository.runTransactionForResult(CgmSourceTransaction(glucoseValues, emptyList(), null)) .doOnError { - aapsLogger.error(LTag.DATABASE, "Error while saving values from Xdrip", it) + aapsLogger.error(LTag.DATABASE, "Error while saving values from Aidex", it) ret = Result.failure(workDataOf("Error" to it.toString())) } .blockingGet() diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/DexcomPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/source/DexcomPlugin.kt index 5bef08a3f8..0255209c5e 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/source/DexcomPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/DexcomPlugin.kt @@ -149,11 +149,15 @@ class DexcomPlugin @Inject constructor( sourceSensor = sourceSensor ) } - val sensorStartTime = if (sp.getBoolean(R.string.key_dexcom_lognssensorchange, false) && bundle.containsKey("sensorInsertionTime")) { + var sensorStartTime = if (sp.getBoolean(R.string.key_dexcom_lognssensorchange, false) && bundle.containsKey("sensorInsertionTime")) { bundle.getLong("sensorInsertionTime", 0) * 1000 } else { null } + // check start time validity + sensorStartTime?.let { + if (abs(it - now) > T.months(1).msecs() || it > now) sensorStartTime = null + } repository.runTransactionForResult(CgmSourceTransaction(glucoseValues, calibrations, sensorStartTime)) .doOnError { aapsLogger.error(LTag.DATABASE, "Error while saving values from Dexcom App", it) diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveReceiver.kt b/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveReceiver.kt deleted file mode 100644 index e20ebcb414..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveReceiver.kt +++ /dev/null @@ -1,198 +0,0 @@ -package info.nightscout.androidaps.receivers - -import android.app.AlarmManager -import android.app.PendingIntent -import android.app.PendingIntent.CanceledException -import android.app.PendingIntent.FLAG_IMMUTABLE -import android.content.Context -import android.content.Intent -import android.os.SystemClock -import androidx.work.* -import com.google.common.util.concurrent.ListenableFuture -import dagger.android.DaggerBroadcastReceiver -import dagger.android.HasAndroidInjector -import info.nightscout.androidaps.BuildConfig -import info.nightscout.androidaps.R -import info.nightscout.androidaps.data.ProfileSealed -import info.nightscout.androidaps.database.AppRepository -import info.nightscout.androidaps.events.EventProfileSwitchChanged -import info.nightscout.androidaps.extensions.buildDeviceStatus -import info.nightscout.androidaps.interfaces.* -import info.nightscout.shared.logging.AAPSLogger -import info.nightscout.shared.logging.LTag -import info.nightscout.androidaps.plugins.bus.RxBus -import info.nightscout.androidaps.plugins.configBuilder.RunningConfiguration -import info.nightscout.androidaps.plugins.general.maintenance.MaintenancePlugin -import info.nightscout.androidaps.queue.commands.Command -import info.nightscout.androidaps.utils.DateUtil -import info.nightscout.androidaps.utils.FabricPrivacy -import info.nightscout.androidaps.utils.LocalAlertUtils -import info.nightscout.androidaps.utils.T -import info.nightscout.androidaps.utils.resources.ResourceHelper -import javax.inject.Inject -import kotlin.math.abs - -class KeepAliveReceiver : DaggerBroadcastReceiver() { - - @Inject lateinit var aapsLogger: AAPSLogger - - companion object { - - private val KEEP_ALIVE_MILLISECONDS = T.mins(5).msecs() - } - - override fun onReceive(context: Context, intent: Intent) { - super.onReceive(context, intent) - aapsLogger.debug(LTag.CORE, "KeepAlive received") - - WorkManager.getInstance(context) - .enqueue(OneTimeWorkRequest.Builder(KeepAliveWorker::class.java).build()) - } - - class KeepAliveWorker( - private val context: Context, - params: WorkerParameters - ) : Worker(context, params) { - - @Inject lateinit var aapsLogger: AAPSLogger - @Inject lateinit var localAlertUtils: LocalAlertUtils - @Inject lateinit var repository: AppRepository - @Inject lateinit var config: Config - @Inject lateinit var iobCobCalculator: IobCobCalculator - @Inject lateinit var loop: Loop - @Inject lateinit var dateUtil: DateUtil - @Inject lateinit var activePlugin: ActivePlugin - @Inject lateinit var profileFunction: ProfileFunction - @Inject lateinit var runningConfiguration: RunningConfiguration - @Inject lateinit var receiverStatusStore: ReceiverStatusStore - @Inject lateinit var rxBus: RxBus - @Inject lateinit var commandQueue: CommandQueue - @Inject lateinit var fabricPrivacy: FabricPrivacy - @Inject lateinit var maintenancePlugin: MaintenancePlugin - @Inject lateinit var rh: ResourceHelper - - init { - (context.applicationContext as HasAndroidInjector).androidInjector().inject(this) - } - - companion object { - - private val STATUS_UPDATE_FREQUENCY = T.mins(15).msecs() - private const val IOB_UPDATE_FREQUENCY_IN_MINUTES = 5L - - private var lastReadStatus: Long = 0 - private var lastRun: Long = 0 - private var lastIobUpload: Long = 0 - - } - - override fun doWork(): Result { - localAlertUtils.shortenSnoozeInterval() - localAlertUtils.checkStaleBGAlert() - checkPump() - checkAPS() - maintenancePlugin.deleteLogs(30) - workerDbStatus() - - return Result.success() - } - - // When Worker DB grows too much, work operations become slow - // Library is cleaning DB every 7 days which may not be sufficient for NSClient full sync - private fun workerDbStatus() { - val workQuery = WorkQuery.Builder - .fromStates(listOf(WorkInfo.State.FAILED, WorkInfo.State.SUCCEEDED)) - .build() - - val workInfo: ListenableFuture> = WorkManager.getInstance(context).getWorkInfos(workQuery) - aapsLogger.debug(LTag.CORE, "WorkManager size is ${workInfo.get().size}") - if (workInfo.get().size > 1000) { - WorkManager.getInstance(context).pruneWork() - aapsLogger.debug(LTag.CORE, "WorkManager pruning ....") - } - } - - // Usually deviceStatus is uploaded through LoopPlugin after every loop cycle. - // if there is no BG available, we have to upload anyway to have correct - // IOB displayed in NS - private fun checkAPS() { - var shouldUploadStatus = false - if (config.NSCLIENT) return - if (config.PUMPCONTROL) shouldUploadStatus = true - else if (!(loop as PluginBase).isEnabled() || iobCobCalculator.ads.actualBg() == null) - shouldUploadStatus = true - else if (dateUtil.isOlderThan(activePlugin.activeAPS.lastAPSRun, 5)) shouldUploadStatus = true - if (dateUtil.isOlderThan(lastIobUpload, IOB_UPDATE_FREQUENCY_IN_MINUTES) && shouldUploadStatus) { - lastIobUpload = dateUtil.now() - buildDeviceStatus(dateUtil, loop, iobCobCalculator, profileFunction, - activePlugin.activePump, receiverStatusStore, runningConfiguration, - BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION)?.also { - repository.insert(it) - } - } - } - - private fun checkPump() { - val pump = activePlugin.activePump - val ps = profileFunction.getRequestedProfile() ?: return - val requestedProfile = ProfileSealed.PS(ps) - val runningProfile = profileFunction.getProfile() - val lastConnection = pump.lastDataTime() - val isStatusOutdated = lastConnection + STATUS_UPDATE_FREQUENCY < System.currentTimeMillis() - val isBasalOutdated = abs(requestedProfile.getBasal() - pump.baseBasalRate) > pump.pumpDescription.basalStep - aapsLogger.debug(LTag.CORE, "Last connection: " + dateUtil.dateAndTimeString(lastConnection)) - // sometimes keep alive broadcast stops - // as as workaround test if readStatus was requested before an alarm is generated - if (lastReadStatus != 0L && lastReadStatus > System.currentTimeMillis() - T.mins(5).msecs()) { - localAlertUtils.checkPumpUnreachableAlarm(lastConnection, isStatusOutdated, loop.isDisconnected) - } - if (loop.isDisconnected) { - // do nothing if pump is disconnected - } else if (runningProfile == null || ((!pump.isThisProfileSet(requestedProfile) || !requestedProfile.isEqual(runningProfile)) && !commandQueue.isRunning(Command.CommandType.BASAL_PROFILE))) { - rxBus.send(EventProfileSwitchChanged()) - } else if (isStatusOutdated && !pump.isBusy()) { - lastReadStatus = System.currentTimeMillis() - commandQueue.readStatus(rh.gs(R.string.keepalive_status_outdated), null) - } else if (isBasalOutdated && !pump.isBusy()) { - lastReadStatus = System.currentTimeMillis() - commandQueue.readStatus(rh.gs(R.string.keepalive_basal_outdated), null) - } - if (lastRun != 0L && System.currentTimeMillis() - lastRun > T.mins(10).msecs()) { - aapsLogger.error(LTag.CORE, "KeepAlive fail") - fabricPrivacy.logCustom("KeepAliveFail") - } - lastRun = System.currentTimeMillis() - } - } - - class KeepAliveManager @Inject constructor( - private val aapsLogger: AAPSLogger, - private val localAlertUtils: LocalAlertUtils - ) { - - //called by MainApp at first app start - fun setAlarm(context: Context) { - aapsLogger.debug(LTag.CORE, "KeepAlive scheduled") - SystemClock.sleep(5000) // wait for app initialization - localAlertUtils.shortenSnoozeInterval() - localAlertUtils.preSnoozeAlarms() - val am = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager - val i = Intent(context, KeepAliveReceiver::class.java) - val pi = PendingIntent.getBroadcast(context, 0, i, FLAG_IMMUTABLE) - try { - pi.send() - } catch (e: CanceledException) { - } - am.cancel(pi) - am.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), KEEP_ALIVE_MILLISECONDS, pi) - } - - fun cancelAlarm(context: Context) { - aapsLogger.debug(LTag.CORE, "KeepAlive canceled") - val intent = Intent(context, KeepAliveReceiver::class.java) - val sender = PendingIntent.getBroadcast(context, 0, intent, FLAG_IMMUTABLE) - val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager - alarmManager.cancel(sender) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveWorker.kt b/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveWorker.kt new file mode 100644 index 0000000000..3370908ea2 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveWorker.kt @@ -0,0 +1,169 @@ +package info.nightscout.androidaps.receivers + +import android.content.Context +import androidx.work.* +import com.google.common.util.concurrent.ListenableFuture +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.BuildConfig +import info.nightscout.androidaps.R +import info.nightscout.androidaps.data.ProfileSealed +import info.nightscout.androidaps.database.AppRepository +import info.nightscout.androidaps.events.EventProfileSwitchChanged +import info.nightscout.androidaps.extensions.buildDeviceStatus +import info.nightscout.androidaps.interfaces.* +import info.nightscout.androidaps.plugins.bus.RxBus +import info.nightscout.androidaps.plugins.configBuilder.RunningConfiguration +import info.nightscout.androidaps.plugins.general.maintenance.MaintenancePlugin +import info.nightscout.androidaps.queue.commands.Command +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.FabricPrivacy +import info.nightscout.androidaps.utils.LocalAlertUtils +import info.nightscout.androidaps.utils.T +import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.androidaps.widget.updateWidget +import info.nightscout.shared.logging.AAPSLogger +import info.nightscout.shared.logging.LTag +import java.util.concurrent.TimeUnit +import javax.inject.Inject +import kotlin.math.abs + +class KeepAliveWorker( + private val context: Context, + params: WorkerParameters +) : Worker(context, params) { + + @Inject lateinit var aapsLogger: AAPSLogger + @Inject lateinit var localAlertUtils: LocalAlertUtils + @Inject lateinit var repository: AppRepository + @Inject lateinit var config: Config + @Inject lateinit var iobCobCalculator: IobCobCalculator + @Inject lateinit var loop: Loop + @Inject lateinit var dateUtil: DateUtil + @Inject lateinit var activePlugin: ActivePlugin + @Inject lateinit var profileFunction: ProfileFunction + @Inject lateinit var runningConfiguration: RunningConfiguration + @Inject lateinit var receiverStatusStore: ReceiverStatusStore + @Inject lateinit var rxBus: RxBus + @Inject lateinit var commandQueue: CommandQueue + @Inject lateinit var fabricPrivacy: FabricPrivacy + @Inject lateinit var maintenancePlugin: MaintenancePlugin + @Inject lateinit var rh: ResourceHelper + + init { + (context.applicationContext as HasAndroidInjector).androidInjector().inject(this) + } + + companion object { + + private val STATUS_UPDATE_FREQUENCY = T.mins(15).msecs() + private const val IOB_UPDATE_FREQUENCY_IN_MINUTES = 5L + + private var lastReadStatus: Long = 0 + private var lastRun: Long = 0 + private var lastIobUpload: Long = 0 + + } + + override fun doWork(): Result { + aapsLogger.debug(LTag.CORE, "KeepAlive received from: " + inputData.getString("schedule")) + + // 15 min interval is WorkManager minimum so schedule another instances to have 5 min interval + if (inputData.getString("schedule") == "KeepAlive") { + WorkManager.getInstance(context).enqueueUniqueWork( + "KeepAlive_5", + ExistingWorkPolicy.REPLACE, + OneTimeWorkRequest.Builder(KeepAliveWorker::class.java) + .setInputData(Data.Builder().putString("schedule", "KeepAlive_5").build()) + .setInitialDelay(5, TimeUnit.MINUTES) + .build() + ) + WorkManager.getInstance(context).enqueueUniqueWork( + "KeepAlive_10", + ExistingWorkPolicy.REPLACE, + OneTimeWorkRequest.Builder(KeepAliveWorker::class.java) + .setInputData(Data.Builder().putString("schedule", "KeepAlive_10").build()) + .setInitialDelay(10, TimeUnit.MINUTES) + .build() + ) + } + + updateWidget(context) + localAlertUtils.shortenSnoozeInterval() + localAlertUtils.checkStaleBGAlert() + checkPump() + checkAPS() + maintenancePlugin.deleteLogs(30) + workerDbStatus() + + return Result.success() + } + + // When Worker DB grows too much, work operations become slow + // Library is cleaning DB every 7 days which may not be sufficient for NSClient full sync + private fun workerDbStatus() { + val workQuery = WorkQuery.Builder + .fromStates(listOf(WorkInfo.State.FAILED, WorkInfo.State.SUCCEEDED)) + .build() + + val workInfo: ListenableFuture> = WorkManager.getInstance(context).getWorkInfos(workQuery) + aapsLogger.debug(LTag.CORE, "WorkManager size is ${workInfo.get().size}") + if (workInfo.get().size > 1000) { + WorkManager.getInstance(context).pruneWork() + aapsLogger.debug(LTag.CORE, "WorkManager pruning ....") + } + } + + // Usually deviceStatus is uploaded through LoopPlugin after every loop cycle. + // if there is no BG available, we have to upload anyway to have correct + // IOB displayed in NS + private fun checkAPS() { + var shouldUploadStatus = false + if (config.NSCLIENT) return + if (config.PUMPCONTROL) shouldUploadStatus = true + else if (!(loop as PluginBase).isEnabled() || iobCobCalculator.ads.actualBg() == null) + shouldUploadStatus = true + else if (dateUtil.isOlderThan(activePlugin.activeAPS.lastAPSRun, 5)) shouldUploadStatus = true + if (dateUtil.isOlderThan(lastIobUpload, IOB_UPDATE_FREQUENCY_IN_MINUTES) && shouldUploadStatus) { + lastIobUpload = dateUtil.now() + buildDeviceStatus( + dateUtil, loop, iobCobCalculator, profileFunction, + activePlugin.activePump, receiverStatusStore, runningConfiguration, + BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION + )?.also { + repository.insert(it) + } + } + } + + private fun checkPump() { + val pump = activePlugin.activePump + val ps = profileFunction.getRequestedProfile() ?: return + val requestedProfile = ProfileSealed.PS(ps) + val runningProfile = profileFunction.getProfile() + val lastConnection = pump.lastDataTime() + val isStatusOutdated = lastConnection + STATUS_UPDATE_FREQUENCY < System.currentTimeMillis() + val isBasalOutdated = abs(requestedProfile.getBasal() - pump.baseBasalRate) > pump.pumpDescription.basalStep + aapsLogger.debug(LTag.CORE, "Last connection: " + dateUtil.dateAndTimeString(lastConnection)) + // sometimes keep alive broadcast stops + // as as workaround test if readStatus was requested before an alarm is generated + if (lastReadStatus != 0L && lastReadStatus > System.currentTimeMillis() - T.mins(5).msecs()) { + localAlertUtils.checkPumpUnreachableAlarm(lastConnection, isStatusOutdated, loop.isDisconnected) + } + if (loop.isDisconnected) { + // do nothing if pump is disconnected + } else if (runningProfile == null || ((!pump.isThisProfileSet(requestedProfile) || !requestedProfile.isEqual(runningProfile)) && !commandQueue.isRunning(Command.CommandType.BASAL_PROFILE))) { + rxBus.send(EventProfileSwitchChanged()) + } else if (isStatusOutdated && !pump.isBusy()) { + lastReadStatus = System.currentTimeMillis() + commandQueue.readStatus(rh.gs(R.string.keepalive_status_outdated), null) + } else if (isBasalOutdated && !pump.isBusy()) { + lastReadStatus = System.currentTimeMillis() + commandQueue.readStatus(rh.gs(R.string.keepalive_basal_outdated), null) + } + if (lastRun != 0L && System.currentTimeMillis() - lastRun > T.mins(10).msecs()) { + aapsLogger.error(LTag.CORE, "KeepAlive fail") + fabricPrivacy.logCustom("KeepAliveFail") + } + lastRun = System.currentTimeMillis() + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/setupwizard/SetupWizardActivity.kt b/app/src/main/java/info/nightscout/androidaps/setupwizard/SetupWizardActivity.kt index 090b9ae07d..a5d157707f 100644 --- a/app/src/main/java/info/nightscout/androidaps/setupwizard/SetupWizardActivity.kt +++ b/app/src/main/java/info/nightscout/androidaps/setupwizard/SetupWizardActivity.kt @@ -34,7 +34,6 @@ class SetupWizardActivity : NoSplashAppCompatActivity() { @Inject lateinit var injector: HasAndroidInjector @Inject lateinit var localProfilePlugin: LocalProfilePlugin @Inject lateinit var swDefinition: SWDefinition - @Inject lateinit var rxBus: RxBus @Inject lateinit var sp: SP @Inject lateinit var fabricPrivacy: FabricPrivacy @Inject lateinit var aapsSchedulers: AapsSchedulers diff --git a/app/src/main/java/info/nightscout/androidaps/skins/SkinButtonsOn.kt b/app/src/main/java/info/nightscout/androidaps/skins/SkinButtonsOn.kt index 8c3595d069..aa04e0268e 100644 --- a/app/src/main/java/info/nightscout/androidaps/skins/SkinButtonsOn.kt +++ b/app/src/main/java/info/nightscout/androidaps/skins/SkinButtonsOn.kt @@ -11,4 +11,4 @@ class SkinButtonsOn @Inject constructor(private val config: Config) : SkinInterf override val description: Int get() = R.string.buttonson_description override val mainGraphHeight: Int get() = 200 override val secondaryGraphHeight: Int get() = 100 -} \ No newline at end of file +} diff --git a/app/src/main/java/info/nightscout/androidaps/skins/SkinClassic.kt b/app/src/main/java/info/nightscout/androidaps/skins/SkinClassic.kt index a6ae3b51b9..2513066d60 100644 --- a/app/src/main/java/info/nightscout/androidaps/skins/SkinClassic.kt +++ b/app/src/main/java/info/nightscout/androidaps/skins/SkinClassic.kt @@ -5,6 +5,7 @@ import android.view.View import android.widget.LinearLayout import info.nightscout.androidaps.interfaces.Config import info.nightscout.androidaps.R +import info.nightscout.androidaps.databinding.OverviewFragmentBinding import javax.inject.Inject import javax.inject.Singleton @@ -15,8 +16,8 @@ class SkinClassic @Inject constructor(private val config: Config): SkinInterface override val mainGraphHeight: Int get() = 200 override val secondaryGraphHeight: Int get() = 100 - override fun preProcessLandscapeOverviewLayout(dm: DisplayMetrics, view: View, isLandscape: Boolean, isTablet: Boolean, isSmallHeight: Boolean) { - super.preProcessLandscapeOverviewLayout(dm, view, isLandscape, isTablet, isSmallHeight) - if (!config.NSCLIENT && (isSmallHeight || isLandscape)) moveButtonsLayout(view as LinearLayout) + override fun preProcessLandscapeOverviewLayout(dm: DisplayMetrics, binding: OverviewFragmentBinding, isLandscape: Boolean, isTablet: Boolean, isSmallHeight: Boolean) { + super.preProcessLandscapeOverviewLayout(dm, binding, isLandscape, isTablet, isSmallHeight) + if (!config.NSCLIENT && (isSmallHeight || isLandscape)) moveButtonsLayout(binding.root) } -} \ No newline at end of file +} diff --git a/app/src/main/java/info/nightscout/androidaps/skins/SkinInterface.kt b/app/src/main/java/info/nightscout/androidaps/skins/SkinInterface.kt index 85ab6791ac..3ef7501ae4 100644 --- a/app/src/main/java/info/nightscout/androidaps/skins/SkinInterface.kt +++ b/app/src/main/java/info/nightscout/androidaps/skins/SkinInterface.kt @@ -4,11 +4,11 @@ import android.util.DisplayMetrics import android.util.TypedValue.COMPLEX_UNIT_PX import android.view.View import android.widget.LinearLayout -import android.widget.TextView -import androidx.annotation.LayoutRes import androidx.annotation.StringRes import androidx.constraintlayout.widget.ConstraintLayout import info.nightscout.androidaps.R +import info.nightscout.androidaps.databinding.ActionsFragmentBinding +import info.nightscout.androidaps.databinding.OverviewFragmentBinding interface SkinInterface { @@ -17,19 +17,19 @@ interface SkinInterface { val mainGraphHeight: Int // in dp val secondaryGraphHeight: Int // in dp - @LayoutRes - fun actionsLayout(isLandscape: Boolean, isSmallWidth: Boolean): Int = R.layout.actions_fragment + fun preProcessLandscapeActionsLayout(dm: DisplayMetrics, binding: ActionsFragmentBinding) { + } - fun preProcessLandscapeOverviewLayout(dm: DisplayMetrics, view: View, isLandscape: Boolean, isTablet: Boolean, isSmallHeight: Boolean) { + fun preProcessLandscapeOverviewLayout(dm: DisplayMetrics, binding: OverviewFragmentBinding, isLandscape: Boolean, isTablet: Boolean, isSmallHeight: Boolean) { // pre-process landscape mode val screenWidth = dm.widthPixels val screenHeight = dm.heightPixels val landscape = screenHeight < screenWidth if (landscape) { - val iobLayout = view.findViewById(R.id.iob_layout) + val iobLayout = binding.infoLayout.iobLayout val iobLayoutParams = iobLayout.layoutParams as ConstraintLayout.LayoutParams - val timeLayout = view.findViewById(R.id.time_layout) + val timeLayout = binding.infoLayout.timeLayout iobLayoutParams.startToStart = ConstraintLayout.LayoutParams.UNSET iobLayoutParams.startToEnd = timeLayout.id iobLayoutParams.topToBottom = ConstraintLayout.LayoutParams.UNSET @@ -37,43 +37,36 @@ interface SkinInterface { val timeLayoutParams = timeLayout.layoutParams as ConstraintLayout.LayoutParams timeLayoutParams.endToEnd = ConstraintLayout.LayoutParams.UNSET timeLayoutParams.endToStart = iobLayout.id - val cobLayoutParams = view.findViewById(R.id.cob_layout).layoutParams as ConstraintLayout.LayoutParams + val cobLayoutParams = binding.infoLayout.cobLayout.layoutParams as ConstraintLayout.LayoutParams cobLayoutParams.topToTop = ConstraintLayout.LayoutParams.PARENT_ID - val basalLayoutParams = view.findViewById(R.id.basal_layout).layoutParams as ConstraintLayout.LayoutParams + val basalLayoutParams = binding.infoLayout.basalLayout.layoutParams as ConstraintLayout.LayoutParams basalLayoutParams.topToTop = ConstraintLayout.LayoutParams.PARENT_ID - val extendedLayoutParams = view.findViewById(R.id.extended_layout).layoutParams as ConstraintLayout.LayoutParams + val extendedLayoutParams = binding.infoLayout.extendedLayout.layoutParams as ConstraintLayout.LayoutParams extendedLayoutParams.topToTop = ConstraintLayout.LayoutParams.PARENT_ID - val asLayoutParams = view.findViewById(R.id.as_layout).layoutParams as ConstraintLayout.LayoutParams + val asLayoutParams = binding.infoLayout.asLayout.layoutParams as ConstraintLayout.LayoutParams asLayoutParams.topToTop = ConstraintLayout.LayoutParams.PARENT_ID if (isTablet) { - for (v in listOf( - view.findViewById(R.id.bg), - view.findViewById(R.id.time), - view.findViewById(R.id.time_ago_short), - view.findViewById(R.id.iob), - view.findViewById(R.id.cob), - view.findViewById(R.id.base_basal), - view.findViewById(R.id.extended_bolus), - view.findViewById(R.id.sensitivity) - )) v?.setTextSize(COMPLEX_UNIT_PX, v.textSize * 1.5f) - for (v in listOf( - view.findViewById(R.id.pump), - view.findViewById(R.id.openaps), - view.findViewById(R.id.uploader), - view.findViewById(R.id.cannula_age), - view.findViewById(R.id.insulin_age), - view.findViewById(R.id.reservoir_level), - view.findViewById(R.id.sensor_age), - view.findViewById(R.id.pb_age), - view.findViewById(R.id.battery_level) - )) v?.setTextSize(COMPLEX_UNIT_PX, v.textSize * 1.3f) - timeLayout?.orientation = LinearLayout.HORIZONTAL - view.findViewById(R.id.time_ago_short)?.setTextSize(COMPLEX_UNIT_PX, view.findViewById(R.id.time).textSize) + binding.infoLayout.apply { + val texts = listOf(bg, iob, cob, baseBasal, extendedBolus, sensitivity) + for (v in texts) v.setTextSize(COMPLEX_UNIT_PX, v.textSize * 1.5f) + val textsTime = listOf(time, timeAgoShort) + for (v in textsTime) v.setTextSize(COMPLEX_UNIT_PX, v.textSize * 2.25f) + } + binding.apply { + val texts = listOf(pump, openaps, uploader) + for (v in texts) v.setTextSize(COMPLEX_UNIT_PX, v.textSize * 1.3f) + } + binding.statusLightsLayout.apply { + val texts = listOf(cannulaAge, insulinAge, reservoirLevel, sensorAge, pbAge, batteryLevel) + for (v in texts) v.setTextSize(COMPLEX_UNIT_PX, v.textSize * 1.3f) + } + timeLayout.orientation = LinearLayout.HORIZONTAL + binding.infoLayout.timeAgoShort.setTextSize(COMPLEX_UNIT_PX, binding.infoLayout.time.textSize) - view.findViewById(R.id.delta_large)?.visibility = View.VISIBLE + binding.infoLayout.deltaLarge.visibility = View.VISIBLE } else { - view.findViewById(R.id.delta_large)?.visibility = View.GONE + binding.infoLayout.deltaLarge.visibility = View.GONE } } } @@ -84,4 +77,5 @@ interface SkinInterface { val innerLayout = root.findViewById(R.id.inner_layout) innerLayout.addView(buttonsLayout) } -} \ No newline at end of file + +} diff --git a/app/src/main/java/info/nightscout/androidaps/skins/SkinLargeDisplay.kt b/app/src/main/java/info/nightscout/androidaps/skins/SkinLargeDisplay.kt index 751e0b3ad0..eb64d5514b 100644 --- a/app/src/main/java/info/nightscout/androidaps/skins/SkinLargeDisplay.kt +++ b/app/src/main/java/info/nightscout/androidaps/skins/SkinLargeDisplay.kt @@ -1,10 +1,9 @@ package info.nightscout.androidaps.skins import android.util.DisplayMetrics -import android.view.View -import android.widget.LinearLayout -import info.nightscout.androidaps.interfaces.Config import info.nightscout.androidaps.R +import info.nightscout.androidaps.databinding.OverviewFragmentBinding +import info.nightscout.androidaps.interfaces.Config import javax.inject.Inject import javax.inject.Singleton @@ -15,8 +14,8 @@ class SkinLargeDisplay @Inject constructor(private val config: Config): SkinInte override val mainGraphHeight: Int get() = 400 override val secondaryGraphHeight: Int get() = 150 - override fun preProcessLandscapeOverviewLayout(dm: DisplayMetrics, view: View, isLandscape: Boolean, isTablet: Boolean, isSmallHeight: Boolean) { - super.preProcessLandscapeOverviewLayout(dm, view, isLandscape, isTablet, isSmallHeight) - if (!config.NSCLIENT && (isSmallHeight || isLandscape)) moveButtonsLayout(view as LinearLayout) + override fun preProcessLandscapeOverviewLayout(dm: DisplayMetrics, binding: OverviewFragmentBinding, isLandscape: Boolean, isTablet: Boolean, isSmallHeight: Boolean) { + super.preProcessLandscapeOverviewLayout(dm, binding, isLandscape, isTablet, isSmallHeight) + if (!config.NSCLIENT && (isSmallHeight || isLandscape)) moveButtonsLayout(binding.root) } -} \ No newline at end of file +} diff --git a/app/src/main/java/info/nightscout/androidaps/skins/SkinListPreference.kt b/app/src/main/java/info/nightscout/androidaps/skins/SkinListPreference.kt index 977f19c268..51b546ce71 100644 --- a/app/src/main/java/info/nightscout/androidaps/skins/SkinListPreference.kt +++ b/app/src/main/java/info/nightscout/androidaps/skins/SkinListPreference.kt @@ -26,4 +26,4 @@ class SkinListPreference(context: Context, attrs: AttributeSet?) entryValues = values.toTypedArray() setEntries(entries.toTypedArray()) } -} \ No newline at end of file +} diff --git a/app/src/main/java/info/nightscout/androidaps/skins/SkinLowRes.kt b/app/src/main/java/info/nightscout/androidaps/skins/SkinLowRes.kt index c4384db93b..45ba680275 100644 --- a/app/src/main/java/info/nightscout/androidaps/skins/SkinLowRes.kt +++ b/app/src/main/java/info/nightscout/androidaps/skins/SkinLowRes.kt @@ -1,10 +1,11 @@ package info.nightscout.androidaps.skins import android.util.DisplayMetrics -import android.view.View -import android.widget.LinearLayout +import android.view.View.GONE import info.nightscout.androidaps.interfaces.Config import info.nightscout.androidaps.R +import info.nightscout.androidaps.databinding.ActionsFragmentBinding +import info.nightscout.androidaps.databinding.OverviewFragmentBinding import javax.inject.Inject import javax.inject.Singleton @@ -15,13 +16,28 @@ class SkinLowRes @Inject constructor(private val config: Config) : SkinInterface override val mainGraphHeight: Int get() = 200 override val secondaryGraphHeight: Int get() = 100 - override fun actionsLayout(isLandscape: Boolean, isSmallWidth: Boolean): Int = - when { - isLandscape -> R.layout.actions_fragment - else -> R.layout.actions_fragment_lowres - } + override fun preProcessLandscapeActionsLayout(dm: DisplayMetrics, binding: ActionsFragmentBinding) { + val screenWidth = dm.widthPixels + val screenHeight = dm.heightPixels + val isLandscape = screenHeight < screenWidth - override fun preProcessLandscapeOverviewLayout(dm: DisplayMetrics, view: View, isLandscape: Boolean, isTablet: Boolean, isSmallHeight: Boolean) { - if (!config.NSCLIENT && isLandscape) moveButtonsLayout(view as LinearLayout) + if (!isLandscape) { + binding.status.apply { + sensorAgeLabel.visibility = GONE + sensorAgeLabel.visibility = GONE + sensorLevelLabel.visibility = GONE + insulinAgeLabel.visibility = GONE + insulinLevelLabel.visibility = GONE + cannulaAgeLabel.visibility = GONE + cannulaPlaceholder.visibility = GONE + pbAgeLabel.visibility = GONE + pbLevelLabel.visibility = GONE + } + } } + + override fun preProcessLandscapeOverviewLayout(dm: DisplayMetrics, binding: OverviewFragmentBinding, isLandscape: Boolean, isTablet: Boolean, isSmallHeight: Boolean) { + if (!config.NSCLIENT && isLandscape) moveButtonsLayout(binding.root) + } + } diff --git a/app/src/main/java/info/nightscout/androidaps/skins/SkinProvider.kt b/app/src/main/java/info/nightscout/androidaps/skins/SkinProvider.kt index a119f102a8..b9d51adcda 100644 --- a/app/src/main/java/info/nightscout/androidaps/skins/SkinProvider.kt +++ b/app/src/main/java/info/nightscout/androidaps/skins/SkinProvider.kt @@ -19,4 +19,4 @@ class SkinProvider @Inject constructor( val list: List get() = allSkins.toImmutableMap().toList().sortedBy { it.first }.map { it.second } -} \ No newline at end of file +} diff --git a/app/src/main/java/info/nightscout/androidaps/utils/ActivityMonitor.kt b/app/src/main/java/info/nightscout/androidaps/utils/ActivityMonitor.kt index 480bc30aa2..f1352441b4 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/ActivityMonitor.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/ActivityMonitor.kt @@ -2,14 +2,20 @@ package info.nightscout.androidaps.utils import android.app.Activity import android.app.Application +import android.content.Context +import android.graphics.Typeface import android.os.Bundle -import android.text.Spanned +import android.view.Gravity +import android.view.ViewGroup +import android.widget.TableLayout +import android.widget.TableRow +import android.widget.TextView import info.nightscout.androidaps.R +import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.shared.SafeParse import info.nightscout.shared.logging.AAPSLogger import info.nightscout.shared.logging.LTag -import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.shared.sharedPreferences.SP -import info.nightscout.shared.SafeParse import javax.inject.Inject import javax.inject.Singleton @@ -58,24 +64,47 @@ class ActivityMonitor @Inject constructor( override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { } - private fun toText(): String { - val keys: Map = sp.getAll() - var result = "" - for ((key, value) in keys) - if (key.startsWith("Monitor") && key.endsWith("total")) { - val v = if (value is Long) value else SafeParse.stringToLong(value as String) - val activity = key.split("_")[1].replace("Activity", "") - val duration = dateUtil.niceTimeScalar(v, rh) - val start = sp.getLong(key.replace("total", "start"), 0) - val days = T.msecs(dateUtil.now() - start).days() - result += rh.gs(R.string.activitymonitorformat, activity, duration, days) - } - return result - } + fun stats(context: Context): TableLayout = + TableLayout(context).also { layout -> + layout.layoutParams = TableLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f) + layout.addView( + TextView(context).apply { + text = rh.gs(R.string.activitymonitor) + setTypeface(typeface, Typeface.BOLD) + gravity = Gravity.CENTER_HORIZONTAL + setTextAppearance(android.R.style.TextAppearance_Material_Medium) + }) + layout.addView( + TableRow(context).also { row -> + val lp = TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT, TableRow.LayoutParams.WRAP_CONTENT).apply { weight = 1f } + row.layoutParams = TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.WRAP_CONTENT) + row.gravity = Gravity.CENTER_HORIZONTAL + row.addView(TextView(context).apply { layoutParams = lp.apply { column = 0 }; text = rh.gs(R.string.activity) }) + row.addView(TextView(context).apply { layoutParams = lp.apply { column = 1 }; text = rh.gs(R.string.duration) }) + row.addView(TextView(context).apply { layoutParams = lp.apply { column = 2 } }) + } + ) - fun stats(): Spanned { - return HtmlHelper.fromHtml("
" + rh.gs(R.string.activitymonitor) + ":
" + toText()) - } + val keys: Map = sp.getAll() + for ((key, value) in keys) + if (key.startsWith("Monitor") && key.endsWith("total")) { + val v = if (value is Long) value else SafeParse.stringToLong(value as String) + val activity = key.split("_")[1].replace("Activity", "") + val duration = dateUtil.niceTimeScalar(v, rh) + val start = sp.getLong(key.replace("total", "start"), 0) + val days = T.msecs(dateUtil.now() - start).days() + layout.addView( + TableRow(context).also { row -> + val lp = TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT, TableRow.LayoutParams.WRAP_CONTENT).apply { weight = 1f } + row.layoutParams = TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.WRAP_CONTENT) + row.gravity = Gravity.CENTER_HORIZONTAL + row.addView(TextView(context).apply { layoutParams = lp.apply { column = 0 }; text = activity }) + row.addView(TextView(context).apply { layoutParams = lp.apply { column = 1 }; text = duration }) + row.addView(TextView(context).apply { layoutParams = lp.apply { column = 2 }; text = rh.gs(R.string.in_days, days.toDouble()) }) + } + ) + } + } fun reset() { val keys: Map = sp.getAll() diff --git a/app/src/main/java/info/nightscout/androidaps/utils/AndroidPermission.kt b/app/src/main/java/info/nightscout/androidaps/utils/AndroidPermission.kt index 3ccd6d6900..6d32d15594 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/AndroidPermission.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/AndroidPermission.kt @@ -113,7 +113,7 @@ class AndroidPermission @Inject constructor( @Synchronized fun notifyForBatteryOptimizationPermission(activity: FragmentActivity) { if (permissionNotGranted(activity, Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)) { - val notification = NotificationWithAction(injector, Notification.PERMISSION_BATTERY, String.format(rh.gs(R.string.needwhitelisting), rh.gs(R.string.app_name)), Notification.URGENT) + val notification = NotificationWithAction(injector, Notification.PERMISSION_BATTERY, rh.gs(R.string.needwhitelisting, rh.gs(R.string.app_name)), Notification.URGENT) notification.action(R.string.request) { askForPermission(activity, arrayOf(Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)) } rxBus.send(EventNewNotification(notification)) } else rxBus.send(EventDismissNotification(Notification.PERMISSION_BATTERY)) diff --git a/app/src/main/java/info/nightscout/androidaps/utils/ProcessLifecycleListener.kt b/app/src/main/java/info/nightscout/androidaps/utils/ProcessLifecycleListener.kt new file mode 100644 index 0000000000..e1bc3a719b --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/utils/ProcessLifecycleListener.kt @@ -0,0 +1,13 @@ +package info.nightscout.androidaps.utils + +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import info.nightscout.androidaps.utils.protection.ProtectionCheck +import javax.inject.Inject + +class ProcessLifecycleListener @Inject constructor(private val protectionCheck: ProtectionCheck) : DefaultLifecycleObserver { + + override fun onPause(owner: LifecycleOwner) { + protectionCheck.resetAuthorization() + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/utils/stats/TIR.kt b/app/src/main/java/info/nightscout/androidaps/utils/stats/TIR.kt index 6b688c15cb..6f316e0ae8 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/stats/TIR.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/stats/TIR.kt @@ -1,11 +1,16 @@ package info.nightscout.androidaps.utils.stats +import android.annotation.SuppressLint +import android.content.Context +import android.view.Gravity +import android.widget.TableRow +import android.widget.TextView import info.nightscout.androidaps.R import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.resources.ResourceHelper -import kotlin.math.roundToInt class TIR(val date: Long, val lowThreshold: Double, val highThreshold: Double) { + internal var below = 0 internal var inRange = 0 internal var above = 0 @@ -17,11 +22,44 @@ class TIR(val date: Long, val lowThreshold: Double, val highThreshold: Double) { fun inRange() = run { inRange++; count++ } fun above() = run { above++; count++ } - private fun belowPct() = if (count > 0) (below.toDouble() / count * 100.0).roundToInt() else 0 - private fun inRangePct() = if (count > 0) 100 - belowPct() - abovePct() else 0 - private fun abovePct() = if (count > 0) (above.toDouble() / count * 100.0).roundToInt() else 0 + private fun belowPct() = if (count > 0) below.toDouble() / count * 100.0 else 0.0 + private fun inRangePct() = if (count > 0) 100 - belowPct() - abovePct() else 0.0 + private fun abovePct() = if (count > 0) above.toDouble() / count * 100.0 else 0.0 - fun toText(rh: ResourceHelper, dateUtil: DateUtil): String = rh.gs(R.string.tirformat, dateUtil.dateStringShort(date), belowPct(), inRangePct(), abovePct()) + companion object { - fun toText(rh: ResourceHelper, days: Int): String = rh.gs(R.string.tirformat, "%02d".format(days) + " " + rh.gs(R.string.days), belowPct(), inRangePct(), abovePct()) + fun toTableRowHeader(context: Context, rh: ResourceHelper): TableRow = + TableRow(context).also { header -> + val lp = TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT, TableRow.LayoutParams.WRAP_CONTENT) + header.layoutParams = TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.WRAP_CONTENT) + header.gravity = Gravity.CENTER_HORIZONTAL + header.addView(TextView(context).apply { layoutParams = lp.apply { column = 0; weight = 1f }; text = rh.gs(R.string.date) }) + header.addView(TextView(context).apply { layoutParams = lp.apply { column = 1; weight = 1f }; text = rh.gs(R.string.below) }) + header.addView(TextView(context).apply { layoutParams = lp.apply { column = 2; weight = 1f }; text = rh.gs(R.string.in_range) }) + header.addView(TextView(context).apply { layoutParams = lp.apply { column = 3; weight = 1f }; text = rh.gs(R.string.above) }) + } + } + + fun toTableRow(context: Context, rh: ResourceHelper, dateUtil: DateUtil): TableRow = + TableRow(context).also { row -> + val lp = TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT, TableRow.LayoutParams.WRAP_CONTENT).apply { weight = 1f } + row.layoutParams = TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.WRAP_CONTENT) + row.gravity = Gravity.CENTER_HORIZONTAL + row.addView(TextView(context).apply { layoutParams = lp.apply { column = 0 }; text = dateUtil.dateStringShort(date) }) + row.addView(TextView(context).apply { layoutParams = lp.apply { column = 1 }; text = rh.gs(R.string.formatPercent, belowPct()) }) + row.addView(TextView(context).apply { layoutParams = lp.apply { column = 2 }; text = rh.gs(R.string.formatPercent, inRangePct()) }) + row.addView(TextView(context).apply { layoutParams = lp.apply { column = 3 }; text = rh.gs(R.string.formatPercent, abovePct()) }) + } + + @SuppressLint("SetTextI18n") + fun toTableRow(context: Context, rh: ResourceHelper, days: Int): TableRow = + TableRow(context).also { row -> + val lp = TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT, TableRow.LayoutParams.WRAP_CONTENT).apply { weight = 1f } + row.layoutParams = TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.WRAP_CONTENT) + row.gravity = Gravity.CENTER_HORIZONTAL + row.addView(TextView(context).apply { layoutParams = lp.apply { column = 0 }; text = "%02d".format(days) + " " + rh.gs(R.string.days) }) + row.addView(TextView(context).apply { layoutParams = lp.apply { column = 1 }; text = rh.gs(R.string.formatPercent, belowPct()) }) + row.addView(TextView(context).apply { layoutParams = lp.apply { column = 2 }; text = rh.gs(R.string.formatPercent, inRangePct()) }) + row.addView(TextView(context).apply { layoutParams = lp.apply { column = 3 }; text = rh.gs(R.string.formatPercent, abovePct()) }) + } } diff --git a/app/src/main/java/info/nightscout/androidaps/utils/stats/TddCalculator.kt b/app/src/main/java/info/nightscout/androidaps/utils/stats/TddCalculator.kt index 349dd597c7..6334d5bb91 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/stats/TddCalculator.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/stats/TddCalculator.kt @@ -1,18 +1,25 @@ package info.nightscout.androidaps.utils.stats -import android.text.Spanned +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Typeface import android.util.LongSparseArray +import android.view.Gravity +import android.view.ViewGroup +import android.widget.TableLayout +import android.widget.TableRow +import android.widget.TextView import info.nightscout.androidaps.R import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.entities.Bolus import info.nightscout.androidaps.database.entities.TotalDailyDose import info.nightscout.androidaps.extensions.convertedToAbsolute -import info.nightscout.androidaps.extensions.toText +import info.nightscout.androidaps.extensions.toTableRow +import info.nightscout.androidaps.extensions.toTableRowHeader import info.nightscout.androidaps.interfaces.ActivePlugin import info.nightscout.androidaps.interfaces.IobCobCalculator import info.nightscout.androidaps.interfaces.ProfileFunction import info.nightscout.androidaps.utils.DateUtil -import info.nightscout.androidaps.utils.HtmlHelper import info.nightscout.androidaps.utils.MidnightTime import info.nightscout.androidaps.utils.T import info.nightscout.androidaps.utils.resources.ResourceHelper @@ -186,24 +193,31 @@ class TddCalculator @Inject constructor( return totalTdd } - fun stats(): Spanned { + @SuppressLint("SetTextI18n") + fun stats(context: Context): TableLayout { val tdds = calculate(7) val averageTdd = averageTDD(tdds) - return HtmlHelper.fromHtml( - if (averageTdd != null) "" + rh.gs(R.string.tdd) + ":
" + - toText(tdds, true) + - "" + rh.gs(R.string.average) + ":
" + - averageTdd.toText(rh, tdds.size(), true) - else "" - ) - } - - @Suppress("SameParameterValue") - private fun toText(tdds: LongSparseArray, includeCarbs: Boolean): String { - var t = "" - for (i in 0 until tdds.size()) { - t += "${tdds.valueAt(i).toText(rh, dateUtil, includeCarbs)}
" + val lp = TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT, TableRow.LayoutParams.WRAP_CONTENT) + return TableLayout(context).also { layout -> + layout.layoutParams = TableLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f) + layout.addView(TextView(context).apply { + text = rh.gs(R.string.tdd) + setTypeface(typeface, Typeface.BOLD) + gravity = Gravity.CENTER_HORIZONTAL + setTextAppearance(android.R.style.TextAppearance_Material_Medium) + }) + layout.addView(TotalDailyDose.toTableRowHeader(context, rh, includeCarbs = true)) + for (i in 0 until tdds.size()) layout.addView(tdds.valueAt(i).toTableRow(context, rh, dateUtil, includeCarbs = true)) + averageTdd?.let { averageTdd -> + layout.addView(TextView(context).apply { + layoutParams = lp + text = rh.gs(R.string.average) + setTypeface(typeface, Typeface.BOLD) + gravity = Gravity.CENTER_HORIZONTAL + setTextAppearance(android.R.style.TextAppearance_Material_Medium) + }) + layout.addView(averageTdd.toTableRow(context, rh, tdds.size(), includeCarbs = true)) + } } - return t } } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/utils/stats/TirCalculator.kt b/app/src/main/java/info/nightscout/androidaps/utils/stats/TirCalculator.kt index 58ec13ab54..22cd19e030 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/stats/TirCalculator.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/stats/TirCalculator.kt @@ -1,14 +1,19 @@ package info.nightscout.androidaps.utils.stats -import android.text.Spanned +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Typeface import android.util.LongSparseArray +import android.view.Gravity +import android.view.ViewGroup +import android.widget.TableLayout +import android.widget.TextView import info.nightscout.androidaps.Constants import info.nightscout.androidaps.R -import info.nightscout.androidaps.interfaces.Profile import info.nightscout.androidaps.database.AppRepository +import info.nightscout.androidaps.interfaces.Profile import info.nightscout.androidaps.interfaces.ProfileFunction import info.nightscout.androidaps.utils.DateUtil -import info.nightscout.androidaps.utils.HtmlHelper import info.nightscout.androidaps.utils.MidnightTime import info.nightscout.androidaps.utils.T import info.nightscout.androidaps.utils.resources.ResourceHelper @@ -63,38 +68,49 @@ class TirCalculator @Inject constructor( return totalTir } - fun stats(): Spanned { - val lowTirMgdl = Constants.STATS_RANGE_LOW_MMOL * Constants.MMOLL_TO_MGDL - val highTirMgdl = Constants.STATS_RANGE_HIGH_MMOL * Constants.MMOLL_TO_MGDL - val lowTitMgdl = Constants.STATS_TARGET_LOW_MMOL * Constants.MMOLL_TO_MGDL - val highTitMgdl = Constants.STATS_TARGET_HIGH_MMOL * Constants.MMOLL_TO_MGDL + @SuppressLint("SetTextI18n") + fun stats(context: Context): TableLayout = + TableLayout(context).also { layout -> + val lowTirMgdl = Constants.STATS_RANGE_LOW_MMOL * Constants.MMOLL_TO_MGDL + val highTirMgdl = Constants.STATS_RANGE_HIGH_MMOL * Constants.MMOLL_TO_MGDL + val lowTitMgdl = Constants.STATS_TARGET_LOW_MMOL * Constants.MMOLL_TO_MGDL + val highTitMgdl = Constants.STATS_TARGET_HIGH_MMOL * Constants.MMOLL_TO_MGDL - val tir7 = calculate(7, lowTirMgdl, highTirMgdl) - val averageTir7 = averageTIR(tir7) - val tir30 = calculate(30, lowTirMgdl, highTirMgdl) - val averageTir30 = averageTIR(tir30) - val tit7 = calculate(7, lowTitMgdl, highTitMgdl) - val averageTit7 = averageTIR(tit7) - val tit30 = calculate(30, lowTitMgdl, highTitMgdl) - val averageTit30 = averageTIR(tit30) - return HtmlHelper.fromHtml( - "
" + rh.gs(R.string.tir) + " (" + Profile.toCurrentUnitsString(profileFunction, lowTirMgdl) + "-" + Profile.toCurrentUnitsString(profileFunction, highTirMgdl) + "):
" + - toText(rh, tir7) + - "
" + rh.gs(R.string.average) + " (" + Profile.toCurrentUnitsString(profileFunction, lowTirMgdl) + "-" + Profile.toCurrentUnitsString(profileFunction, highTirMgdl) + "):
" + - averageTir7.toText(rh, tir7.size()) + "
" + - averageTir30.toText(rh, tir30.size()) + - "
" + rh.gs(R.string.average) + " (" + Profile.toCurrentUnitsString(profileFunction, lowTitMgdl) + "-" + Profile.toCurrentUnitsString(profileFunction, highTitMgdl) + "):
" + - averageTit7.toText(rh, tit7.size()) + "
" + - averageTit30.toText(rh, tit30.size()) - ) - } - - fun toText(rh: ResourceHelper, tirs: LongSparseArray): String { - var t = "" - for (i in 0 until tirs.size()) { - t += "${tirs.valueAt(i).toText(rh, dateUtil)}
" + val tir7 = calculate(7, lowTirMgdl, highTirMgdl) + val averageTir7 = averageTIR(tir7) + val tir30 = calculate(30, lowTirMgdl, highTirMgdl) + val averageTir30 = averageTIR(tir30) + val tit7 = calculate(7, lowTitMgdl, highTitMgdl) + val averageTit7 = averageTIR(tit7) + val tit30 = calculate(30, lowTitMgdl, highTitMgdl) + val averageTit30 = averageTIR(tit30) + layout.layoutParams = TableLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f) + layout.addView( + TextView(context).apply { + text = rh.gs(R.string.tir) + " (" + Profile.toCurrentUnitsString(profileFunction, lowTirMgdl) + "-" + Profile.toCurrentUnitsString(profileFunction, highTirMgdl) + ")" + setTypeface(typeface, Typeface.BOLD) + gravity = Gravity.CENTER_HORIZONTAL + setTextAppearance(android.R.style.TextAppearance_Material_Medium) + }) + layout.addView(TIR.toTableRowHeader(context, rh)) + for (i in 0 until tir7.size()) layout.addView(tir7.valueAt(i).toTableRow(context, rh, dateUtil)) + layout.addView( + TextView(context).apply { + text = rh.gs(R.string.average) + " (" + Profile.toCurrentUnitsString(profileFunction, lowTirMgdl) + "-" + Profile.toCurrentUnitsString(profileFunction, highTirMgdl) + ")" + setTypeface(typeface, Typeface.BOLD) + gravity = Gravity.CENTER_HORIZONTAL + setTextAppearance(android.R.style.TextAppearance_Material_Medium) + }) + layout.addView(averageTir7.toTableRow(context, rh, tir7.size())) + layout.addView(averageTir30.toTableRow(context, rh, tir30.size())) + layout.addView( + TextView(context).apply { + text = rh.gs(R.string.average) + " (" + Profile.toCurrentUnitsString(profileFunction, lowTitMgdl) + "-" + Profile.toCurrentUnitsString(profileFunction, highTitMgdl) + ")" + setTypeface(typeface, Typeface.BOLD) + gravity = Gravity.CENTER_HORIZONTAL + setTextAppearance(android.R.style.TextAppearance_Material_Medium) + }) + layout.addView(averageTit7.toTableRow(context, rh, tit7.size())) + layout.addView(averageTit30.toTableRow(context, rh, tit30.size())) } - return t - } - } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/utils/wizard/BolusWizard.kt b/app/src/main/java/info/nightscout/androidaps/utils/wizard/BolusWizard.kt index dca32f246a..4b691ec932 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/wizard/BolusWizard.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/wizard/BolusWizard.kt @@ -300,12 +300,12 @@ class BolusWizard @Inject constructor( ) } - private fun confirmMessageAfterConstraints(advisor: Boolean): Spanned { + private fun confirmMessageAfterConstraints(context: Context, advisor: Boolean): Spanned { val actions: LinkedList = LinkedList() if (insulinAfterConstraints > 0) { val pct = if (percentageCorrection != 100) " ($percentageCorrection%)" else "" - actions.add(rh.gs(R.string.bolus) + ": " + rh.gs(R.string.formatinsulinunits, insulinAfterConstraints).formatColor(rh, R.color.bolus) + pct) + actions.add(rh.gs(R.string.bolus) + ": " + rh.gs(R.string.formatinsulinunits, insulinAfterConstraints).formatColor(context, rh, R.attr.bolusColor) + pct) } if (carbs > 0 && !advisor) { var timeShift = "" @@ -314,24 +314,25 @@ class BolusWizard @Inject constructor( } else if (carbTime < 0) { timeShift += " (" + rh.gs(R.string.mins, carbTime) + ")" } - actions.add(rh.gs(R.string.carbs) + ": " + rh.gs(R.string.format_carbs, carbs).formatColor(rh, R.color.carbs) + timeShift) + actions.add(rh.gs(R.string.carbs) + ": " + rh.gs(R.string.format_carbs, carbs).formatColor(context, rh, R.attr.carbsColor) + timeShift) } if (insulinFromCOB > 0) { actions.add( - rh.gs(R.string.cobvsiob) + ": " + rh.gs(R.string.formatsignedinsulinunits, insulinFromBolusIOB + insulinFromBasalIOB + insulinFromCOB + insulinFromBG).formatColor(rh, R.color.cobAlert) + rh.gs(R.string.cobvsiob) + ": " + rh.gs(R.string.formatsignedinsulinunits, insulinFromBolusIOB + insulinFromBasalIOB + insulinFromCOB + insulinFromBG).formatColor(context, rh, R.attr + .cobAlertColor) ) val absorptionRate = iobCobCalculator.ads.slowAbsorptionPercentage(60) if (absorptionRate > .25) - actions.add(rh.gs(R.string.slowabsorptiondetected, rh.gc(R.color.cobAlert), (absorptionRate * 100).toInt())) + actions.add(rh.gs(R.string.slowabsorptiondetected, rh.gac(context, R.attr.cobAlertColor), (absorptionRate * 100).toInt())) } if (abs(insulinAfterConstraints - calculatedTotalInsulin) > activePlugin.activePump.pumpDescription.pumpType.determineCorrectBolusStepSize(insulinAfterConstraints)) - actions.add(rh.gs(R.string.bolusconstraintappliedwarn, calculatedTotalInsulin, insulinAfterConstraints).formatColor(rh, R.color.warning)) + actions.add(rh.gs(R.string.bolusconstraintappliedwarn, calculatedTotalInsulin, insulinAfterConstraints).formatColor(context, rh, R.attr.warningColor)) if (config.NSCLIENT && insulinAfterConstraints > 0) - actions.add(rh.gs(R.string.bolusrecordedonly).formatColor(rh, R.color.warning)) + actions.add(rh.gs(R.string.bolusrecordedonly).formatColor(context, rh, R.attr.warningColor)) if (useAlarm && !advisor && carbs > 0 && carbTime > 0) - actions.add(rh.gs(R.string.alarminxmin, carbTime).formatColor(rh, R.color.info)) + actions.add(rh.gs(R.string.alarminxmin, carbTime).formatColor(context, rh, R.attr.infoColor)) if (advisor) - actions.add(rh.gs(R.string.advisoralarm).formatColor(rh, R.color.info)) + actions.add(rh.gs(R.string.advisoralarm).formatColor(context, rh, R.attr.infoColor)) return HtmlHelper.fromHtml(Joiner.on("
").join(actions)) } @@ -360,7 +361,7 @@ class BolusWizard @Inject constructor( } private fun bolusAdvisorProcessing(ctx: Context) { - val confirmMessage = confirmMessageAfterConstraints(advisor = true) + val confirmMessage = confirmMessageAfterConstraints(ctx, advisor = true) OKDialog.showConfirmation(ctx, rh.gs(R.string.boluswizard), confirmMessage, { DetailedBolusInfo().apply { eventType = DetailedBolusInfo.EventType.CORRECTION_BOLUS @@ -417,7 +418,7 @@ class BolusWizard @Inject constructor( val profile = profileFunction.getProfile() ?: return val pump = activePlugin.activePump - val confirmMessage = confirmMessageAfterConstraints(advisor = false) + val confirmMessage = confirmMessageAfterConstraints(ctx, advisor = false) OKDialog.showConfirmation(ctx, rh.gs(R.string.boluswizard), confirmMessage, { if (insulinAfterConstraints > 0 || carbs > 0) { if (useSuperBolus) { diff --git a/app/src/main/java/info/nightscout/androidaps/widget/Widget.kt b/app/src/main/java/info/nightscout/androidaps/widget/Widget.kt new file mode 100644 index 0000000000..e123a4cc0e --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/widget/Widget.kt @@ -0,0 +1,259 @@ +package info.nightscout.androidaps.widget + +import android.app.PendingIntent +import android.appwidget.AppWidgetManager +import android.appwidget.AppWidgetProvider +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.graphics.Color +import android.graphics.Paint +import android.view.View +import android.widget.RemoteViews +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.Constants +import info.nightscout.androidaps.MainActivity +import info.nightscout.androidaps.R +import info.nightscout.androidaps.data.ProfileSealed +import info.nightscout.androidaps.database.interfaces.end +import info.nightscout.androidaps.extensions.directionToIcon +import info.nightscout.androidaps.extensions.toVisibility +import info.nightscout.androidaps.extensions.valueToUnitsString +import info.nightscout.androidaps.interfaces.* +import info.nightscout.androidaps.plugins.aps.openAPSSMB.DetermineBasalResultSMB +import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker +import info.nightscout.androidaps.plugins.general.overview.OverviewData +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProvider +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.TrendCalculator +import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.shared.logging.AAPSLogger +import info.nightscout.shared.logging.LTag +import info.nightscout.shared.sharedPreferences.SP +import java.util.* +import javax.inject.Inject +import kotlin.math.abs + +/** + * Implementation of App Widget functionality. + */ +class Widget : AppWidgetProvider() { + + @Inject lateinit var profileFunction: ProfileFunction + @Inject lateinit var overviewData: OverviewData + @Inject lateinit var trendCalculator: TrendCalculator + @Inject lateinit var rh: ResourceHelper + @Inject lateinit var glucoseStatusProvider: GlucoseStatusProvider + @Inject lateinit var dateUtil: DateUtil + @Inject lateinit var aapsLogger: AAPSLogger + @Inject lateinit var activePlugin: ActivePlugin + @Inject lateinit var iobCobCalculator: IobCobCalculator + @Inject lateinit var loop: Loop + @Inject lateinit var config: Config + @Inject lateinit var sp: SP + @Inject lateinit var constraintChecker: ConstraintChecker + + private val intentAction = "OpenApp" + + override fun onReceive(context: Context, intent: Intent?) { + (context.applicationContext as HasAndroidInjector).androidInjector().inject(this) + super.onReceive(context, intent) + } + + override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { + // There may be multiple widgets active, so update all of them + for (appWidgetId in appWidgetIds) { + updateAppWidget(context, appWidgetManager, appWidgetId) + } + } + + override fun onEnabled(context: Context) { + // Enter relevant functionality for when the first widget is created + } + + override fun onDisabled(context: Context) { + // Enter relevant functionality for when the last widget is disabled + } + + private fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int) { + aapsLogger.debug(LTag.WIDGET, "updateAppWidget called") + + val views = RemoteViews(context.packageName, R.layout.widget_layout) + val alpha = sp.getInt(WidgetConfigureActivity.PREF_PREFIX_KEY + appWidgetId, WidgetConfigureActivity.DEFAULT_OPACITY) + + // Create an Intent to launch MainActivity when clicked + val intent = Intent(context, MainActivity::class.java).also { it.action = intentAction } + val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT) + // Widgets allow click handlers to only launch pending intents + views.setOnClickPendingIntent(R.id.widget_layout, pendingIntent) + views.setInt(R.id.widget_layout, "setBackgroundColor", Color.argb(alpha, 0, 0, 0)) + + updateBg(views) + updateTemporaryBasal(views) + updateExtendedBolus(views) + updateIobCob(views) + updateTemporaryTarget(views) + updateProfile(views) + updateSensitivity(views) + + // Instruct the widget manager to update the widget + appWidgetManager.updateAppWidget(appWidgetId, views) + } + + private fun updateBg(views: RemoteViews) { + val units = profileFunction.getUnits() + views.setTextViewText(R.id.bg, overviewData.lastBg?.valueToUnitsString(units) ?: rh.gs(R.string.notavailable)) + views.setTextColor( + R.id.bg, when { + overviewData.isLow -> rh.gc(R.color.widget_low) + overviewData.isHigh -> rh.gc(R.color.widget_high) + else -> rh.gc(R.color.widget_inrange) + } + ) + views.setImageViewResource(R.id.arrow, trendCalculator.getTrendArrow(overviewData.lastBg).directionToIcon()) + views.setInt( + R.id.arrow, "setColorFilter", when { + overviewData.isLow -> rh.gc(R.color.widget_low) + overviewData.isHigh -> rh.gc(R.color.widget_high) + else -> rh.gc(R.color.widget_inrange) + } + ) + + val glucoseStatus = glucoseStatusProvider.glucoseStatusData + if (glucoseStatus != null) { + views.setTextViewText(R.id.delta, Profile.toSignedUnitsString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units)) + views.setTextViewText(R.id.avg_delta, Profile.toSignedUnitsString(glucoseStatus.shortAvgDelta, glucoseStatus.shortAvgDelta * Constants.MGDL_TO_MMOLL, units)) + views.setTextViewText(R.id.long_avg_delta, Profile.toSignedUnitsString(glucoseStatus.longAvgDelta, glucoseStatus.longAvgDelta * Constants.MGDL_TO_MMOLL, units)) + } else { + views.setTextViewText(R.id.delta, rh.gs(R.string.notavailable)) + views.setTextViewText(R.id.avg_delta, rh.gs(R.string.notavailable)) + views.setTextViewText(R.id.long_avg_delta, rh.gs(R.string.notavailable)) + } + + // strike through if BG is old + if (!overviewData.isActualBg) views.setInt(R.id.bg, "setPaintFlags", Paint.STRIKE_THRU_TEXT_FLAG or Paint.ANTI_ALIAS_FLAG) + else views.setInt(R.id.bg, "setPaintFlags", Paint.ANTI_ALIAS_FLAG) + + views.setTextViewText(R.id.time_ago, dateUtil.minAgo(rh, overviewData.lastBg?.timestamp)) + views.setTextViewText(R.id.time_ago_short, "(" + dateUtil.minAgoShort(overviewData.lastBg?.timestamp) + ")") + } + + private fun updateTemporaryBasal(views: RemoteViews) { + views.setTextViewText(R.id.base_basal, overviewData.temporaryBasalText(iobCobCalculator)) + views.setTextColor(R.id.base_basal, iobCobCalculator.getTempBasalIncludingConvertedExtended(dateUtil.now())?.let { rh.gc(R.color.widget_basal) } + ?: rh.gc(R.color.white)) + views.setImageViewResource(R.id.base_basal_icon, overviewData.temporaryBasalIcon(iobCobCalculator)) + } + + private fun updateExtendedBolus(views: RemoteViews) { + val pump = activePlugin.activePump + views.setTextViewText(R.id.extended_bolus, overviewData.extendedBolusText(iobCobCalculator)) + views.setViewVisibility(R.id.extended_layout, (iobCobCalculator.getExtendedBolus(dateUtil.now()) != null && !pump.isFakingTempsByExtendedBoluses).toVisibility()) + } + + private fun updateIobCob(views: RemoteViews) { + views.setTextViewText(R.id.iob, overviewData.iobText(iobCobCalculator)) + // cob + var cobText = overviewData.cobInfo(iobCobCalculator).displayText(rh, dateUtil, isDev = false) ?: rh.gs(R.string.value_unavailable_short) + + val constraintsProcessed = loop.lastRun?.constraintsProcessed + val lastRun = loop.lastRun + if (config.APS && constraintsProcessed != null && lastRun != null) { + if (constraintsProcessed.carbsReq > 0) { + //only display carbsreq when carbs have not been entered recently + if (overviewData.lastCarbsTime < lastRun.lastAPSRun) { + cobText += " | " + constraintsProcessed.carbsReq + " " + rh.gs(R.string.required) + } + } + } + views.setTextViewText(R.id.cob, cobText) + } + + private fun updateTemporaryTarget(views: RemoteViews) { + val units = profileFunction.getUnits() + val tempTarget = overviewData.temporaryTarget + if (tempTarget != null) { + // this is crashing, use background as text for now + //views.setTextColor(R.id.temp_target, rh.gc(R.color.ribbonTextWarning)) + //views.setInt(R.id.temp_target, "setBackgroundColor", rh.gc(R.color.ribbonWarning)) + views.setTextColor(R.id.temp_target, rh.gc(R.color.widget_ribbonWarning)) + views.setTextViewText(R.id.temp_target, Profile.toTargetRangeString(tempTarget.lowTarget, tempTarget.highTarget, GlucoseUnit.MGDL, units) + " " + dateUtil.untilString(tempTarget.end, rh)) + } else { + // If the target is not the same as set in the profile then oref has overridden it + profileFunction.getProfile()?.let { profile -> + val targetUsed = loop.lastRun?.constraintsProcessed?.targetBG ?: 0.0 + + if (targetUsed != 0.0 && abs(profile.getTargetMgdl() - targetUsed) > 0.01) { + aapsLogger.debug("Adjusted target. Profile: ${profile.getTargetMgdl()} APS: $targetUsed") + views.setTextViewText(R.id.temp_target, Profile.toTargetRangeString(targetUsed, targetUsed, GlucoseUnit.MGDL, units)) + // this is crashing, use background as text for now + //views.setTextColor(R.id.temp_target, rh.gc(R.color.ribbonTextWarning)) + //views.setInt(R.id.temp_target, "setBackgroundResource", rh.gc(R.color.tempTargetBackground)) + views.setTextColor(R.id.temp_target, rh.gc(R.color.widget_ribbonWarning)) + } else { + // this is crashing, use background as text for now + //views.setTextColor(R.id.temp_target, rh.gc(R.color.ribbonTextDefault)) + //views.setInt(R.id.temp_target, "setBackgroundColor", rh.gc(R.color.ribbonDefault)) + views.setTextColor(R.id.temp_target, rh.gc(R.color.widget_ribbonTextDefault)) + views.setTextViewText(R.id.temp_target, Profile.toTargetRangeString(profile.getTargetLowMgdl(), profile.getTargetHighMgdl(), GlucoseUnit.MGDL, units)) + } + } + } + } + + fun updateProfile(views: RemoteViews) { + val profileTextColor = + profileFunction.getProfile()?.let { + if (it is ProfileSealed.EPS) { + if (it.value.originalPercentage != 100 || it.value.originalTimeshift != 0L || it.value.originalDuration != 0L) + rh.gc(R.color.widget_ribbonWarning) + else rh.gc(R.color.widget_ribbonTextDefault) + } else if (it is ProfileSealed.PS) { + rh.gc(R.color.widget_ribbonTextDefault) + } else { + rh.gc(R.color.widget_ribbonTextDefault) + } + } ?: rh.gc(R.color.widget_ribbonCritical) + + views.setTextViewText(R.id.active_profile, profileFunction.getProfileNameWithRemainingTime()) + // this is crashing, use background as text for now + //views.setInt(R.id.active_profile, "setBackgroundColor", profileBackgroundColor) + //views.setTextColor(R.id.active_profile, profileTextColor) + views.setTextColor(R.id.active_profile, profileTextColor) + } + + private fun updateSensitivity(views: RemoteViews) { + if (sp.getBoolean(R.string.key_openapsama_useautosens, false) && constraintChecker.isAutosensModeEnabled().value()) + views.setImageViewResource(R.id.sensitivity_icon, R.drawable.ic_swap_vert_black_48dp_green) + else + views.setImageViewResource(R.id.sensitivity_icon, R.drawable.ic_x_swap_vert) + views.setTextViewText(R.id.sensitivity, overviewData.lastAutosensData(iobCobCalculator)?.let { autosensData -> + String.format(Locale.ENGLISH, "%.0f%%", autosensData.autosensResult.ratio * 100) + } ?: "") + + // Show variable sensitivity + val request = loop.lastRun?.request + if (request is DetermineBasalResultSMB) { + val isfMgdl = profileFunction.getProfile()?.getIsfMgdl() + val variableSens = request.variableSens + if (variableSens != isfMgdl && variableSens != null && isfMgdl != null) { + views.setTextViewText( + R.id.variable_sensitivity, + String.format( + Locale.getDefault(), "%1$.1f→%2$.1f", + Profile.toUnits(isfMgdl, isfMgdl * Constants.MGDL_TO_MMOLL, profileFunction.getUnits()), + Profile.toUnits(variableSens, variableSens * Constants.MGDL_TO_MMOLL, profileFunction.getUnits()) + ) + ) + views.setViewVisibility(R.id.variable_sensitivity, View.VISIBLE) + } else views.setViewVisibility(R.id.variable_sensitivity, View.GONE) + } else views.setViewVisibility(R.id.variable_sensitivity, View.GONE) + } +} + +internal fun updateWidget(context: Context) { + context.sendBroadcast(Intent().also { + it.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, AppWidgetManager.getInstance(context).getAppWidgetIds(ComponentName(context, Widget::class.java))) + it.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE + }) +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/widget/WidgetConfigureActivity.kt b/app/src/main/java/info/nightscout/androidaps/widget/WidgetConfigureActivity.kt new file mode 100644 index 0000000000..4647b86454 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/widget/WidgetConfigureActivity.kt @@ -0,0 +1,78 @@ +package info.nightscout.androidaps.widget + +import android.appwidget.AppWidgetManager +import android.content.Intent +import android.os.Bundle +import android.widget.SeekBar +import dagger.android.DaggerActivity +import info.nightscout.androidaps.databinding.WidgetConfigureBinding +import info.nightscout.shared.sharedPreferences.SP +import javax.inject.Inject + +/** + * The configuration screen for the [Widget] AppWidget. + */ +class WidgetConfigureActivity : DaggerActivity() { + + @Inject lateinit var sp: SP + + companion object { + + @Suppress("PrivatePropertyName") + const val PREF_PREFIX_KEY = "appwidget_" + const val DEFAULT_OPACITY = 25 + } + + private var appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID + private var value = 0 + + private lateinit var binding: WidgetConfigureBinding + + public override fun onCreate(icicle: Bundle?) { + super.onCreate(icicle) + + // Set the result to CANCELED. This will cause the widget host to cancel + // out of the widget placement if the user presses the back button. + setResult(RESULT_CANCELED) + + binding = WidgetConfigureBinding.inflate(layoutInflater) + setContentView(binding.root) + + binding.seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { + override fun onStopTrackingTouch(seekBar: SeekBar) { + // Make sure we pass back the original appWidgetId + val resultValue = Intent() + resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) + setResult(RESULT_OK, resultValue) + finish() + } + + override fun onStartTrackingTouch(seekBar: SeekBar) {} + override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { + value = progress + saveTitlePref(appWidgetId, value) + updateWidget(this@WidgetConfigureActivity) + } + }) + + // Find the widget id from the intent. + appWidgetId = intent.extras?.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID) ?: AppWidgetManager.INVALID_APPWIDGET_ID + + // If this activity was started with an intent without an app widget ID, finish with an error. + if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) { + finish() + return + } + + binding.seekBar.progress = loadTitlePref(appWidgetId) + } + + // Write the prefix to the SharedPreferences object for this widget + fun saveTitlePref(appWidgetId: Int, value: Int) { + sp.putInt(PREF_PREFIX_KEY + appWidgetId, value) + } + + // Read the prefix from the SharedPreferences object for this widget. + // If there is no preference saved, get the default from a resource + private fun loadTitlePref(appWidgetId: Int): Int = sp.getInt(PREF_PREFIX_KEY + appWidgetId, 25) +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/workflow/CalculationWorkflow.kt b/app/src/main/java/info/nightscout/androidaps/workflow/CalculationWorkflow.kt new file mode 100644 index 0000000000..d6a84cf8e5 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/workflow/CalculationWorkflow.kt @@ -0,0 +1,270 @@ +package info.nightscout.androidaps.workflow + +import android.content.Context +import android.os.SystemClock +import androidx.work.* +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.R +import info.nightscout.androidaps.events.Event +import info.nightscout.androidaps.events.EventAppInitialized +import info.nightscout.androidaps.events.EventOfflineChange +import info.nightscout.androidaps.events.EventPreferenceChange +import info.nightscout.androidaps.events.EventTherapyEventChange +import info.nightscout.androidaps.interfaces.ActivePlugin +import info.nightscout.androidaps.interfaces.IobCobCalculator +import info.nightscout.androidaps.plugins.bus.RxBus +import info.nightscout.androidaps.plugins.general.overview.OverviewData +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobOref1Worker +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobOrefWorker +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventNewHistoryData +import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref1Plugin +import info.nightscout.androidaps.receivers.DataWorker +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.FabricPrivacy +import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.androidaps.utils.rx.AapsSchedulers +import info.nightscout.shared.logging.AAPSLogger +import info.nightscout.shared.logging.LTag +import io.reactivex.rxjava3.disposables.CompositeDisposable +import io.reactivex.rxjava3.kotlin.plusAssign +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class CalculationWorkflow @Inject constructor( + aapsSchedulers: AapsSchedulers, + rh: ResourceHelper, + rxBus: RxBus, + private val context: Context, + private val injector: HasAndroidInjector, + private val aapsLogger: AAPSLogger, + private val fabricPrivacy: FabricPrivacy, + private val dateUtil: DateUtil, + private val sensitivityOref1Plugin: SensitivityOref1Plugin, + private val dataWorker: DataWorker, + private val activePlugin: ActivePlugin +) { + + companion object { + + const val MAIN_CALCULATION = "calculation" + const val HISTORY_CALCULATION = "history_calculation" + const val JOB = "job" + } + + private var disposable: CompositeDisposable = CompositeDisposable() + + private val iobCobCalculator: IobCobCalculator + get() = activePlugin.activeIobCobCalculator // cross-dependency CalculationWorkflow x IobCobCalculator + private val overviewData: OverviewData + get() = (iobCobCalculator as IobCobCalculatorPlugin).overviewData + + enum class ProgressData(val pass: Int, val percentOfTotal: Int) { + PREPARE_BASAL_DATA(0, 5), + PREPARE_TEMPORARY_TARGET_DATA(1, 5), + PREPARE_TREATMENTS_DATA(2, 5), + IOB_COB_OREF(3, 75), + PREPARE_IOB_AUTOSENS_DATA(4, 10); + + fun finalPercent(progress: Int): Int { + var total = 0 + for (i in values()) if (i.pass < pass) total += i.percentOfTotal + total += (percentOfTotal.toDouble() * progress / 100.0).toInt() + return total + } + } + + init { + // Verify definition + var sumPercent = 0 + for (pass in ProgressData.values()) sumPercent += pass.percentOfTotal + require(sumPercent == 100) + + disposable += rxBus + .toObservable(EventTherapyEventChange::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ runOnEventTherapyEventChange() }, fabricPrivacy::logException) + disposable += rxBus + .toObservable(EventOfflineChange::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ runOnEventTherapyEventChange() }, fabricPrivacy::logException) + disposable += rxBus + .toObservable(EventPreferenceChange::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ event -> + if (event.isChanged(rh, R.string.key_units)) { + overviewData.reset() + rxBus.send(EventNewHistoryData(0, false)) + } + if (event.isChanged(rh, R.string.key_rangetodisplay)) { + overviewData.initRange() + runOnScaleChanged() + rxBus.send(EventNewHistoryData(0, false)) + } + }, fabricPrivacy::logException) + disposable += rxBus + .toObservable(EventAppInitialized::class.java) + .observeOn(aapsSchedulers.io) + .subscribe( + { + runCalculation( + MAIN_CALCULATION, + iobCobCalculator, + overviewData, + "onEventAppInitialized", + System.currentTimeMillis(), + bgDataReload = true, + limitDataToOldestAvailable = true, + cause = it, + runLoop = true + ) + }, + fabricPrivacy::logException + ) + + } + + fun stopCalculation(job: String, from: String) { + aapsLogger.debug(LTag.AUTOSENS, "Stopping calculation thread: $from") + WorkManager.getInstance(context).cancelUniqueWork(job) + val workStatus = WorkManager.getInstance(context).getWorkInfosForUniqueWork(job).get() + while (workStatus.size >= 1 && workStatus[0].state == WorkInfo.State.RUNNING) + SystemClock.sleep(100) + aapsLogger.debug(LTag.AUTOSENS, "Calculation thread stopped: $from") + } + + fun runCalculation( + job: String, + iobCobCalculator: IobCobCalculator, + overviewData: OverviewData, + from: String, + end: Long, + bgDataReload: Boolean, + limitDataToOldestAvailable: Boolean, + cause: Event?, + runLoop: Boolean + ) { + aapsLogger.debug(LTag.AUTOSENS, "Starting calculation worker: $from to ${dateUtil.dateAndTimeAndSecondsString(end)}") + + WorkManager.getInstance(context) + .beginUniqueWork( + job, ExistingWorkPolicy.REPLACE, + if (bgDataReload) OneTimeWorkRequest.Builder(LoadBgDataWorker::class.java).setInputData(dataWorker.storeInputData(LoadBgDataWorker.LoadBgData(iobCobCalculator, end))).build() + else OneTimeWorkRequest.Builder(DummyWorker::class.java).build() + ) + .then( + OneTimeWorkRequest.Builder(PrepareBucketedDataWorker::class.java) + .setInputData(dataWorker.storeInputData(PrepareBucketedDataWorker.PrepareBucketedData(iobCobCalculator, overviewData))) + .build() + ) + .then( + OneTimeWorkRequest.Builder(PrepareBgDataWorker::class.java) + .setInputData(dataWorker.storeInputData(PrepareBgDataWorker.PrepareBgData(iobCobCalculator, overviewData))) + .build() + ) + .then( + OneTimeWorkRequest.Builder(UpdateGraphWorker::class.java) + .setInputData(Data.Builder().putString(JOB, job).build()) + .build() + ) + .then( + OneTimeWorkRequest.Builder(PrepareTreatmentsDataWorker::class.java) + .setInputData(dataWorker.storeInputData(PrepareTreatmentsDataWorker.PrepareTreatmentsData(overviewData))) + .build() + ) + .then( + OneTimeWorkRequest.Builder(PrepareBasalDataWorker::class.java) + .setInputData(dataWorker.storeInputData(PrepareBasalDataWorker.PrepareBasalData(iobCobCalculator, overviewData))) + .build() + ) + .then( + OneTimeWorkRequest.Builder(PrepareTemporaryTargetDataWorker::class.java) + .setInputData(dataWorker.storeInputData(PrepareTemporaryTargetDataWorker.PrepareTemporaryTargetData(overviewData))) + .build() + ) + .then( + OneTimeWorkRequest.Builder(UpdateGraphWorker::class.java) + .setInputData(Data.Builder().putString(JOB, job).build()) + .build() + ) + .then( + if (sensitivityOref1Plugin.isEnabled()) + OneTimeWorkRequest.Builder(IobCobOref1Worker::class.java) + .setInputData(dataWorker.storeInputData(IobCobOref1Worker.IobCobOref1WorkerData(injector, iobCobCalculator, from, end, limitDataToOldestAvailable, cause))) + .build() + else + OneTimeWorkRequest.Builder(IobCobOrefWorker::class.java) + .setInputData(dataWorker.storeInputData(IobCobOrefWorker.IobCobOrefWorkerData(injector, iobCobCalculator, from, end, limitDataToOldestAvailable, cause))) + .build() + ) + .then(OneTimeWorkRequest.Builder(UpdateIobCobSensWorker::class.java).build()) + .then( + OneTimeWorkRequest.Builder(PrepareIobAutosensGraphDataWorker::class.java) + .setInputData(dataWorker.storeInputData(PrepareIobAutosensGraphDataWorker.PrepareIobAutosensData(iobCobCalculator, overviewData))) + .build() + ) + .then( + OneTimeWorkRequest.Builder(UpdateGraphWorker::class.java) + .setInputData(Data.Builder().putString(JOB, job).build()) + .build() + ) + .then( + runLoop, + OneTimeWorkRequest.Builder(InvokeLoopWorker::class.java) + .setInputData(dataWorker.storeInputData(InvokeLoopWorker.InvokeLoopData(cause))) + .build() + ) + .then( + runLoop, + OneTimeWorkRequest.Builder(PreparePredictionsWorker::class.java) + .setInputData(dataWorker.storeInputData(PreparePredictionsWorker.PreparePredictionsData(overviewData))) + .build() + ) + .then( + runLoop, OneTimeWorkRequest.Builder(UpdateGraphWorker::class.java) + .setInputData(Data.Builder().putString(JOB, job).build()) + .build() + ) + .enqueue() + } + + fun WorkContinuation.then(shouldAdd: Boolean, work: OneTimeWorkRequest): WorkContinuation = + if (shouldAdd) then(work) else this + + private fun runOnEventTherapyEventChange() { + WorkManager.getInstance(context) + .beginUniqueWork( + MAIN_CALCULATION, ExistingWorkPolicy.APPEND, + OneTimeWorkRequest.Builder(PrepareTreatmentsDataWorker::class.java) + .setInputData(dataWorker.storeInputData(PrepareTreatmentsDataWorker.PrepareTreatmentsData(overviewData))) + .build() + ) + .then( + OneTimeWorkRequest.Builder(UpdateGraphWorker::class.java) + .build() + ) + .enqueue() + + } + + private fun runOnScaleChanged() { + WorkManager.getInstance(context) + .beginUniqueWork( + MAIN_CALCULATION, ExistingWorkPolicy.APPEND, + OneTimeWorkRequest.Builder(PrepareBucketedDataWorker::class.java) + .setInputData(dataWorker.storeInputData(PrepareBucketedDataWorker.PrepareBucketedData(iobCobCalculator, overviewData))) + .build() + ) + .then( + OneTimeWorkRequest.Builder(PrepareBgDataWorker::class.java) + .setInputData(dataWorker.storeInputData(PrepareBgDataWorker.PrepareBgData(iobCobCalculator, overviewData))) + .build() + ) + .then( + OneTimeWorkRequest.Builder(UpdateGraphWorker::class.java) + .build() + ) + .enqueue() + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/workflow/DummyWorker.kt b/app/src/main/java/info/nightscout/androidaps/workflow/DummyWorker.kt new file mode 100644 index 0000000000..fd30c54dc6 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/workflow/DummyWorker.kt @@ -0,0 +1,13 @@ +package info.nightscout.androidaps.workflow + +import android.content.Context +import androidx.work.Worker +import androidx.work.WorkerParameters + +class DummyWorker( + context: Context, + params: WorkerParameters +) : Worker(context, params) { + + override fun doWork(): Result = Result.success() +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/workflow/InvokeLoopWorker.kt b/app/src/main/java/info/nightscout/androidaps/workflow/InvokeLoopWorker.kt new file mode 100644 index 0000000000..b6e5fb910d --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/workflow/InvokeLoopWorker.kt @@ -0,0 +1,51 @@ +package info.nightscout.androidaps.workflow + +import android.content.Context +import androidx.work.Worker +import androidx.work.WorkerParameters +import androidx.work.workDataOf +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.events.Event +import info.nightscout.androidaps.events.EventNewBG +import info.nightscout.androidaps.interfaces.IobCobCalculator +import info.nightscout.androidaps.interfaces.Loop +import info.nightscout.androidaps.receivers.DataWorker +import javax.inject.Inject + +class InvokeLoopWorker( + context: Context, + params: WorkerParameters +) : Worker(context, params) { + + @Inject lateinit var dataWorker: DataWorker + @Inject lateinit var iobCobCalculator: IobCobCalculator + @Inject lateinit var loop: Loop + + init { + (context.applicationContext as HasAndroidInjector).androidInjector().inject(this) + } + + class InvokeLoopData( + val cause: Event? + ) + + /* + This method is triggered once autosens calculation has completed, so the LoopPlugin + has current data to work with. However, autosens calculation can be triggered by multiple + sources and currently only a new BG should trigger a loop run. Hence we return early if + the event causing the calculation is not EventNewBg. +

+ */ + override fun doWork(): Result { + + val data = dataWorker.pickupObject(inputData.getLong(DataWorker.STORE_KEY, -1)) as InvokeLoopData? + ?: return Result.failure(workDataOf("Error" to "missing input data")) + + if (data.cause !is EventNewBG) return Result.success(workDataOf("Result" to "no calculation needed")) + val glucoseValue = iobCobCalculator.ads.actualBg() ?: return Result.success(workDataOf("Result" to "bg outdated")) + if (glucoseValue.timestamp <= loop.lastBgTriggeredRun) return Result.success(workDataOf("Result" to "already looped with that value")) + loop.lastBgTriggeredRun = glucoseValue.timestamp + loop.invoke("Calculation for $glucoseValue", true) + return Result.success() + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/workflow/LoadBgDataWorker.kt b/app/src/main/java/info/nightscout/androidaps/workflow/LoadBgDataWorker.kt new file mode 100644 index 0000000000..67e8290822 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/workflow/LoadBgDataWorker.kt @@ -0,0 +1,45 @@ +package info.nightscout.androidaps.workflow + +import android.content.Context +import androidx.work.Worker +import androidx.work.WorkerParameters +import androidx.work.workDataOf +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.database.AppRepository +import info.nightscout.androidaps.interfaces.IobCobCalculator +import info.nightscout.androidaps.plugins.bus.RxBus +import info.nightscout.androidaps.receivers.DataWorker +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.shared.logging.AAPSLogger +import javax.inject.Inject + +class LoadBgDataWorker( + context: Context, + params: WorkerParameters +) : Worker(context, params) { + + @Inject lateinit var dataWorker: DataWorker + @Inject lateinit var aapsLogger: AAPSLogger + @Inject lateinit var dateUtil: DateUtil + @Inject lateinit var rxBus: RxBus + @Inject lateinit var repository: AppRepository + + init { + (context.applicationContext as HasAndroidInjector).androidInjector().inject(this) + } + + class LoadBgData( + val iobCobCalculator: IobCobCalculator, + val end: Long + ) + + override fun doWork(): Result { + + val data = dataWorker.pickupObject(inputData.getLong(DataWorker.STORE_KEY, -1)) as LoadBgData? + ?: return Result.failure(workDataOf("Error" to "missing input data")) + + data.iobCobCalculator.ads.loadBgData(data.end, repository, aapsLogger, dateUtil, rxBus) + data.iobCobCalculator.clearCache() + return Result.success() + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/workflow/PrepareBasalDataWorker.kt b/app/src/main/java/info/nightscout/androidaps/workflow/PrepareBasalDataWorker.kt new file mode 100644 index 0000000000..d69bde9668 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/workflow/PrepareBasalDataWorker.kt @@ -0,0 +1,147 @@ +package info.nightscout.androidaps.workflow + +import android.content.Context +import android.graphics.DashPathEffect +import android.graphics.Paint +import androidx.core.content.ContextCompat +import androidx.work.Worker +import androidx.work.WorkerParameters +import androidx.work.workDataOf +import com.jjoe64.graphview.series.LineGraphSeries +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.R +import info.nightscout.androidaps.interfaces.IobCobCalculator +import info.nightscout.androidaps.interfaces.ProfileFunction +import info.nightscout.androidaps.plugins.bus.RxBus +import info.nightscout.androidaps.plugins.general.overview.OverviewData +import info.nightscout.androidaps.plugins.general.overview.graphExtensions.ScaledDataPoint +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress +import info.nightscout.androidaps.receivers.DataWorker +import info.nightscout.androidaps.utils.resources.ResourceHelper +import java.util.ArrayList +import javax.inject.Inject +import kotlin.math.max + +class PrepareBasalDataWorker( + context: Context, + params: WorkerParameters +) : Worker(context, params) { + + @Inject lateinit var dataWorker: DataWorker + @Inject lateinit var profileFunction: ProfileFunction + @Inject lateinit var rh: ResourceHelper + @Inject lateinit var rxBus: RxBus + var ctx: Context + init { + (context.applicationContext as HasAndroidInjector).androidInjector().inject(this) + ctx = rh.getThemedCtx(context) + } + + class PrepareBasalData( + val iobCobCalculator: IobCobCalculator, // cannot be injected : HistoryBrowser uses different instance + val overviewData: OverviewData + ) + + override fun doWork(): Result { + + val data = dataWorker.pickupObject(inputData.getLong(DataWorker.STORE_KEY, -1)) as PrepareBasalData? + ?: return Result.failure(workDataOf("Error" to "missing input data")) + + rxBus.send(EventIobCalculationProgress(CalculationWorkflow.ProgressData.PREPARE_BASAL_DATA, 0, null)) + data.overviewData.maxBasalValueFound = 0.0 + val baseBasalArray: MutableList = ArrayList() + val tempBasalArray: MutableList = ArrayList() + val basalLineArray: MutableList = ArrayList() + val absoluteBasalLineArray: MutableList = ArrayList() + var lastLineBasal = 0.0 + var lastAbsoluteLineBasal = -1.0 + var lastBaseBasal = 0.0 + var lastTempBasal = 0.0 + var time = data.overviewData.fromTime + while (time < data.overviewData.toTime) { + val progress = (time - data.overviewData.fromTime).toDouble() / (data.overviewData.toTime - data.overviewData.fromTime) * 100.0 + rxBus.send(EventIobCalculationProgress(CalculationWorkflow.ProgressData.PREPARE_BASAL_DATA, progress.toInt(), null)) + val profile = profileFunction.getProfile(time) + if (profile == null) { + time += 60 * 1000L + continue + } + val basalData = data.iobCobCalculator.getBasalData(profile, time) + val baseBasalValue = basalData.basal + var absoluteLineValue = baseBasalValue + var tempBasalValue = 0.0 + var basal = 0.0 + if (basalData.isTempBasalRunning) { + tempBasalValue = basalData.tempBasalAbsolute + absoluteLineValue = tempBasalValue + if (tempBasalValue != lastTempBasal) { + tempBasalArray.add(ScaledDataPoint(time, lastTempBasal, data.overviewData.basalScale)) + tempBasalArray.add(ScaledDataPoint(time, tempBasalValue.also { basal = it }, data.overviewData.basalScale)) + } + if (lastBaseBasal != 0.0) { + baseBasalArray.add(ScaledDataPoint(time, lastBaseBasal, data.overviewData.basalScale)) + baseBasalArray.add(ScaledDataPoint(time, 0.0, data.overviewData.basalScale)) + lastBaseBasal = 0.0 + } + } else { + if (baseBasalValue != lastBaseBasal) { + baseBasalArray.add(ScaledDataPoint(time, lastBaseBasal, data.overviewData.basalScale)) + baseBasalArray.add(ScaledDataPoint(time, baseBasalValue.also { basal = it }, data.overviewData.basalScale)) + lastBaseBasal = baseBasalValue + } + if (lastTempBasal != 0.0) { + tempBasalArray.add(ScaledDataPoint(time, lastTempBasal, data.overviewData.basalScale)) + tempBasalArray.add(ScaledDataPoint(time, 0.0, data.overviewData.basalScale)) + } + } + if (baseBasalValue != lastLineBasal) { + basalLineArray.add(ScaledDataPoint(time, lastLineBasal, data.overviewData.basalScale)) + basalLineArray.add(ScaledDataPoint(time, baseBasalValue, data.overviewData.basalScale)) + } + if (absoluteLineValue != lastAbsoluteLineBasal) { + absoluteBasalLineArray.add(ScaledDataPoint(time, lastAbsoluteLineBasal, data.overviewData.basalScale)) + absoluteBasalLineArray.add(ScaledDataPoint(time, basal, data.overviewData.basalScale)) + } + lastAbsoluteLineBasal = absoluteLineValue + lastLineBasal = baseBasalValue + lastTempBasal = tempBasalValue + data.overviewData.maxBasalValueFound = max(data.overviewData.maxBasalValueFound, max(tempBasalValue, baseBasalValue)) + time += 60 * 1000L + } + + // final points + basalLineArray.add(ScaledDataPoint(data.overviewData.toTime, lastLineBasal, data.overviewData.basalScale)) + baseBasalArray.add(ScaledDataPoint(data.overviewData.toTime, lastBaseBasal, data.overviewData.basalScale)) + tempBasalArray.add(ScaledDataPoint(data.overviewData.toTime, lastTempBasal, data.overviewData.basalScale)) + absoluteBasalLineArray.add(ScaledDataPoint(data.overviewData.toTime, lastAbsoluteLineBasal, data.overviewData.basalScale)) + + // create series + data.overviewData.baseBasalGraphSeries = LineGraphSeries(Array(baseBasalArray.size) { i -> baseBasalArray[i] }).also { + it.isDrawBackground = true + it.backgroundColor = rh.gac(ctx, R.attr.basebasalColor ) + it.thickness = 0 + } + data.overviewData.tempBasalGraphSeries = LineGraphSeries(Array(tempBasalArray.size) { i -> tempBasalArray[i] }).also { + it.isDrawBackground = true + it.backgroundColor = rh.gac(ctx, R.attr.tempBasalColor ) + it.thickness = 0 + } + data.overviewData.basalLineGraphSeries = LineGraphSeries(Array(basalLineArray.size) { i -> basalLineArray[i] }).also { + it.setCustomPaint(Paint().also { paint -> + paint.style = Paint.Style.STROKE + paint.strokeWidth = rh.getDisplayMetrics().scaledDensity * 2 + paint.pathEffect = DashPathEffect(floatArrayOf(2f, 4f), 0f) + paint.color = rh.gac(ctx, R.attr.basal ) + }) + } + data.overviewData.absoluteBasalGraphSeries = LineGraphSeries(Array(absoluteBasalLineArray.size) { i -> absoluteBasalLineArray[i] }).also { + it.setCustomPaint(Paint().also { absolutePaint -> + absolutePaint.style = Paint.Style.STROKE + absolutePaint.strokeWidth = rh.getDisplayMetrics().scaledDensity * 2 + absolutePaint.color =rh.gac(ctx, R.attr.basal ) + }) + } + rxBus.send(EventIobCalculationProgress(CalculationWorkflow.ProgressData.PREPARE_BASAL_DATA, 100, null)) + return Result.success() + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/workflow/PrepareBgDataWorker.kt b/app/src/main/java/info/nightscout/androidaps/workflow/PrepareBgDataWorker.kt new file mode 100644 index 0000000000..eb9941b419 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/workflow/PrepareBgDataWorker.kt @@ -0,0 +1,67 @@ +package info.nightscout.androidaps.workflow + +import android.content.Context +import androidx.work.Worker +import androidx.work.WorkerParameters +import androidx.work.workDataOf +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.database.AppRepository +import info.nightscout.androidaps.interfaces.GlucoseUnit +import info.nightscout.androidaps.interfaces.IobCobCalculator +import info.nightscout.androidaps.interfaces.Profile +import info.nightscout.androidaps.interfaces.ProfileFunction +import info.nightscout.androidaps.plugins.general.overview.OverviewData +import info.nightscout.androidaps.plugins.general.overview.graphExtensions.DataPointWithLabelInterface +import info.nightscout.androidaps.plugins.general.overview.graphExtensions.GlucoseValueDataPoint +import info.nightscout.androidaps.plugins.general.overview.graphExtensions.PointsWithLabelGraphSeries +import info.nightscout.androidaps.receivers.DataWorker +import info.nightscout.androidaps.utils.DefaultValueHelper +import info.nightscout.androidaps.utils.Round +import info.nightscout.androidaps.utils.resources.ResourceHelper +import java.util.ArrayList +import javax.inject.Inject + +class PrepareBgDataWorker( + context: Context, + params: WorkerParameters +) : Worker(context, params) { + + @Inject lateinit var dataWorker: DataWorker + @Inject lateinit var profileFunction: ProfileFunction + @Inject lateinit var rh: ResourceHelper + @Inject lateinit var defaultValueHelper: DefaultValueHelper + @Inject lateinit var repository: AppRepository + + init { + (context.applicationContext as HasAndroidInjector).androidInjector().inject(this) + } + + class PrepareBgData( + val iobCobCalculator: IobCobCalculator, // cannot be injected : HistoryBrowser uses different instance + val overviewData: OverviewData + ) + + override fun doWork(): Result { + + val data = dataWorker.pickupObject(inputData.getLong(DataWorker.STORE_KEY, -1)) as PrepareBgData? + ?: return Result.failure(workDataOf("Error" to "missing input data")) + + data.overviewData.maxBgValue = Double.MIN_VALUE + data.overviewData.bgReadingsArray = repository.compatGetBgReadingsDataFromTime(data.overviewData.fromTime, data.overviewData.toTime, false).blockingGet() + val bgListArray: MutableList = ArrayList() + for (bg in data.overviewData.bgReadingsArray) { + if (bg.timestamp < data.overviewData.fromTime || bg.timestamp > data.overviewData.toTime) continue + if (bg.value > data.overviewData.maxBgValue) data.overviewData.maxBgValue = bg.value + bgListArray.add(GlucoseValueDataPoint(bg, defaultValueHelper, profileFunction, rh)) + } + bgListArray.sortWith { o1: DataPointWithLabelInterface, o2: DataPointWithLabelInterface -> o1.x.compareTo(o2.x) } + data.overviewData.bgReadingGraphSeries = PointsWithLabelGraphSeries(Array(bgListArray.size) { i -> bgListArray[i] }) + data.overviewData.maxBgValue = Profile.fromMgdlToUnits(data.overviewData.maxBgValue, profileFunction.getUnits()) + if (defaultValueHelper.determineHighLine() > data.overviewData.maxBgValue) data.overviewData.maxBgValue = defaultValueHelper.determineHighLine() + data.overviewData.maxBgValue = addUpperChartMargin(data.overviewData.maxBgValue) + return Result.success() + } + + private fun addUpperChartMargin(maxBgValue: Double) = + if (profileFunction.getUnits() == GlucoseUnit.MGDL) Round.roundTo(maxBgValue, 40.0) + 80 else Round.roundTo(maxBgValue, 2.0) + 4 +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/workflow/PrepareBucketedDataWorker.kt b/app/src/main/java/info/nightscout/androidaps/workflow/PrepareBucketedDataWorker.kt new file mode 100644 index 0000000000..54e98ec316 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/workflow/PrepareBucketedDataWorker.kt @@ -0,0 +1,57 @@ +package info.nightscout.androidaps.workflow + +import android.content.Context +import androidx.work.Worker +import androidx.work.WorkerParameters +import androidx.work.workDataOf +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.interfaces.IobCobCalculator +import info.nightscout.androidaps.interfaces.ProfileFunction +import info.nightscout.androidaps.plugins.general.overview.OverviewData +import info.nightscout.androidaps.plugins.general.overview.graphExtensions.DataPointWithLabelInterface +import info.nightscout.androidaps.plugins.general.overview.graphExtensions.InMemoryGlucoseValueDataPoint +import info.nightscout.androidaps.plugins.general.overview.graphExtensions.PointsWithLabelGraphSeries +import info.nightscout.androidaps.receivers.DataWorker +import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.shared.logging.AAPSLogger +import javax.inject.Inject + +class PrepareBucketedDataWorker( + context: Context, + params: WorkerParameters +) : Worker(context, params) { + + @Inject lateinit var dataWorker: DataWorker + @Inject lateinit var profileFunction: ProfileFunction + @Inject lateinit var rh: ResourceHelper + @Inject lateinit var aapsLogger: AAPSLogger + + init { + (context.applicationContext as HasAndroidInjector).androidInjector().inject(this) + } + + class PrepareBucketedData( + val iobCobCalculator: IobCobCalculator, // cannot be injected : HistoryBrowser uses different instance + val overviewData: OverviewData + ) + + override fun doWork(): Result { + + val data = dataWorker.pickupObject(inputData.getLong(DataWorker.STORE_KEY, -1)) as PrepareBucketedData? + ?: return Result.failure(workDataOf("Error" to "missing input data")) + + val bucketedData = data.iobCobCalculator.ads.getBucketedDataTableCopy() ?: return Result.success() + if (bucketedData.isEmpty()) { + aapsLogger.debug("No bucketed data.") + return Result.success() + } + val bucketedListArray: MutableList = ArrayList() + for (inMemoryGlucoseValue in bucketedData) { + if (inMemoryGlucoseValue.timestamp < data.overviewData.fromTime || inMemoryGlucoseValue.timestamp > data.overviewData.toTime) continue + bucketedListArray.add(InMemoryGlucoseValueDataPoint(inMemoryGlucoseValue, profileFunction, rh)) + } + bucketedListArray.sortWith { o1: DataPointWithLabelInterface, o2: DataPointWithLabelInterface -> o1.x.compareTo(o2.x) } + data.overviewData.bucketedGraphSeries = PointsWithLabelGraphSeries(Array(bucketedListArray.size) { i -> bucketedListArray[i] }) + return Result.success() + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/workflow/PrepareIobAutosensGraphDataWorker.kt b/app/src/main/java/info/nightscout/androidaps/workflow/PrepareIobAutosensGraphDataWorker.kt new file mode 100644 index 0000000000..cc81bf3593 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/workflow/PrepareIobAutosensGraphDataWorker.kt @@ -0,0 +1,285 @@ +package info.nightscout.androidaps.workflow + +import android.content.Context +import android.graphics.DashPathEffect +import android.graphics.Paint +import androidx.work.Worker +import androidx.work.WorkerParameters +import androidx.work.workDataOf +import com.jjoe64.graphview.series.BarGraphSeries +import com.jjoe64.graphview.series.LineGraphSeries +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.R +import info.nightscout.androidaps.data.IobTotal +import info.nightscout.androidaps.database.AppRepository +import info.nightscout.androidaps.database.ValueWrapper +import info.nightscout.androidaps.interfaces.IobCobCalculator +import info.nightscout.androidaps.interfaces.ProfileFunction +import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults +import info.nightscout.androidaps.plugins.bus.RxBus +import info.nightscout.androidaps.plugins.general.overview.OverviewData +import info.nightscout.androidaps.plugins.general.overview.OverviewMenus +import info.nightscout.androidaps.plugins.general.overview.OverviewPlugin +import info.nightscout.androidaps.plugins.general.overview.graphExtensions.DataPointWithLabelInterface +import info.nightscout.androidaps.plugins.general.overview.graphExtensions.FixedLineGraphSeries +import info.nightscout.androidaps.plugins.general.overview.graphExtensions.PointsWithLabelGraphSeries +import info.nightscout.androidaps.plugins.general.overview.graphExtensions.ScaledDataPoint +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress +import info.nightscout.androidaps.receivers.DataWorker +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.DecimalFormatter +import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.androidaps.utils.resources.getThemeColor +import info.nightscout.shared.logging.AAPSLogger +import info.nightscout.shared.logging.LTag +import java.util.ArrayList +import javax.inject.Inject +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.min + +class PrepareIobAutosensGraphDataWorker( + context: Context, + params: WorkerParameters +) : Worker(context, params) { + + @Inject lateinit var dataWorker: DataWorker + @Inject lateinit var dateUtil: DateUtil + @Inject lateinit var profileFunction: ProfileFunction + @Inject lateinit var rh: ResourceHelper + @Inject lateinit var overviewMenus: OverviewMenus + @Inject lateinit var aapsLogger: AAPSLogger + @Inject lateinit var repository: AppRepository + @Inject lateinit var rxBus: RxBus + var ctx: Context + init { + (context.applicationContext as HasAndroidInjector).androidInjector().inject(this) + ctx = rh.getThemedCtx(context) + } + + class PrepareIobAutosensData( + val iobCobCalculator: IobCobCalculator, // cannot be injected : HistoryBrowser uses different instance + val overviewData: OverviewData + ) + + override fun doWork(): Result { + val data = dataWorker.pickupObject(inputData.getLong(DataWorker.STORE_KEY, -1)) as PrepareIobAutosensData? + ?: return Result.failure(workDataOf("Error" to "missing input data")) + rxBus.send(EventIobCalculationProgress(CalculationWorkflow.ProgressData.PREPARE_IOB_AUTOSENS_DATA, 0, null)) + val iobArray: MutableList = ArrayList() + val absIobArray: MutableList = ArrayList() + data.overviewData.maxIobValueFound = Double.MIN_VALUE + var lastIob = 0.0 + var absLastIob = 0.0 + var time = data.overviewData.fromTime + + val minFailOverActiveList: MutableList = ArrayList() + val cobArray: MutableList = ArrayList() + data.overviewData.maxCobValueFound = Double.MIN_VALUE + var lastCob = 0 + + val actArrayHist: MutableList = ArrayList() + val actArrayPrediction: MutableList = ArrayList() + val now = dateUtil.now().toDouble() + data.overviewData.maxIAValue = 0.0 + + val bgiArrayHist: MutableList = ArrayList() + val bgiArrayPrediction: MutableList = ArrayList() + data.overviewData.maxBGIValue = Double.MIN_VALUE + + val devArray: MutableList = ArrayList() + data.overviewData.maxDevValueFound = Double.MIN_VALUE + + val ratioArray: MutableList = ArrayList() + data.overviewData.maxRatioValueFound = 5.0 //even if sens data equals 0 for all the period, minimum scale is between 95% and 105% + data.overviewData.minRatioValueFound = -5.0 + + val dsMaxArray: MutableList = ArrayList() + val dsMinArray: MutableList = ArrayList() + data.overviewData.maxFromMaxValueFound = Double.MIN_VALUE + data.overviewData.maxFromMinValueFound = Double.MIN_VALUE + + val adsData = data.iobCobCalculator.ads.clone() + + while (time <= data.overviewData.toTime) { + val progress = (time - data.overviewData.fromTime).toDouble() / (data.overviewData.toTime - data.overviewData.fromTime) * 100.0 + rxBus.send(EventIobCalculationProgress(CalculationWorkflow.ProgressData.PREPARE_IOB_AUTOSENS_DATA, progress.toInt(), null)) + val profile = profileFunction.getProfile(time) + if (profile == null) { + time += 5 * 60 * 1000L + continue + } + // IOB + val iob = data.iobCobCalculator.calculateFromTreatmentsAndTemps(time, profile) + val baseBasalIob = data.iobCobCalculator.calculateAbsoluteIobFromBaseBasals(time) + val absIob = IobTotal.combine(iob, baseBasalIob) + val autosensData = adsData.getAutosensDataAtTime(time) + if (abs(lastIob - iob.iob) > 0.02) { + if (abs(lastIob - iob.iob) > 0.2) iobArray.add(ScaledDataPoint(time, lastIob, data.overviewData.iobScale)) + iobArray.add(ScaledDataPoint(time, iob.iob, data.overviewData.iobScale)) + data.overviewData.maxIobValueFound = maxOf(data.overviewData.maxIobValueFound, abs(iob.iob)) + lastIob = iob.iob + } + if (abs(absLastIob - absIob.iob) > 0.02) { + if (abs(absLastIob - absIob.iob) > 0.2) absIobArray.add(ScaledDataPoint(time, absLastIob, data.overviewData.iobScale)) + absIobArray.add(ScaledDataPoint(time, absIob.iob, data.overviewData.iobScale)) + data.overviewData.maxIobValueFound = maxOf(data.overviewData.maxIobValueFound, abs(absIob.iob)) + absLastIob = absIob.iob + } + + // COB + if (autosensData != null) { + val cob = autosensData.cob.toInt() + if (cob != lastCob) { + if (autosensData.carbsFromBolus > 0) cobArray.add(ScaledDataPoint(time, lastCob.toDouble(), data.overviewData.cobScale)) + cobArray.add(ScaledDataPoint(time, cob.toDouble(), data.overviewData.cobScale)) + data.overviewData.maxCobValueFound = max(data.overviewData.maxCobValueFound, cob.toDouble()) + lastCob = cob + } + if (autosensData.failOverToMinAbsorptionRate) { + autosensData.scale = data.overviewData.cobScale + autosensData.chartTime = time + minFailOverActiveList.add(autosensData) + } + } + + // ACTIVITY + if (time <= now) actArrayHist.add(ScaledDataPoint(time, iob.activity, data.overviewData.actScale)) + else actArrayPrediction.add(ScaledDataPoint(time, iob.activity, data.overviewData.actScale)) + data.overviewData.maxIAValue = max(data.overviewData.maxIAValue, abs(iob.activity)) + + // BGI + val devBgiScale = overviewMenus.isEnabledIn(OverviewMenus.CharType.DEV) == overviewMenus.isEnabledIn(OverviewMenus.CharType.BGI) + val deviation = if (devBgiScale) autosensData?.deviation ?: 0.0 else 0.0 + val bgi: Double = iob.activity * profile.getIsfMgdl(time) * 5.0 + if (time <= now) bgiArrayHist.add(ScaledDataPoint(time, bgi, data.overviewData.bgiScale)) + else bgiArrayPrediction.add(ScaledDataPoint(time, bgi, data.overviewData.bgiScale)) + data.overviewData.maxBGIValue = max(data.overviewData.maxBGIValue, max(abs(bgi), deviation)) + + // DEVIATIONS + if (autosensData != null) { + var color = rh.gac( ctx, R.attr.deviationblackColor) // "=" + if (autosensData.type == "" || autosensData.type == "non-meal") { + if (autosensData.pastSensitivity == "C") color = rh.gac( ctx, R.attr.deviationgreyColor) + if (autosensData.pastSensitivity == "+") color = rh.gac( ctx, R.attr.deviationgreenColor) + if (autosensData.pastSensitivity == "-") color = rh.gac( ctx, R.attr.deviationredColor) + } else if (autosensData.type == "uam") { + color = rh.gac( ctx, R.attr.uamColor) + } else if (autosensData.type == "csf") { + color = rh.gac( ctx, R.attr.deviationgreyColor) + } + devArray.add(OverviewPlugin.DeviationDataPoint(time.toDouble(), autosensData.deviation, color, data.overviewData.devScale)) + data.overviewData.maxDevValueFound = maxOf(data.overviewData.maxDevValueFound, abs(autosensData.deviation), abs(bgi)) + } + + // RATIO + if (autosensData != null) { + ratioArray.add(ScaledDataPoint(time, 100.0 * (autosensData.autosensResult.ratio - 1), data.overviewData.ratioScale)) + data.overviewData.maxRatioValueFound = max(data.overviewData.maxRatioValueFound, 100.0 * (autosensData.autosensResult.ratio - 1)) + data.overviewData.minRatioValueFound = min(data.overviewData.minRatioValueFound, 100.0 * (autosensData.autosensResult.ratio - 1)) + } + + // DEV SLOPE + if (autosensData != null) { + dsMaxArray.add(ScaledDataPoint(time, autosensData.slopeFromMaxDeviation, data.overviewData.dsMaxScale)) + dsMinArray.add(ScaledDataPoint(time, autosensData.slopeFromMinDeviation, data.overviewData.dsMinScale)) + data.overviewData.maxFromMaxValueFound = max(data.overviewData.maxFromMaxValueFound, abs(autosensData.slopeFromMaxDeviation)) + data.overviewData.maxFromMinValueFound = max(data.overviewData.maxFromMinValueFound, abs(autosensData.slopeFromMinDeviation)) + } + + time += 5 * 60 * 1000L + } + // IOB + data.overviewData.iobSeries = FixedLineGraphSeries(Array(iobArray.size) { i -> iobArray[i] }).also { + it.isDrawBackground = true + it.backgroundColor = -0x7f000001 and rh.gac( ctx, R.attr.iobColor) //50% + it.color = rh.gac( ctx, R.attr.iobColor) + it.thickness = 3 + } + data.overviewData.absIobSeries = FixedLineGraphSeries(Array(absIobArray.size) { i -> absIobArray[i] }).also { + it.isDrawBackground = true + it.backgroundColor = -0x7f000001 and rh.gac( ctx, R.attr.iobColor) //50% + it.color = rh.gac( ctx, R.attr.iobColor) + it.thickness = 3 + } + + if (overviewMenus.setting[0][OverviewMenus.CharType.PRE.ordinal]) { + val autosensData = adsData.getLastAutosensData("GraphData", aapsLogger, dateUtil) + val lastAutosensResult = autosensData?.autosensResult ?: AutosensResult() + val isTempTarget = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet() is ValueWrapper.Existing + val iobPrediction: MutableList = ArrayList() + val iobPredictionArray = data.iobCobCalculator.calculateIobArrayForSMB(lastAutosensResult, SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget) + for (i in iobPredictionArray) { + iobPrediction.add(i.setColor(rh.gac( ctx, R.attr.iobPredASColor))) + data.overviewData.maxIobValueFound = max(data.overviewData.maxIobValueFound, abs(i.iob)) + } + data.overviewData.iobPredictions1Series = PointsWithLabelGraphSeries(Array(iobPrediction.size) { i -> iobPrediction[i] }) + aapsLogger.debug(LTag.AUTOSENS, "IOB prediction for AS=" + DecimalFormatter.to2Decimal(lastAutosensResult.ratio) + ": " + data.iobCobCalculator.iobArrayToString(iobPredictionArray)) + } else { + data.overviewData.iobPredictions1Series = PointsWithLabelGraphSeries() + } + + // COB + data.overviewData.cobSeries = FixedLineGraphSeries(Array(cobArray.size) { i -> cobArray[i] }).also { + it.isDrawBackground = true + it.backgroundColor = -0x7f000001 and rh.gac( ctx, R.attr.cobColor) //50% + it.color = rh.gac( ctx, R.attr.cobColor) + it.thickness = 3 + } + data.overviewData.cobMinFailOverSeries = PointsWithLabelGraphSeries(Array(minFailOverActiveList.size) { i -> minFailOverActiveList[i] }) + + // ACTIVITY + data.overviewData.activitySeries = FixedLineGraphSeries(Array(actArrayHist.size) { i -> actArrayHist[i] }).also { + it.isDrawBackground = false + it.color = rh.gac( ctx, R.attr.activityColor) + it.thickness = 3 + } + data.overviewData.activityPredictionSeries = FixedLineGraphSeries(Array(actArrayPrediction.size) { i -> actArrayPrediction[i] }).also { + it.setCustomPaint(Paint().also { paint -> + paint.style = Paint.Style.STROKE + paint.strokeWidth = 3f + paint.pathEffect = DashPathEffect(floatArrayOf(4f, 4f), 0f) + paint.color = rh.gac( ctx, R.attr.activityColor) + }) + } + + // BGI + data.overviewData.minusBgiSeries = FixedLineGraphSeries(Array(bgiArrayHist.size) { i -> bgiArrayHist[i] }).also { + it.isDrawBackground = false + it.color = rh.gac( ctx, R.attr.bgiColor) + it.thickness = 3 + } + data.overviewData.minusBgiHistSeries = FixedLineGraphSeries(Array(bgiArrayPrediction.size) { i -> bgiArrayPrediction[i] }).also { + it.setCustomPaint(Paint().also { paint -> + paint.style = Paint.Style.STROKE + paint.strokeWidth = 3f + paint.pathEffect = DashPathEffect(floatArrayOf(4f, 4f), 0f) + paint.color = rh.gac( ctx, R.attr.bgiColor) + }) + } + + // DEVIATIONS + data.overviewData.deviationsSeries = BarGraphSeries(Array(devArray.size) { i -> devArray[i] }).also { + it.setValueDependentColor { data: OverviewPlugin.DeviationDataPoint -> data.color } + } + + // RATIO + data.overviewData.ratioSeries = LineGraphSeries(Array(ratioArray.size) { i -> ratioArray[i] }).also { + it.color = rh.gac( ctx, R.attr.ratioColor) + it.thickness = 3 + } + + // DEV SLOPE + data.overviewData.dsMaxSeries = LineGraphSeries(Array(dsMaxArray.size) { i -> dsMaxArray[i] }).also { + it.color = rh.gac( ctx, R.attr.devslopeposColor) + it.thickness = 3 + } + data.overviewData.dsMinSeries = LineGraphSeries(Array(dsMinArray.size) { i -> dsMinArray[i] }).also { + it.color = rh.gac( ctx, R.attr.devslopenegColor) + it.thickness = 3 + } + rxBus.send(EventIobCalculationProgress(CalculationWorkflow.ProgressData.PREPARE_IOB_AUTOSENS_DATA, 100, null)) + return Result.success() + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/workflow/PreparePredictionsWorker.kt b/app/src/main/java/info/nightscout/androidaps/workflow/PreparePredictionsWorker.kt new file mode 100644 index 0000000000..ff57c4260c --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/workflow/PreparePredictionsWorker.kt @@ -0,0 +1,95 @@ +package info.nightscout.androidaps.workflow + +import android.content.Context +import androidx.work.Worker +import androidx.work.WorkerParameters +import androidx.work.workDataOf +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.database.AppRepository +import info.nightscout.androidaps.interfaces.Config +import info.nightscout.androidaps.interfaces.Loop +import info.nightscout.androidaps.interfaces.ProfileFunction +import info.nightscout.androidaps.plugins.bus.RxBus +import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus +import info.nightscout.androidaps.plugins.general.overview.OverviewData +import info.nightscout.androidaps.plugins.general.overview.OverviewMenus +import info.nightscout.androidaps.plugins.general.overview.graphExtensions.DataPointWithLabelInterface +import info.nightscout.androidaps.plugins.general.overview.graphExtensions.GlucoseValueDataPoint +import info.nightscout.androidaps.plugins.general.overview.graphExtensions.PointsWithLabelGraphSeries +import info.nightscout.androidaps.receivers.DataWorker +import info.nightscout.androidaps.utils.DefaultValueHelper +import info.nightscout.androidaps.utils.T +import info.nightscout.androidaps.utils.resources.ResourceHelper +import java.util.* +import javax.inject.Inject +import kotlin.math.ceil +import kotlin.math.max +import kotlin.math.min + +class PreparePredictionsWorker( + context: Context, + params: WorkerParameters +) : Worker(context, params) { + + @Inject lateinit var injector: HasAndroidInjector + @Inject lateinit var overviewData: OverviewData + @Inject lateinit var repository: AppRepository + @Inject lateinit var rxBus: RxBus + @Inject lateinit var config: Config + @Inject lateinit var nsDeviceStatus: NSDeviceStatus + @Inject lateinit var loop: Loop + @Inject lateinit var overviewMenus: OverviewMenus + @Inject lateinit var dataWorker: DataWorker + @Inject lateinit var defaultValueHelper: DefaultValueHelper + @Inject lateinit var profileFunction: ProfileFunction + @Inject lateinit var rh: ResourceHelper + + init { + (context.applicationContext as HasAndroidInjector).androidInjector().inject(this) + } + + class PreparePredictionsData( + val overviewData: OverviewData + ) + + override fun doWork(): Result { + val data = dataWorker.pickupObject(inputData.getLong(DataWorker.STORE_KEY, -1)) as PreparePredictionsData? + ?: return Result.failure(workDataOf("Error" to "missing input data")) + + val apsResult = if (config.APS) loop.lastRun?.constraintsProcessed else nsDeviceStatus.getAPSResult(injector) + val predictionsAvailable = if (config.APS) loop.lastRun?.request?.hasPredictions == true else config.NSCLIENT + val menuChartSettings = overviewMenus.setting + // align to hours + val calendar = Calendar.getInstance().also { + it.timeInMillis = System.currentTimeMillis() + it[Calendar.MILLISECOND] = 0 + it[Calendar.SECOND] = 0 + it[Calendar.MINUTE] = 0 + it.add(Calendar.HOUR, 1) + } + if (predictionsAvailable && apsResult != null && menuChartSettings[0][OverviewMenus.CharType.PRE.ordinal]) { + var predictionHours = (ceil(apsResult.latestPredictionsTime - System.currentTimeMillis().toDouble()) / (60 * 60 * 1000)).toInt() + predictionHours = min(2, predictionHours) + predictionHours = max(0, predictionHours) + val hoursToFetch = data.overviewData.rangeToDisplay - predictionHours + data.overviewData.toTime = calendar.timeInMillis + 100000 // little bit more to avoid wrong rounding - GraphView specific + data.overviewData.fromTime = data.overviewData.toTime - T.hours(hoursToFetch.toLong()).msecs() + data.overviewData.endTime = data.overviewData.toTime + T.hours(predictionHours.toLong()).msecs() + } else { + data.overviewData.toTime = calendar.timeInMillis + 100000 // little bit more to avoid wrong rounding - GraphView specific + data.overviewData.fromTime = data.overviewData.toTime - T.hours(data.overviewData.rangeToDisplay.toLong()).msecs() + data.overviewData.endTime = data.overviewData.toTime + } + + val bgListArray: MutableList = ArrayList() + val predictions: MutableList? = apsResult?.predictions + ?.map { bg -> GlucoseValueDataPoint(bg, defaultValueHelper, profileFunction, rh) } + ?.toMutableList() + if (predictions != null) { + predictions.sortWith { o1: GlucoseValueDataPoint, o2: GlucoseValueDataPoint -> o1.x.compareTo(o2.x) } + for (prediction in predictions) if (prediction.data.value >= 40) bgListArray.add(prediction) + } + data.overviewData.predictionsGraphSeries = PointsWithLabelGraphSeries(Array(bgListArray.size) { i -> bgListArray[i] }) + return Result.success() + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/workflow/PrepareTemporaryTargetDataWorker.kt b/app/src/main/java/info/nightscout/androidaps/workflow/PrepareTemporaryTargetDataWorker.kt new file mode 100644 index 0000000000..49bedb98e2 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/workflow/PrepareTemporaryTargetDataWorker.kt @@ -0,0 +1,87 @@ +package info.nightscout.androidaps.workflow + +import android.content.Context +import androidx.core.content.ContextCompat +import androidx.work.Worker +import androidx.work.WorkerParameters +import androidx.work.workDataOf +import com.jjoe64.graphview.series.DataPoint +import com.jjoe64.graphview.series.LineGraphSeries +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.R +import info.nightscout.androidaps.database.AppRepository +import info.nightscout.androidaps.database.ValueWrapper +import info.nightscout.androidaps.extensions.target +import info.nightscout.androidaps.interfaces.Loop +import info.nightscout.androidaps.interfaces.Profile +import info.nightscout.androidaps.interfaces.ProfileFunction +import info.nightscout.androidaps.plugins.bus.RxBus +import info.nightscout.androidaps.plugins.general.overview.OverviewData +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress +import info.nightscout.androidaps.receivers.DataWorker +import info.nightscout.androidaps.utils.resources.ResourceHelper +import javax.inject.Inject +import kotlin.math.max + +class PrepareTemporaryTargetDataWorker( + context: Context, + params: WorkerParameters +) : Worker(context, params) { + + @Inject lateinit var dataWorker: DataWorker + @Inject lateinit var profileFunction: ProfileFunction + @Inject lateinit var rh: ResourceHelper + @Inject lateinit var repository: AppRepository + @Inject lateinit var loop: Loop + @Inject lateinit var rxBus: RxBus + var ctx: Context + init { + (context.applicationContext as HasAndroidInjector).androidInjector().inject(this) + ctx = rh.getThemedCtx(context) + } + + class PrepareTemporaryTargetData( + val overviewData: OverviewData + ) + + override fun doWork(): Result { + + val data = dataWorker.pickupObject(inputData.getLong(DataWorker.STORE_KEY, -1)) as PrepareTemporaryTargetData? + ?: return Result.failure(workDataOf("Error" to "missing input data")) + + rxBus.send(EventIobCalculationProgress(CalculationWorkflow.ProgressData.PREPARE_TEMPORARY_TARGET_DATA, 0, null)) + val profile = profileFunction.getProfile() ?: return Result.failure(workDataOf("Error" to "missing profile")) + val units = profileFunction.getUnits() + var toTime = data.overviewData.toTime + val targetsSeriesArray: MutableList = ArrayList() + var lastTarget = -1.0 + loop.lastRun?.constraintsProcessed?.let { toTime = max(it.latestPredictionsTime, toTime) } + var time = data.overviewData.fromTime + while (time < toTime) { + val progress = (time - data.overviewData.fromTime).toDouble() / (data.overviewData.toTime - data.overviewData.fromTime) * 100.0 + rxBus.send(EventIobCalculationProgress(CalculationWorkflow.ProgressData.PREPARE_TEMPORARY_TARGET_DATA, progress.toInt(), null)) + val tt = repository.getTemporaryTargetActiveAt(time).blockingGet() + val value: Double = if (tt is ValueWrapper.Existing) { + Profile.fromMgdlToUnits(tt.value.target(), units) + } else { + Profile.fromMgdlToUnits((profile.getTargetLowMgdl(time) + profile.getTargetHighMgdl(time)) / 2, units) + } + if (lastTarget != value) { + if (lastTarget != -1.0) targetsSeriesArray.add(DataPoint(time.toDouble(), lastTarget)) + targetsSeriesArray.add(DataPoint(time.toDouble(), value)) + } + lastTarget = value + time += 5 * 60 * 1000L + } + // final point + targetsSeriesArray.add(DataPoint(toTime.toDouble(), lastTarget)) + // create series + data.overviewData.temporaryTargetSeries = LineGraphSeries(Array(targetsSeriesArray.size) { i -> targetsSeriesArray[i] }).also { + it.isDrawBackground = false + it.color = rh.gac(ctx, R.attr.tempTargetBackgroundColor ) + it.thickness = 2 + } + rxBus.send(EventIobCalculationProgress(CalculationWorkflow.ProgressData.PREPARE_TEMPORARY_TARGET_DATA, 100, null)) + return Result.success() + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/workflow/PrepareTreatmentsDataWorker.kt b/app/src/main/java/info/nightscout/androidaps/workflow/PrepareTreatmentsDataWorker.kt new file mode 100644 index 0000000000..f56e2056e6 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/workflow/PrepareTreatmentsDataWorker.kt @@ -0,0 +1,143 @@ +package info.nightscout.androidaps.workflow + +import android.content.Context +import androidx.work.Worker +import androidx.work.WorkerParameters +import androidx.work.workDataOf +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.database.AppRepository +import info.nightscout.androidaps.database.entities.Bolus +import info.nightscout.androidaps.database.entities.TherapyEvent +import info.nightscout.androidaps.interfaces.ActivePlugin +import info.nightscout.androidaps.interfaces.GlucoseUnit +import info.nightscout.androidaps.interfaces.Profile +import info.nightscout.androidaps.interfaces.ProfileFunction +import info.nightscout.androidaps.plugins.bus.RxBus +import info.nightscout.androidaps.plugins.general.overview.OverviewData +import info.nightscout.androidaps.plugins.general.overview.graphExtensions.* +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress +import info.nightscout.androidaps.receivers.DataWorker +import info.nightscout.androidaps.utils.DefaultValueHelper +import info.nightscout.androidaps.utils.Round +import info.nightscout.androidaps.utils.T +import info.nightscout.androidaps.utils.Translator +import info.nightscout.androidaps.utils.resources.ResourceHelper +import javax.inject.Inject + +class PrepareTreatmentsDataWorker( + context: Context, + params: WorkerParameters +) : Worker(context, params) { + + @Inject lateinit var dataWorker: DataWorker + @Inject lateinit var profileFunction: ProfileFunction + @Inject lateinit var rh: ResourceHelper + @Inject lateinit var rxBus: RxBus + @Inject lateinit var translator: Translator + @Inject lateinit var activePlugin: ActivePlugin + @Inject lateinit var repository: AppRepository + @Inject lateinit var defaultValueHelper: DefaultValueHelper + + init { + (context.applicationContext as HasAndroidInjector).androidInjector().inject(this) + } + + class PrepareTreatmentsData( + val overviewData: OverviewData + ) + + override fun doWork(): Result { + + val data = dataWorker.pickupObject(inputData.getLong(DataWorker.STORE_KEY, -1)) as PrepareTreatmentsData? + ?: return Result.failure(workDataOf("Error" to "missing input data")) + + rxBus.send(EventIobCalculationProgress(CalculationWorkflow.ProgressData.PREPARE_TREATMENTS_DATA, 0, null)) + data.overviewData.maxTreatmentsValue = 0.0 + val filteredTreatments: MutableList = ArrayList() + val filteredTherapyEvents: MutableList = ArrayList() + + repository.getBolusesDataFromTimeToTime(data.overviewData.fromTime, data.overviewData.endTime, true).blockingGet() + .map { BolusDataPoint(it, rh, activePlugin, defaultValueHelper) } + .filter { it.data.type == Bolus.Type.NORMAL || it.data.type == Bolus.Type.SMB } + .forEach { + it.y = getNearestBg(data.overviewData, it.x.toLong()) + filteredTreatments.add(it) + } + repository.getCarbsDataFromTimeToTimeExpanded(data.overviewData.fromTime, data.overviewData.endTime, true).blockingGet() + .map { CarbsDataPoint(it, rh) } + .forEach { + it.y = getNearestBg(data.overviewData, it.x.toLong()) + filteredTreatments.add(it) + } + + // ProfileSwitch + repository.getEffectiveProfileSwitchDataFromTimeToTime(data.overviewData.fromTime, data.overviewData.endTime, true).blockingGet() + .map { EffectiveProfileSwitchDataPoint(it, rh) } + .forEach(filteredTreatments::add) + + // OfflineEvent + repository.getOfflineEventDataFromTimeToTime(data.overviewData.fromTime, data.overviewData.endTime, true).blockingGet() + .map { + TherapyEventDataPoint( + TherapyEvent(timestamp = it.timestamp, duration = it.duration, type = TherapyEvent.Type.APS_OFFLINE, glucoseUnit = TherapyEvent.GlucoseUnit.MMOL), + rh, + profileFunction, + translator + ) + } + .forEach(filteredTreatments::add) + + // Extended bolus + if (!activePlugin.activePump.isFakingTempsByExtendedBoluses) { + repository.getExtendedBolusDataFromTimeToTime(data.overviewData.fromTime, data.overviewData.endTime, true).blockingGet() + .map { ExtendedBolusDataPoint(it, rh) } + .filter { it.duration != 0L } + .forEach { + it.y = getNearestBg(data.overviewData, it.x.toLong()) + filteredTreatments.add(it) + } + } + + // Careportal + repository.compatGetTherapyEventDataFromToTime(data.overviewData.fromTime - T.hours(6).msecs(), data.overviewData.endTime).blockingGet() + .map { TherapyEventDataPoint(it, rh, profileFunction, translator) } + .filterTimeframe(data.overviewData.fromTime, data.overviewData.endTime) + .forEach { + if (it.y == 0.0) it.y = getNearestBg(data.overviewData, it.x.toLong()) + filteredTherapyEvents.add(it) + } + + // increase maxY if a treatment forces it's own height that's higher than a BG value + filteredTreatments.map { it.y } + .maxOrNull() + ?.let(::addUpperChartMargin) + ?.let { data.overviewData.maxTreatmentsValue = maxOf(data.overviewData.maxTreatmentsValue, it) } + filteredTherapyEvents.map { it.y } + .maxOrNull() + ?.let(::addUpperChartMargin) + ?.let { data.overviewData.maxTherapyEventValue = maxOf(data.overviewData.maxTherapyEventValue, it) } + + data.overviewData.treatmentsSeries = PointsWithLabelGraphSeries(filteredTreatments.toTypedArray()) + data.overviewData.therapyEventSeries = PointsWithLabelGraphSeries(filteredTherapyEvents.toTypedArray()) + + rxBus.send(EventIobCalculationProgress(CalculationWorkflow.ProgressData.PREPARE_TREATMENTS_DATA, 100, null)) + return Result.success() + } + + private fun addUpperChartMargin(maxBgValue: Double) = + if (profileFunction.getUnits() == GlucoseUnit.MGDL) Round.roundTo(maxBgValue, 40.0) + 80 else Round.roundTo(maxBgValue, 2.0) + 4 + + private fun getNearestBg(overviewData: OverviewData, date: Long): Double { + overviewData.bgReadingsArray.let { bgReadingsArray -> + for (reading in bgReadingsArray) { + if (reading.timestamp > date) continue + return Profile.fromMgdlToUnits(reading.value, profileFunction.getUnits()) + } + return if (bgReadingsArray.isNotEmpty()) Profile.fromMgdlToUnits(bgReadingsArray[0].value, profileFunction.getUnits()) + else Profile.fromMgdlToUnits(100.0, profileFunction.getUnits()) + } + } + + private fun List.filterTimeframe(fromTime: Long, endTime: Long): List = + filter { it.x + it.duration >= fromTime && it.x <= endTime } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/workflow/UpdateGraphWorker.kt b/app/src/main/java/info/nightscout/androidaps/workflow/UpdateGraphWorker.kt new file mode 100644 index 0000000000..3038afcd6b --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/workflow/UpdateGraphWorker.kt @@ -0,0 +1,31 @@ +package info.nightscout.androidaps.workflow + +import android.content.Context +import androidx.work.Worker +import androidx.work.WorkerParameters +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.plugins.bus.RxBus +import info.nightscout.androidaps.plugins.general.overview.OverviewPlugin +import info.nightscout.androidaps.plugins.general.overview.events.EventUpdateOverviewGraph +import javax.inject.Inject + +class UpdateGraphWorker( + context: Context, + params: WorkerParameters +) : Worker(context, params) { + + @Inject lateinit var rxBus: RxBus + @Inject lateinit var overviewPlugin: OverviewPlugin + + init { + (context.applicationContext as HasAndroidInjector).androidInjector().inject(this) + } + + override fun doWork(): Result { + if (inputData.getString(CalculationWorkflow.JOB) == CalculationWorkflow.MAIN_CALCULATION) + overviewPlugin.overviewBus.send(EventUpdateOverviewGraph("UpdateGraphWorker")) + else + rxBus.send(EventUpdateOverviewGraph("UpdateGraphWorker")) + return Result.success() + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/workflow/UpdateIobCobSensWorker.kt b/app/src/main/java/info/nightscout/androidaps/workflow/UpdateIobCobSensWorker.kt new file mode 100644 index 0000000000..5f611e130c --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/workflow/UpdateIobCobSensWorker.kt @@ -0,0 +1,30 @@ +package info.nightscout.androidaps.workflow + +import android.content.Context +import androidx.work.Worker +import androidx.work.WorkerParameters +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.plugins.bus.RxBus +import info.nightscout.androidaps.plugins.general.overview.OverviewPlugin +import info.nightscout.androidaps.plugins.general.overview.events.EventUpdateOverviewIobCob +import info.nightscout.androidaps.plugins.general.overview.events.EventUpdateOverviewSensitivity +import javax.inject.Inject + +class UpdateIobCobSensWorker( + context: Context, + params: WorkerParameters +) : Worker(context, params) { + + @Inject lateinit var rxBus: RxBus + @Inject lateinit var overviewPlugin: OverviewPlugin + + init { + (context.applicationContext as HasAndroidInjector).androidInjector().inject(this) + } + + override fun doWork(): Result { + overviewPlugin.overviewBus.send(EventUpdateOverviewIobCob("UpdateIobCobSensWorker")) + overviewPlugin.overviewBus.send(EventUpdateOverviewSensitivity("UpdateIobCobSensWorker")) + return Result.success() + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable-nodpi/widget_preview.png b/app/src/main/res/drawable-nodpi/widget_preview.png new file mode 100644 index 0000000000..53f2854fa4 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/widget_preview.png differ diff --git a/app/src/main/res/drawable-v21/widget_background.xml b/app/src/main/res/drawable-v21/widget_background.xml new file mode 100644 index 0000000000..47e7ec2a04 --- /dev/null +++ b/app/src/main/res/drawable-v21/widget_background.xml @@ -0,0 +1,11 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/widget_inner_view_background.xml b/app/src/main/res/drawable-v21/widget_inner_view_background.xml new file mode 100644 index 0000000000..e1b8cf7827 --- /dev/null +++ b/app/src/main/res/drawable-v21/widget_inner_view_background.xml @@ -0,0 +1,11 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_actions_cancelextbolus.xml b/app/src/main/res/drawable/ic_actions_cancelextbolus.xml index 913b1b9aeb..92998ff78e 100644 --- a/app/src/main/res/drawable/ic_actions_cancelextbolus.xml +++ b/app/src/main/res/drawable/ic_actions_cancelextbolus.xml @@ -5,14 +5,14 @@ android:viewportHeight="24"> + android:fillColor="?attr/extBolusStopColor"/> + android:fillColor="?attr/extBolusStopColor"/> + android:fillColor="?attr/extBolusStopColor"/> + android:fillColor="?attr/extBolusStopColor"/> diff --git a/app/src/main/res/drawable/ic_actions_profileswitch.xml b/app/src/main/res/drawable/ic_actions_profileswitch.xml index 31c93c3483..6f16a3bf09 100644 --- a/app/src/main/res/drawable/ic_actions_profileswitch.xml +++ b/app/src/main/res/drawable/ic_actions_profileswitch.xml @@ -5,5 +5,5 @@ android:viewportHeight="24"> + android:fillColor="?attr/colorControlNormal"/> diff --git a/app/src/main/res/drawable/ic_byoda.xml b/app/src/main/res/drawable/ic_byoda.xml index 1bf06ca7d8..da63bef0df 100644 --- a/app/src/main/res/drawable/ic_byoda.xml +++ b/app/src/main/res/drawable/ic_byoda.xml @@ -5,5 +5,5 @@ android:viewportHeight="24"> + android:fillColor="@color/byodagray"/> diff --git a/app/src/main/res/drawable/ic_cancelbasal.xml b/app/src/main/res/drawable/ic_cancelbasal.xml index 76640a4534..dcc01452e3 100644 --- a/app/src/main/res/drawable/ic_cancelbasal.xml +++ b/app/src/main/res/drawable/ic_cancelbasal.xml @@ -4,7 +4,8 @@ android:viewportHeight="24" android:viewportWidth="24" xmlns:android="http://schemas.android.com/apk/res/android"> - - - + + + diff --git a/app/src/main/res/drawable/ic_chevron_left_black_24dp.xml b/app/src/main/res/drawable/ic_chevron_left_black_24dp.xml index e6bb3ca920..493503f72f 100644 --- a/app/src/main/res/drawable/ic_chevron_left_black_24dp.xml +++ b/app/src/main/res/drawable/ic_chevron_left_black_24dp.xml @@ -4,6 +4,6 @@ android:viewportWidth="24.0" android:viewportHeight="24.0"> diff --git a/app/src/main/res/drawable/ic_chevron_right_black_24dp.xml b/app/src/main/res/drawable/ic_chevron_right_black_24dp.xml index 24835127dd..8e87477ed5 100644 --- a/app/src/main/res/drawable/ic_chevron_right_black_24dp.xml +++ b/app/src/main/res/drawable/ic_chevron_right_black_24dp.xml @@ -4,6 +4,6 @@ android:viewportWidth="24.0" android:viewportHeight="24.0"> diff --git a/app/src/main/res/drawable/ic_cp_age_battery.xml b/app/src/main/res/drawable/ic_cp_age_battery.xml index cc36e585a0..2316d660aa 100644 --- a/app/src/main/res/drawable/ic_cp_age_battery.xml +++ b/app/src/main/res/drawable/ic_cp_age_battery.xml @@ -6,11 +6,11 @@ diff --git a/app/src/main/res/drawable/ic_cp_age_cannula.xml b/app/src/main/res/drawable/ic_cp_age_cannula.xml index f2578fa72b..58b450d81f 100644 --- a/app/src/main/res/drawable/ic_cp_age_cannula.xml +++ b/app/src/main/res/drawable/ic_cp_age_cannula.xml @@ -6,6 +6,6 @@ diff --git a/app/src/main/res/drawable/ic_cp_age_insulin.xml b/app/src/main/res/drawable/ic_cp_age_insulin.xml index 701e31cee3..7e96c91e7a 100644 --- a/app/src/main/res/drawable/ic_cp_age_insulin.xml +++ b/app/src/main/res/drawable/ic_cp_age_insulin.xml @@ -6,6 +6,6 @@ diff --git a/app/src/main/res/drawable/ic_cp_age_sensor.xml b/app/src/main/res/drawable/ic_cp_age_sensor.xml index 08134cc20b..64c684320c 100644 --- a/app/src/main/res/drawable/ic_cp_age_sensor.xml +++ b/app/src/main/res/drawable/ic_cp_age_sensor.xml @@ -6,16 +6,16 @@ diff --git a/app/src/main/res/drawable/ic_dice.xml b/app/src/main/res/drawable/ic_dice.xml index de1b02ea11..b9861ec905 100644 --- a/app/src/main/res/drawable/ic_dice.xml +++ b/app/src/main/res/drawable/ic_dice.xml @@ -4,6 +4,6 @@ android:viewportWidth="640" android:viewportHeight="512"> diff --git a/app/src/main/res/drawable/ic_exit_to_app.xml b/app/src/main/res/drawable/ic_exit_to_app.xml index 37a6c7d058..049c0af2c2 100644 --- a/app/src/main/res/drawable/ic_exit_to_app.xml +++ b/app/src/main/res/drawable/ic_exit_to_app.xml @@ -5,8 +5,8 @@ android:viewportHeight="24"> + android:fillColor="?attr/defaultTextColor"/> + android:fillColor="?attr/defaultTextColor"/> diff --git a/app/src/main/res/drawable/ic_header_log.xml b/app/src/main/res/drawable/ic_header_log.xml index 6ef0e68d81..41d3979612 100644 --- a/app/src/main/res/drawable/ic_header_log.xml +++ b/app/src/main/res/drawable/ic_header_log.xml @@ -10,7 +10,7 @@ android:scaleX="0.8" android:scaleY="0.8"> \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_last_page_black_24dp.xml b/app/src/main/res/drawable/ic_last_page_black_24dp.xml index 0d04354c14..964248be45 100644 --- a/app/src/main/res/drawable/ic_last_page_black_24dp.xml +++ b/app/src/main/res/drawable/ic_last_page_black_24dp.xml @@ -4,6 +4,6 @@ android:viewportWidth="24.0" android:viewportHeight="24.0"> diff --git a/app/src/main/res/drawable/ic_loop_disabled.xml b/app/src/main/res/drawable/ic_loop_disabled.xml index 685118b92d..e8004f78fe 100644 --- a/app/src/main/res/drawable/ic_loop_disabled.xml +++ b/app/src/main/res/drawable/ic_loop_disabled.xml @@ -5,11 +5,11 @@ android:viewportHeight="24"> + android:fillColor="?attr/loopDisabled"/> + android:fillColor="?attr/loopDisabled"/> + android:fillColor="?attr/loopDisabled"/> diff --git a/app/src/main/res/drawable/ic_loop_disconnected.xml b/app/src/main/res/drawable/ic_loop_disconnected.xml index ea2cd137f4..3753c6a451 100644 --- a/app/src/main/res/drawable/ic_loop_disconnected.xml +++ b/app/src/main/res/drawable/ic_loop_disconnected.xml @@ -5,8 +5,8 @@ android:viewportHeight="24"> + android:fillColor="?attr/loopDisconnected"/> + android:fillColor="?attr/loopDisconnected"/> diff --git a/app/src/main/res/drawable/ic_loop_lgs.xml b/app/src/main/res/drawable/ic_loop_lgs.xml index c570403dbf..23a4ddf300 100644 --- a/app/src/main/res/drawable/ic_loop_lgs.xml +++ b/app/src/main/res/drawable/ic_loop_lgs.xml @@ -5,20 +5,20 @@ android:viewportHeight="24"> + android:fillColor="?attr/loopClosed"/> + android:fillColor="?attr/loopClosed"/> + android:fillColor="?attr/loopClosed"/> + android:fillColor="?attr/loopClosed"/> + android:fillColor="?attr/loopClosed"/> + android:fillColor="?attr/loopClosed"/> diff --git a/app/src/main/res/drawable/ic_loop_open.xml b/app/src/main/res/drawable/ic_loop_open.xml index 6f97953808..c7b4f00e3b 100644 --- a/app/src/main/res/drawable/ic_loop_open.xml +++ b/app/src/main/res/drawable/ic_loop_open.xml @@ -5,23 +5,23 @@ android:viewportHeight="24"> + android:fillColor="?attr/loopOpened"/> + android:fillColor="?attr/loopOpened"/> + android:fillColor="?attr/loopOpened"/> + android:fillColor="?attr/loopOpened"/> + android:fillColor="?attr/loopOpened"/> + android:fillColor="?attr/loopOpened"/> + android:fillColor="?attr/loopOpened"/> diff --git a/app/src/main/res/drawable/ic_loop_paused.xml b/app/src/main/res/drawable/ic_loop_paused.xml index c49d3084c3..372b6a7d61 100644 --- a/app/src/main/res/drawable/ic_loop_paused.xml +++ b/app/src/main/res/drawable/ic_loop_paused.xml @@ -5,11 +5,11 @@ android:viewportHeight="24"> + android:fillColor="?attr/loopDisabled"/> + android:fillColor="?attr/loopDisabled"/> + android:fillColor="?attr/loopDisabled"/> diff --git a/app/src/main/res/drawable/ic_loop_reconnect.xml b/app/src/main/res/drawable/ic_loop_reconnect.xml index f61d0f97b1..ca8e54fa6c 100644 --- a/app/src/main/res/drawable/ic_loop_reconnect.xml +++ b/app/src/main/res/drawable/ic_loop_reconnect.xml @@ -5,8 +5,8 @@ android:viewportHeight="24"> + android:fillColor="?attr/loopClosed"/> + android:fillColor="?attr/loopClosed"/> diff --git a/app/src/main/res/drawable/ic_loop_resume.xml b/app/src/main/res/drawable/ic_loop_resume.xml index ade6ba2f8d..cf80f79567 100644 --- a/app/src/main/res/drawable/ic_loop_resume.xml +++ b/app/src/main/res/drawable/ic_loop_resume.xml @@ -5,8 +5,8 @@ android:viewportHeight="24"> + android:fillColor="?attr/loopClosed"/> + android:fillColor="?attr/loopClosed"/> diff --git a/app/src/main/res/drawable/ic_loop_superbolus.xml b/app/src/main/res/drawable/ic_loop_superbolus.xml index 2f94edf2c4..fd9223c587 100644 --- a/app/src/main/res/drawable/ic_loop_superbolus.xml +++ b/app/src/main/res/drawable/ic_loop_superbolus.xml @@ -5,8 +5,8 @@ android:viewportHeight="24"> + android:fillColor="?attr/loopSuperBolus"/> + android:fillColor="?attr/loopSuperBolus"/> diff --git a/app/src/main/res/drawable/ic_patch_pump_outline.xml b/app/src/main/res/drawable/ic_patch_pump_outline.xml index 6e95a27545..6a86ec1503 100644 --- a/app/src/main/res/drawable/ic_patch_pump_outline.xml +++ b/app/src/main/res/drawable/ic_patch_pump_outline.xml @@ -6,7 +6,7 @@ diff --git a/app/src/main/res/drawable/ic_settings.xml b/app/src/main/res/drawable/ic_settings.xml index 8b7d553260..2c771e2896 100644 --- a/app/src/main/res/drawable/ic_settings.xml +++ b/app/src/main/res/drawable/ic_settings.xml @@ -2,10 +2,9 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" - android:tint="@color/white" android:viewportHeight="24" android:viewportWidth="24"> diff --git a/app/src/main/res/drawable/ic_sort.xml b/app/src/main/res/drawable/ic_sort.xml new file mode 100644 index 0000000000..4b1317ac20 --- /dev/null +++ b/app/src/main/res/drawable/ic_sort.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_target_activity.xml b/app/src/main/res/drawable/ic_target_activity.xml index 4439597b75..4873b388cc 100644 --- a/app/src/main/res/drawable/ic_target_activity.xml +++ b/app/src/main/res/drawable/ic_target_activity.xml @@ -5,14 +5,14 @@ android:viewportHeight="24"> + android:fillColor="?attr/exerciseColor"/> - - + android:fillColor="?attr/exerciseColor"/> + + diff --git a/app/src/main/res/drawable/ic_target_cancel.xml b/app/src/main/res/drawable/ic_target_cancel.xml index 50d7304662..c00ad238f7 100644 --- a/app/src/main/res/drawable/ic_target_cancel.xml +++ b/app/src/main/res/drawable/ic_target_cancel.xml @@ -5,11 +5,11 @@ android:viewportHeight="24"> - - + android:fillColor="?attr/tempTargetConfirmation"/> + + diff --git a/app/src/main/res/drawable/ic_target_eatingsoon.xml b/app/src/main/res/drawable/ic_target_eatingsoon.xml index 54b6162546..70d0ab72d4 100644 --- a/app/src/main/res/drawable/ic_target_eatingsoon.xml +++ b/app/src/main/res/drawable/ic_target_eatingsoon.xml @@ -5,17 +5,17 @@ android:viewportHeight="24"> + android:fillColor="?attr/carbsColor"/> + android:fillColor="?attr/carbsColor"/> - - + android:fillColor="?attr/carbsColor"/> + + diff --git a/app/src/main/res/drawable/ic_target_hypo.xml b/app/src/main/res/drawable/ic_target_hypo.xml index 30721cbf14..eab6df8e3f 100644 --- a/app/src/main/res/drawable/ic_target_hypo.xml +++ b/app/src/main/res/drawable/ic_target_hypo.xml @@ -5,17 +5,17 @@ android:viewportHeight="24"> + android:fillColor="?attr/lowColor"/> + android:fillColor="?attr/lowColor"/> + android:fillColor="?attr/lowColor"/> + android:fillColor="?attr/bgInRange"/> + android:fillColor="?attr/tempTargetConfirmation"/> diff --git a/app/src/main/res/drawable/ic_visibility.xml b/app/src/main/res/drawable/ic_visibility.xml index de67f3ee84..76c9b61954 100644 --- a/app/src/main/res/drawable/ic_visibility.xml +++ b/app/src/main/res/drawable/ic_visibility.xml @@ -5,8 +5,8 @@ android:viewportHeight="24"> + android:fillColor="?attr/colorControlNormal"/> + android:fillColor="?attr/colorControlNormal"/> diff --git a/app/src/main/res/drawable/ic_visibility_off.xml b/app/src/main/res/drawable/ic_visibility_off.xml new file mode 100644 index 0000000000..00ecc3cdb3 --- /dev/null +++ b/app/src/main/res/drawable/ic_visibility_off.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_xdrip.xml b/app/src/main/res/drawable/ic_xdrip.xml index 54f88823ba..b144f6335f 100644 --- a/app/src/main/res/drawable/ic_xdrip.xml +++ b/app/src/main/res/drawable/ic_xdrip.xml @@ -4,5 +4,6 @@ android:viewportHeight="24" android:viewportWidth="24" xmlns:android="http://schemas.android.com/apk/res/android"> - + diff --git a/app/src/main/res/layout/actions_fragment.xml b/app/src/main/res/layout/actions_fragment.xml index 9e2810b77b..e8362b0524 100644 --- a/app/src/main/res/layout/actions_fragment.xml +++ b/app/src/main/res/layout/actions_fragment.xml @@ -6,7 +6,7 @@ tools:context=".plugins.general.actions.ActionsFragment"> @@ -26,7 +26,7 @@ app:columnCount="2"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/activity_historybrowse.xml b/app/src/main/res/layout/activity_historybrowse.xml index 6306b9e979..f3792b7e91 100644 --- a/app/src/main/res/layout/activity_historybrowse.xml +++ b/app/src/main/res/layout/activity_historybrowse.xml @@ -18,7 +18,7 @@ android:gravity="center_horizontal" android:text="@string/noprofileset" android:textAppearance="?android:attr/textAppearanceLarge" - android:textColor="@android:color/holo_red_light" + android:textColor="?attr/alarmColor" android:textStyle="bold" android:visibility="gone" /> @@ -63,6 +63,17 @@ + + - - diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 6edf1fa105..ab62af204d 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -7,23 +7,20 @@ android:clipChildren="false" android:clipToPadding="false"> - + android:elevation="2dp"> - + + app:itemIconTint="?android:textColorPrimary" + app:itemTextColor="?android:textColorPrimary"/> \ No newline at end of file diff --git a/app/src/main/res/layout/activity_preferences.xml b/app/src/main/res/layout/activity_preferences.xml index eef88c7bad..2ff946628a 100644 --- a/app/src/main/res/layout/activity_preferences.xml +++ b/app/src/main/res/layout/activity_preferences.xml @@ -22,7 +22,8 @@ android:autofillHints="@string/filter" android:gravity="start" android:textStyle="bold" - android:inputType="text" /> + android:inputType="text" + android:background="@color/transparent"/> diff --git a/app/src/main/res/layout/activity_profilehelper.xml b/app/src/main/res/layout/activity_profilehelper.xml index d87aca5296..73353f3d1c 100644 --- a/app/src/main/res/layout/activity_profilehelper.xml +++ b/app/src/main/res/layout/activity_profilehelper.xml @@ -15,9 +15,9 @@ + android:background="?attr/defaultbackground" + android:orientation="horizontal" + android:paddingBottom="10dp"> + android:text="1" + tools:ignore="HardcodedText" /> + android:text="2" + tools:ignore="HardcodedText" /> + android:layout_marginBottom="10dp" + android:hint="@string/profiletype"> @@ -90,6 +91,8 @@ android:id="@+id/age" android:layout_width="130dp" android:layout_height="40dp" + android:layout_marginTop="4dp" + android:layout_marginBottom="2dp" app:customContentDescription="@string/age" /> @@ -105,6 +108,7 @@ android:layout_width="150dp" android:layout_height="wrap_content" android:layout_marginStart="10dp" + android:layout_marginTop="1dp" android:text="@string/tdd_total" android:textAppearance="@style/TextAppearance.AppCompat.Medium" /> @@ -112,6 +116,8 @@ android:id="@+id/tdd" android:layout_width="130dp" android:layout_height="40dp" + android:layout_marginTop="1dp" + android:layout_marginBottom="2dp" app:customContentDescription="@string/tdd_total" /> @@ -134,18 +140,19 @@ android:id="@+id/weight" android:layout_width="130dp" android:layout_height="40dp" + android:layout_marginBottom="2dp" app:customContentDescription="@string/weight_label" /> @@ -162,15 +169,15 @@ - + android:orientation="vertical" /> + android:hint="@string/selected_profile"> + android:hint="@string/careportal_profileswitch"> @@ -63,7 +62,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom|center_horizontal" - android:background="@color/black_overlay" + android:background="@color/black_alpha_40" android:orientation="horizontal" android:paddingLeft="16dp" android:paddingRight="16dp"> @@ -74,8 +73,7 @@ android:layout_height="wrap_content" android:background="@android:color/transparent" android:onClick="showPreviousPage" - android:text="@string/previous_button" - android:textColor="#FFFFFF" /> + android:text="@string/previous_button" /> + android:text="@string/next_button" />