diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000000..ea708e8e64b --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,73 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/2.0/configuration-reference +version: 2.1 + +# Orbs are reusable packages of CircleCI configuration that you may share across projects, enabling you to create encapsulated, parameterized commands, jobs, and executors that can be used across multiple projects. +orbs: + android: circleci/android@1.0.3 + codecov: codecov/codecov@1.2.0 + +jobs: + # Below is the definition of your job to build and test your app, you can rename and customize it as you want. + build-and-test: + # These next lines define the Android machine image executor: https://circleci.com/docs/2.0/executor-types/ + executor: + name: android/android-machine + + steps: + # Checkout the code as the first step. + - checkout + + # The next step will run the unit tests + - android/run-tests: + test-command: ./gradlew -Pcoverage -PfirebaseDisable testFullDebugUnitTest jacocoTestFullDebugUnitTestReport + + # Then start the emulator and run the Instrumentation tests! + # - android/start-emulator-and-run-tests: + # test-command: ./gradlew connectedDebugAndroidTest + # system-image: system-images;android-25;google_apis;x86 + + # And finally run the release build + # - run: + # name: Assemble release build + # command: | + # ./gradlew assembleRelease + - codecov/upload: + file: './app/build/jacoco/jacoco.xml' + - codecov/upload: + file: './automation/build/jacoco/jacoco.xml' + - codecov/upload: + file: './combo/build/jacoco/jacoco.xml' + - codecov/upload: + file: './core/build/jacoco/jacoco.xml' + - codecov/upload: + file: './dana/build/jacoco/jacoco.xml' + - codecov/upload: + file: './danar/build/jacoco/jacoco.xml' + - codecov/upload: + file: './danars/build/jacoco/jacoco.xml' + - codecov/upload: + file: './database/build/jacoco/jacoco.xml' + - codecov/upload: + file: './insight/build/jacoco/jacoco.xml' + - codecov/upload: + file: './medtronic/build/jacoco/jacoco.xml' + - codecov/upload: + file: './omnipod-common/build/jacoco/jacoco.xml' + - codecov/upload: + file: './omnipod-dash/build/jacoco/jacoco.xml' + - codecov/upload: + file: './omnipod-eros/build/jacoco/jacoco.xml' + - codecov/upload: + file: './rileylink/build/jacoco/jacoco.xml' + - codecov/upload: + file: './wear/build/jacoco/jacoco.xml' + +workflows: + # Below is the definition of your workflow. + # Inside the workflow, you provide the jobs you want to run, e.g this workflow runs the build-and-test job above. + # CircleCI will run this workflow on every commit. + # For more details on extending your workflow, see the configuration docs: https://circleci.com/docs/2.0/configuration-reference/#workflows + sample: + jobs: + - build-and-test \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index e4d4c5be8b3..85354209ff9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -111,7 +111,7 @@ android { defaultConfig { multiDexEnabled true versionCode 1500 - version "2.8.2.1-dev-e3" + version "2.8.2.1-dev-e5" buildConfigField "String", "VERSION", '"' + version + '"' buildConfigField "String", "BUILDVERSION", '"' + generateGitBuild() + '-' + generateDate() + '"' buildConfigField "String", "REMOTE", '"' + generateGitRemote() + '"' @@ -186,6 +186,7 @@ dependencies { implementation project(':danars') implementation project(':danar') implementation project(':insight') + implementation project(':pump-common') implementation project(':rileylink') implementation project(':medtronic') implementation project(':omnipod-common') @@ -197,8 +198,6 @@ dependencies { /* Dagger2 - We are going to use dagger.android which includes * support for Activity and fragment injection so we need to include * the following dependencies */ - - annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version" annotationProcessor "com.google.dagger:dagger-android-processor:$dagger_version" kapt "com.google.dagger:dagger-android-processor:$dagger_version" diff --git a/app/libs/android-edittext-validator-v1.3.4-mod.aar b/app/libs/android-edittext-validator-v1.3.4-mod.aar deleted file mode 100644 index e08904b772c..00000000000 Binary files a/app/libs/android-edittext-validator-v1.3.4-mod.aar and /dev/null differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 18750bfda8d..77b68adf4ad 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -68,7 +68,8 @@ - + + diff --git a/app/src/main/java/info/nightscout/androidaps/MainActivity.kt b/app/src/main/java/info/nightscout/androidaps/MainActivity.kt index b5b5974fee9..42559a543bd 100644 --- a/app/src/main/java/info/nightscout/androidaps/MainActivity.kt +++ b/app/src/main/java/info/nightscout/androidaps/MainActivity.kt @@ -27,18 +27,14 @@ import com.google.firebase.crashlytics.FirebaseCrashlytics import com.joanzapata.iconify.Iconify import com.joanzapata.iconify.fonts.FontAwesomeModule import dev.doubledot.doki.ui.DokiActivity -import info.nightscout.androidaps.activities.NoSplashAppCompatActivity -import info.nightscout.androidaps.activities.PreferencesActivity -import info.nightscout.androidaps.activities.ProfileHelperActivity -import info.nightscout.androidaps.activities.SingleFragmentActivity -import info.nightscout.androidaps.activities.StatsActivity +import info.nightscout.androidaps.activities.* 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.EventPreferenceChange import info.nightscout.androidaps.events.EventRebuildTabs -import info.nightscout.androidaps.historyBrowser.HistoryBrowseActivity +import info.nightscout.androidaps.activities.HistoryBrowseActivity import info.nightscout.androidaps.interfaces.ActivePlugin import info.nightscout.androidaps.interfaces.Config import info.nightscout.androidaps.interfaces.IconsProvider @@ -99,6 +95,7 @@ class MainActivity : NoSplashAppCompatActivity() { private lateinit var binding: ActivityMainBinding + @kotlin.ExperimentalStdlibApi override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Iconify.with(FontAwesomeModule()) @@ -290,6 +287,11 @@ class MainActivity : NoSplashAppCompatActivity() { return true } + R.id.nav_treatments -> { + startActivity(Intent(this, TreatmentsActivity::class.java)) + return true + } + R.id.nav_setupwizard -> { protectionCheck.queryProtection(this, ProtectionCheck.Protection.PREFERENCES, { startActivity(Intent(this, SetupWizardActivity::class.java)) @@ -359,11 +361,12 @@ class MainActivity : NoSplashAppCompatActivity() { // Correct place for calling setUserStats() would be probably MainApp // but we need to have it called at least once a day. Thus this location + @kotlin.ExperimentalStdlibApi private fun setUserStats() { if (!fabricPrivacy.fabricEnabled()) return val closedLoopEnabled = if (constraintChecker.isClosedLoopAllowed().value()) "CLOSED_LOOP_ENABLED" else "CLOSED_LOOP_DISABLED" // Size is limited to 36 chars - val remote = BuildConfig.REMOTE.toLowerCase(Locale.getDefault()) + val remote = BuildConfig.REMOTE.lowercase(Locale.getDefault()) .replace("https://", "") .replace("http://", "") .replace(".git", "") diff --git a/app/src/main/java/info/nightscout/androidaps/activities/HistoryBrowseActivity.kt b/app/src/main/java/info/nightscout/androidaps/activities/HistoryBrowseActivity.kt new file mode 100644 index 00000000000..6b0be2cc681 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/activities/HistoryBrowseActivity.kt @@ -0,0 +1,391 @@ +package info.nightscout.androidaps.activities + +import android.annotation.SuppressLint +import android.app.DatePickerDialog +import android.graphics.Color +import android.os.Bundle +import android.util.DisplayMetrics +import android.view.ViewGroup +import android.widget.LinearLayout +import android.widget.RelativeLayout +import android.widget.TextView +import com.jjoe64.graphview.GraphView +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.R +import info.nightscout.androidaps.database.AppRepository +import info.nightscout.androidaps.databinding.ActivityHistorybrowseBinding +import info.nightscout.androidaps.events.EventAutosensCalculationFinished +import info.nightscout.androidaps.events.EventCustomCalculationFinished +import info.nightscout.androidaps.events.EventRefreshOverview +import info.nightscout.androidaps.extensions.toVisibility +import info.nightscout.androidaps.interfaces.ActivePlugin +import info.nightscout.androidaps.interfaces.Config +import info.nightscout.androidaps.interfaces.ProfileFunction +import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin +import info.nightscout.androidaps.plugins.bus.RxBusWrapper +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.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.utils.* +import info.nightscout.androidaps.utils.buildHelper.BuildHelper +import info.nightscout.androidaps.utils.rx.AapsSchedulers +import info.nightscout.androidaps.utils.sharedPreferences.SP +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.rxkotlin.plusAssign +import java.util.* +import javax.inject.Inject +import kotlin.math.min + +class HistoryBrowseActivity : NoSplashAppCompatActivity() { + + @Inject lateinit var injector: HasAndroidInjector + @Inject lateinit var aapsLogger: AAPSLogger + @Inject lateinit var aapsSchedulers: AapsSchedulers + @Inject lateinit var rxBus: RxBusWrapper + @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 + @Inject lateinit var dateUtil: DateUtil + @Inject lateinit var config: Config + @Inject lateinit var loopPlugin: LoopPlugin + @Inject lateinit var nsDeviceStatus: NSDeviceStatus + @Inject lateinit var translator: Translator + + private val disposable = CompositeDisposable() + + private val secondaryGraphs = ArrayList() + private val secondaryGraphsLabel = ArrayList() + + private var axisWidth: Int = 0 + private var rangeToDisplay = 24 // for graph +// private var start: Long = 0 + + private lateinit var iobCobCalculator: IobCobCalculatorPlugin + private lateinit var overviewData: OverviewData + + private lateinit var binding: ActivityHistorybrowseBinding + private var destroyed = false + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityHistorybrowseBinding.inflate(layoutInflater) + setContentView(binding.root) + + // We don't want to use injected singletons but own instance working on top of different data + iobCobCalculator = IobCobCalculatorPlugin(injector, aapsLogger, aapsSchedulers, rxBus, sp, resourceHelper, profileFunction, activePlugin, sensitivityOref1Plugin, sensitivityAAPSPlugin, sensitivityWeightedAveragePlugin, fabricPrivacy, dateUtil, repository) + overviewData = OverviewData(injector, aapsLogger, resourceHelper, dateUtil, sp, activePlugin, defaultValueHelper, profileFunction, config, loopPlugin, nsDeviceStatus, repository, overviewMenus, iobCobCalculator, translator) + + binding.left.setOnClickListener { + setTime(overviewData.fromTime - T.hours(rangeToDisplay.toLong()).msecs()) + loadAll("onClickLeft") + } + binding.right.setOnClickListener { + setTime(overviewData.fromTime + T.hours(rangeToDisplay.toLong()).msecs()) + loadAll("onClickRight") + } + binding.end.setOnClickListener { + setTime(dateUtil.now()) + loadAll("onClickEnd") + } + binding.zoom.setOnClickListener { + rangeToDisplay += 6 + rangeToDisplay = if (rangeToDisplay > 24) 6 else rangeToDisplay + setTime(overviewData.fromTime) + loadAll("rangeChange") + } + binding.zoom.setOnLongClickListener { + Calendar.getInstance().also { calendar -> + calendar.timeInMillis = overviewData.fromTime + calendar[Calendar.MILLISECOND] = 0 + calendar[Calendar.SECOND] = 0 + calendar[Calendar.MINUTE] = 0 + calendar[Calendar.HOUR_OF_DAY] = 0 + setTime(calendar.timeInMillis) + } + loadAll("onLongClickZoom") + true + } + + // create an OnDateSetListener + val dateSetListener = DatePickerDialog.OnDateSetListener { _, year, monthOfYear, dayOfMonth -> + Calendar.getInstance().also { calendar -> + calendar.timeInMillis = overviewData.fromTime + calendar[Calendar.YEAR] = year + calendar[Calendar.MONTH] = monthOfYear + calendar[Calendar.DAY_OF_MONTH] = dayOfMonth + calendar[Calendar.MILLISECOND] = 0 + calendar[Calendar.SECOND] = 0 + calendar[Calendar.MINUTE] = 0 + calendar[Calendar.HOUR_OF_DAY] = 0 + setTime(calendar.timeInMillis) + binding.date.text = dateUtil.dateAndTimeString(overviewData.fromTime) + } + loadAll("onClickDate") + } + + binding.date.setOnClickListener { + val cal = Calendar.getInstance() + cal.timeInMillis = overviewData.fromTime + DatePickerDialog(this, dateSetListener, + cal.get(Calendar.YEAR), + cal.get(Calendar.MONTH), + cal.get(Calendar.DAY_OF_MONTH) + ).show() + } + + val dm = DisplayMetrics() + 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 = resourceHelper.gc(R.color.graphgrid) + binding.bgGraph.gridLabelRenderer?.reloadStyles() + binding.bgGraph.gridLabelRenderer?.labelVerticalWidth = axisWidth + + overviewMenus.setupChartMenu(binding.chartMenuButton) + prepareGraphsIfNeeded(overviewMenus.setting.size) + savedInstanceState?.let { bundle -> + rangeToDisplay = bundle.getInt("rangeToDisplay", 0) + overviewData.fromTime = bundle.getLong("start", 0) + overviewData.toTime = bundle.getLong("end", 0) + } + } + + public override fun onPause() { + super.onPause() + disposable.clear() + iobCobCalculator.stopCalculation("onPause") + } + + @Synchronized + override fun onDestroy() { + destroyed = true + super.onDestroy() + } + + public override fun onResume() { + super.onResume() + disposable.add(rxBus + .toObservable(EventAutosensCalculationFinished::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ + // catch only events from iobCobCalculator + if (it.cause is EventCustomCalculationFinished) + refreshLoop("EventAutosensCalculationFinished") + }, fabricPrivacy::logException) + ) + disposable.add(rxBus + .toObservable(EventIobCalculationProgress::class.java) + .observeOn(aapsSchedulers.main) + .subscribe({ + if (it.cause is EventCustomCalculationFinished) + binding.overviewIobcalculationprogess.text = it.progress + }, fabricPrivacy::logException) + ) + disposable.add(rxBus + .toObservable(EventRefreshOverview::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 + setTime(dateUtil.now()) + loadAll("onResume") + } else { + updateGUI("onResume") + } + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putInt("rangeToDisplay", rangeToDisplay) + outState.putLong("start", overviewData.fromTime) + outState.putLong("end", overviewData.toTime) + + } + + private fun prepareGraphsIfNeeded(numOfGraphs: Int) { + if (numOfGraphs != secondaryGraphs.size - 1) { + //aapsLogger.debug("New secondary graph count ${numOfGraphs-1}") + // rebuild needed + secondaryGraphs.clear() + secondaryGraphsLabel.clear() + binding.iobGraph.removeAllViews() + for (i in 1 until numOfGraphs) { + val relativeLayout = RelativeLayout(this) + relativeLayout.layoutParams = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + + val graph = GraphView(this) + graph.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, resourceHelper.dpToPx(100)).also { it.setMargins(0, resourceHelper.dpToPx(15), 0, resourceHelper.dpToPx(10)) } + graph.gridLabelRenderer?.gridColor = resourceHelper.gc(R.color.graphgrid) + graph.gridLabelRenderer?.reloadStyles() + graph.gridLabelRenderer?.isHorizontalLabelsVisible = false + graph.gridLabelRenderer?.labelVerticalWidth = axisWidth + graph.gridLabelRenderer?.numVerticalLabels = 3 + graph.viewport.backgroundColor = Color.argb(20, 255, 255, 255) // 8% of gray + relativeLayout.addView(graph) + + val label = TextView(this) + val layoutParams = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT).also { it.setMargins(resourceHelper.dpToPx(30), resourceHelper.dpToPx(25), 0, 0) } + layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP) + layoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT) + label.layoutParams = layoutParams + relativeLayout.addView(label) + secondaryGraphsLabel.add(label) + + binding.iobGraph.addView(relativeLayout) + secondaryGraphs.add(graph) + } + } + } + + @Suppress("SameParameterValue") + private fun loadAll(from: String) { + Thread { + overviewData.prepareBasalData(from) + overviewData.prepareTemporaryTargetData(from) + overviewData.prepareTreatmentsData(from) + rxBus.send(EventRefreshOverview(from)) + aapsLogger.debug(LTag.UI, "loadAll $from finished") + runCalculation(from) + }.start() + } + + private fun setTime(start: Long) { + Calendar.getInstance().also { calendar -> + calendar.timeInMillis = start + calendar[Calendar.MILLISECOND] = 0 + calendar[Calendar.SECOND] = 0 + calendar[Calendar.MINUTE] = 0 + calendar[Calendar.HOUR_OF_DAY] = 0 + overviewData.fromTime = calendar.timeInMillis + overviewData.toTime = overviewData.fromTime + T.hours(rangeToDisplay.toLong()).msecs() + overviewData.endTime = overviewData.toTime + } + } + + private fun runCalculation(from: String) { + Thread { + iobCobCalculator.stopCalculation(from) + iobCobCalculator.stopCalculationTrigger = false + iobCobCalculator.runCalculation(from, overviewData.toTime, bgDataReload = true, limitDataToOldestAvailable = false, cause = EventCustomCalculationFinished()) + }.start() + } + + @Volatile + var runningRefresh = false + 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 + } + + @Suppress("UNUSED_PARAMETER") + @SuppressLint("SetTextI18n") + fun updateGUI(from: String) { + aapsLogger.debug(LTag.UI, "updateGui $from") + + binding.date.text = dateUtil.dateAndTimeString(overviewData.fromTime) + binding.zoom.text = rangeToDisplay.toString() + + val pump = activePlugin.activePump + val graphData = GraphData(injector, binding.bgGraph, overviewData) + val menuChartSettings = overviewMenus.setting + graphData.addInRangeArea(overviewData.fromTime, overviewData.endTime, defaultValueHelper.determineLowLine(), defaultValueHelper.determineHighLine()) + graphData.addBgReadings(menuChartSettings[0][OverviewMenus.CharType.PRE.ordinal]) + if (buildHelper.isDev()) graphData.addBucketedData() + graphData.addTreatments() + if (menuChartSettings[0][OverviewMenus.CharType.ACT.ordinal]) + graphData.addActivity(0.8) + if (pump.pumpDescription.isTempBasalCapable && menuChartSettings[0][OverviewMenus.CharType.BAS.ordinal]) + graphData.addBasals() + graphData.addTargetLine() + graphData.addNowLine(dateUtil.now()) + + // set manual x bounds to have nice steps + graphData.setNumVerticalLabels() + graphData.formatAxis(overviewData.fromTime, overviewData.endTime) + + graphData.performUpdate() + + // 2nd graphs + prepareGraphsIfNeeded(menuChartSettings.size) + val secondaryGraphsData: ArrayList = ArrayList() + + val now = System.currentTimeMillis() + for (g in 0 until min(secondaryGraphs.size, menuChartSettings.size + 1)) { + val secondGraphData = GraphData(injector, secondaryGraphs[g], overviewData) + var useABSForScale = false + var useIobForScale = false + var useCobForScale = false + var useDevForScale = false + var useRatioForScale = false + var useDSForScale = false + var useBGIForScale = false + when { + menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] -> useABSForScale = true + menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal] -> useIobForScale = true + menuChartSettings[g + 1][OverviewMenus.CharType.COB.ordinal] -> useCobForScale = true + menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] -> useDevForScale = true + menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] -> useBGIForScale = true + menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal] -> useRatioForScale = true + menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] -> useDSForScale = true + } + val alignDevBgiScale = menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] + + if (menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal]) secondGraphData.addAbsIob(useABSForScale, 1.0) + if (menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal]) secondGraphData.addIob(useIobForScale, 1.0) + if (menuChartSettings[g + 1][OverviewMenus.CharType.COB.ordinal]) secondGraphData.addCob(useCobForScale, if (useCobForScale) 1.0 else 0.5) + if (menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal]) secondGraphData.addDeviations(useDevForScale, 1.0) + if (menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal]) secondGraphData.addMinusBGI(useBGIForScale, if (alignDevBgiScale) 1.0 else 0.8) + if (menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal]) secondGraphData.addRatio(useRatioForScale, if (useRatioForScale) 1.0 else 0.8) + if (menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] && buildHelper.isDev()) secondGraphData.addDeviationSlope(useDSForScale, 1.0) + + // set manual x bounds to have nice steps + secondGraphData.formatAxis(overviewData.fromTime, overviewData.endTime) + secondGraphData.addNowLine(now) + secondaryGraphsData.add(secondGraphData) + } + for (g in 0 until min(secondaryGraphs.size, menuChartSettings.size + 1)) { + secondaryGraphsLabel[g].text = overviewMenus.enabledTypes(g + 1) + secondaryGraphs[g].visibility = ( + menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] || + menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal] || + menuChartSettings[g + 1][OverviewMenus.CharType.COB.ordinal] || + menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] || + menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] || + menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal] || + menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] + ).toVisibility() + secondaryGraphsData[g].performUpdate() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/activities/RequestDexcomPermissionActivity.kt b/app/src/main/java/info/nightscout/androidaps/activities/RequestDexcomPermissionActivity.kt index 8eb93156b18..c5b03dc0f52 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/RequestDexcomPermissionActivity.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/RequestDexcomPermissionActivity.kt @@ -7,8 +7,10 @@ import javax.inject.Inject class RequestDexcomPermissionActivity : DialogAppCompatActivity() { @Inject lateinit var dexcomPlugin: DexcomPlugin - private val requestCode = "AndroidAPS <3".map { it.toInt() }.sum() + @kotlin.ExperimentalStdlibApi + private val requestCode = "AndroidAPS <3".map { it.code }.sum() + @kotlin.ExperimentalStdlibApi override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) requestPermissions(arrayOf(DexcomPlugin.PERMISSION), requestCode) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/TreatmentsActivity.kt similarity index 53% rename from app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsFragment.kt rename to app/src/main/java/info/nightscout/androidaps/activities/TreatmentsActivity.kt index 9de459f6222..4cd4ab559a5 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/TreatmentsActivity.kt @@ -1,53 +1,28 @@ -package info.nightscout.androidaps.plugins.treatments +package info.nightscout.androidaps.activities import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentTransaction -import dagger.android.support.DaggerFragment import info.nightscout.androidaps.R +import info.nightscout.androidaps.activities.fragments.* import info.nightscout.androidaps.databinding.TreatmentsFragmentBinding -import info.nightscout.androidaps.events.EventExtendedBolusChange +import info.nightscout.androidaps.extensions.toVisibility import info.nightscout.androidaps.interfaces.ActivePlugin -import info.nightscout.androidaps.interfaces.IobCobCalculator -import info.nightscout.androidaps.plugins.bus.RxBusWrapper -import info.nightscout.androidaps.plugins.treatments.fragments.* -import info.nightscout.androidaps.utils.DateUtil -import info.nightscout.androidaps.utils.FabricPrivacy 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 io.reactivex.disposables.CompositeDisposable -import io.reactivex.rxkotlin.plusAssign import javax.inject.Inject -class TreatmentsFragment : DaggerFragment() { +class TreatmentsActivity : NoSplashAppCompatActivity() { - @Inject lateinit var rxBus: RxBusWrapper - @Inject lateinit var resourceHelper: ResourceHelper - @Inject lateinit var fabricPrivacy: FabricPrivacy - @Inject lateinit var activePlugin: ActivePlugin - @Inject lateinit var iobCobCalculator: IobCobCalculator - @Inject lateinit var aapsSchedulers: AapsSchedulers @Inject lateinit var buildHelper: BuildHelper - @Inject lateinit var dateUtil: DateUtil - - private val disposable = CompositeDisposable() - - private var _binding: TreatmentsFragmentBinding? = null - - // This property is only valid between onCreateView and - // onDestroyView. - private val binding get() = _binding!! + @Inject lateinit var activePlugin: ActivePlugin - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = - TreatmentsFragmentBinding.inflate(inflater, container, false).also { _binding = it }.root + private lateinit var binding: TreatmentsFragmentBinding - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) + override fun onCreate(savedInstanceState: Bundle?) { + 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() @@ -84,33 +59,10 @@ class TreatmentsFragment : DaggerFragment() { setBackgroundColorOnSelected(binding.treatments) } - @Synchronized - override fun onResume() { - super.onResume() - disposable += rxBus - .toObservable(EventExtendedBolusChange::class.java) - .observeOn(aapsSchedulers.main) - .subscribe({ updateGui() }, fabricPrivacy::logException) - updateGui() - } - - @Synchronized - override fun onPause() { - super.onPause() - disposable.clear() - } - - @Synchronized - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } - private fun setFragment(selectedFragment: Fragment) { - childFragmentManager.beginTransaction() - .replace(R.id.fragment_container, selectedFragment) // f2_container is your FrameLayout container - .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) - .addToBackStack(null) + supportFragmentManager.beginTransaction() + .replace(R.id.fragment_container, selectedFragment) + .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) .commit() } @@ -125,8 +77,4 @@ class TreatmentsFragment : DaggerFragment() { selected.setBackgroundColor(resourceHelper.gc(R.color.tabBgColorSelected)) } - private fun updateGui() { - if (_binding == null) return - binding.extendedBoluses.visibility = (activePlugin.activePump.pumpDescription.isExtendedBolusCapable || iobCobCalculator.getExtendedBolus(dateUtil.now()) != null).toVisibility() - } -} \ No newline at end of file +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsBolusCarbsFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsBolusCarbsFragment.kt similarity index 98% rename from app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsBolusCarbsFragment.kt rename to app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsBolusCarbsFragment.kt index 9b846f613f6..4b5dc48e2a7 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsBolusCarbsFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsBolusCarbsFragment.kt @@ -1,4 +1,4 @@ -package info.nightscout.androidaps.plugins.treatments.fragments +package info.nightscout.androidaps.activities.fragments import android.graphics.Paint import android.os.Bundle @@ -156,7 +156,7 @@ class TreatmentsBolusCarbsFragment : DaggerFragment() { }) } } - val nsUploadOnly = sp.getBoolean(R.string.key_ns_upload_only, true) || !buildHelper.isEngineeringMode() + val nsUploadOnly = !sp.getBoolean(R.string.key_ns_receive_insulin, false) || !sp.getBoolean(R.string.key_ns_receive_carbs, false) || !buildHelper.isEngineeringMode() if (nsUploadOnly) binding.refreshFromNightscout.visibility = View.GONE binding.showInvalidated.setOnCheckedChangeListener { _, _ -> rxBus.send(EventTreatmentUpdateGui()) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsCareportalFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsCareportalFragment.kt similarity index 97% rename from app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsCareportalFragment.kt rename to app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsCareportalFragment.kt index 4c90c80e086..f3eb9e551ff 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsCareportalFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsCareportalFragment.kt @@ -1,4 +1,4 @@ -package info.nightscout.androidaps.plugins.treatments.fragments +package info.nightscout.androidaps.activities.fragments import android.graphics.Paint import android.os.Bundle @@ -25,7 +25,7 @@ import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart import info.nightscout.androidaps.plugins.treatments.events.EventTreatmentUpdateGui -import info.nightscout.androidaps.plugins.treatments.fragments.TreatmentsCareportalFragment.RecyclerViewAdapter.TherapyEventsViewHolder +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 @@ -102,7 +102,7 @@ class TreatmentsCareportalFragment : DaggerFragment() { } } - val nsUploadOnly = sp.getBoolean(R.string.key_ns_upload_only, true) || !buildHelper.isEngineeringMode() + val nsUploadOnly = !sp.getBoolean(R.string.key_ns_receive_therapy_events, false) || !buildHelper.isEngineeringMode() if (nsUploadOnly) binding.refreshFromNightscout.visibility = View.GONE binding.showInvalidated.setOnCheckedChangeListener { _, _ -> rxBus.send(EventTreatmentUpdateGui()) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsExtendedBolusesFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsExtendedBolusesFragment.kt similarity index 93% rename from app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsExtendedBolusesFragment.kt rename to app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsExtendedBolusesFragment.kt index 5e52467f5ec..d5dfec5f6cb 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsExtendedBolusesFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsExtendedBolusesFragment.kt @@ -1,4 +1,4 @@ -package info.nightscout.androidaps.plugins.treatments.fragments +package info.nightscout.androidaps.activities.fragments import android.annotation.SuppressLint import android.content.DialogInterface @@ -15,6 +15,7 @@ import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.entities.ExtendedBolus 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.InvalidateExtendedBolusTransaction import info.nightscout.androidaps.databinding.TreatmentsExtendedbolusFragmentBinding @@ -29,7 +30,7 @@ import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.plugins.bus.RxBusWrapper -import info.nightscout.androidaps.plugins.treatments.fragments.TreatmentsExtendedBolusesFragment.RecyclerViewAdapter.ExtendedBolusesViewHolder +import info.nightscout.androidaps.activities.fragments.TreatmentsExtendedBolusesFragment.RecyclerViewAdapter.ExtendedBolusesViewHolder import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.T @@ -157,7 +158,11 @@ class TreatmentsExtendedBolusesFragment : DaggerFragment() { ${resourceHelper.gs(R.string.extended_bolus)} ${resourceHelper.gs(R.string.date)}: ${dateUtil.dateAndTimeString(extendedBolus.timestamp)} """.trimIndent(), { _: DialogInterface, _: Int -> - uel.log(Action.EXTENDED_BOLUS_REMOVED, Sources.Treatments) + uel.log(Action.EXTENDED_BOLUS_REMOVED, Sources.Treatments, + ValueWithUnit.Timestamp(extendedBolus.timestamp), + ValueWithUnit.Insulin(extendedBolus.amount), + ValueWithUnit.UnitPerHour(extendedBolus.rate), + ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(extendedBolus.duration).toInt())) disposable += repository.runTransactionForResult(InvalidateExtendedBolusTransaction(extendedBolus.id)) .subscribe( { aapsLogger.debug(LTag.DATABASE, "Removed extended bolus $extendedBolus") }, diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsProfileSwitchFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsProfileSwitchFragment.kt similarity index 95% rename from app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsProfileSwitchFragment.kt rename to app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsProfileSwitchFragment.kt index 1ca2b2d2ac5..151b37234f8 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsProfileSwitchFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsProfileSwitchFragment.kt @@ -1,4 +1,4 @@ -package info.nightscout.androidaps.plugins.treatments.fragments +package info.nightscout.androidaps.activities.fragments import android.graphics.Paint import android.os.Bundle @@ -21,18 +21,16 @@ import info.nightscout.androidaps.dialogs.ProfileViewerDialog import info.nightscout.androidaps.events.EventProfileSwitchChanged import info.nightscout.androidaps.extensions.getCustomizedName import info.nightscout.androidaps.extensions.toVisibility -import info.nightscout.androidaps.interfaces.UploadQueueInterface import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.plugins.bus.RxBusWrapper -import info.nightscout.androidaps.plugins.general.nsclient.NSUpload import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventNewHistoryData import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin import info.nightscout.androidaps.plugins.profile.local.events.EventLocalProfileChanged import info.nightscout.androidaps.plugins.treatments.events.EventTreatmentUpdateGui -import info.nightscout.androidaps.plugins.treatments.fragments.TreatmentsProfileSwitchFragment.RecyclerProfileViewAdapter.ProfileSwitchViewHolder +import info.nightscout.androidaps.activities.fragments.TreatmentsProfileSwitchFragment.RecyclerProfileViewAdapter.ProfileSwitchViewHolder import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.T @@ -55,8 +53,6 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() { @Inject lateinit var localProfilePlugin: LocalProfilePlugin @Inject lateinit var resourceHelper: ResourceHelper @Inject lateinit var fabricPrivacy: FabricPrivacy - @Inject lateinit var nsUpload: NSUpload - @Inject lateinit var uploadQueue: UploadQueueInterface @Inject lateinit var dateUtil: DateUtil @Inject lateinit var buildHelper: BuildHelper @Inject lateinit var aapsSchedulers: AapsSchedulers @@ -103,7 +99,7 @@ class TreatmentsProfileSwitchFragment : DaggerFragment() { } } } - if (sp.getBoolean(R.string.key_ns_upload_only, true) || !buildHelper.isEngineeringMode()) binding.refreshFromNightscout.visibility = View.GONE + if (!sp.getBoolean(R.string.key_ns_receive_profile_switch, false) || !buildHelper.isEngineeringMode()) binding.refreshFromNightscout.visibility = View.GONE binding.showInvalidated.setOnCheckedChangeListener { _, _ -> rxBus.send(EventTreatmentUpdateGui()) } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsTempTargetFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsTempTargetFragment.kt similarity index 94% rename from app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsTempTargetFragment.kt rename to app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsTempTargetFragment.kt index 78ead608b6d..16e8a5ae324 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsTempTargetFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsTempTargetFragment.kt @@ -1,4 +1,4 @@ -package info.nightscout.androidaps.plugins.treatments.fragments +package info.nightscout.androidaps.activities.fragments import android.annotation.SuppressLint import android.content.DialogInterface @@ -23,14 +23,13 @@ import info.nightscout.androidaps.databinding.TreatmentsTemptargetFragmentBindin import info.nightscout.androidaps.databinding.TreatmentsTemptargetItemBinding import info.nightscout.androidaps.events.EventTempTargetChange import info.nightscout.androidaps.interfaces.ProfileFunction -import info.nightscout.androidaps.interfaces.UploadQueueInterface import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart import info.nightscout.androidaps.plugins.treatments.events.EventTreatmentUpdateGui -import info.nightscout.androidaps.plugins.treatments.fragments.TreatmentsTempTargetFragment.RecyclerViewAdapter.TempTargetsViewHolder +import info.nightscout.androidaps.activities.fragments.TreatmentsTempTargetFragment.RecyclerViewAdapter.TempTargetsViewHolder import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.T @@ -58,7 +57,6 @@ class TreatmentsTempTargetFragment : DaggerFragment() { @Inject lateinit var aapsLogger: AAPSLogger @Inject lateinit var profileFunction: ProfileFunction @Inject lateinit var resourceHelper: ResourceHelper - @Inject lateinit var uploadQueue: UploadQueueInterface @Inject lateinit var fabricPrivacy: FabricPrivacy @Inject lateinit var translator: Translator @Inject lateinit var dateUtil: DateUtil @@ -99,7 +97,7 @@ class TreatmentsTempTargetFragment : DaggerFragment() { }) } } - val nsUploadOnly = sp.getBoolean(R.string.key_ns_upload_only, true) || !buildHelper.isEngineeringMode() + val nsUploadOnly = !sp.getBoolean(R.string.key_ns_receive_temp_target, false) || !buildHelper.isEngineeringMode() if (nsUploadOnly) binding.refreshFromNightscout.visibility = View.INVISIBLE binding.showInvalidated.setOnCheckedChangeListener { _, _ -> rxBus.send(EventTreatmentUpdateGui()) @@ -151,7 +149,7 @@ class TreatmentsTempTargetFragment : DaggerFragment() { _binding = null } - private inner class RecyclerViewAdapter internal constructor(private var tempTargetList: List) : RecyclerView.Adapter() { + private inner class RecyclerViewAdapter(private var tempTargetList: List) : RecyclerView.Adapter() { private val dbRecord = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet() private val currentlyActiveTarget = if (dbRecord is ValueWrapper.Existing) dbRecord.value else null diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsTemporaryBasalsFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsTemporaryBasalsFragment.kt similarity index 78% rename from app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsTemporaryBasalsFragment.kt rename to app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsTemporaryBasalsFragment.kt index d0ee9dcd5b3..590fef204f3 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsTemporaryBasalsFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsTemporaryBasalsFragment.kt @@ -1,4 +1,4 @@ -package info.nightscout.androidaps.plugins.treatments.fragments +package info.nightscout.androidaps.activities.fragments import android.content.DialogInterface import android.graphics.Paint @@ -12,12 +12,15 @@ import dagger.android.support.DaggerFragment 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.ExtendedBolus import info.nightscout.androidaps.database.entities.TemporaryBasal 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 import info.nightscout.androidaps.database.interfaces.end +import info.nightscout.androidaps.database.transactions.InvalidateExtendedBolusTransaction import info.nightscout.androidaps.database.transactions.InvalidateTemporaryBasalTransaction import info.nightscout.androidaps.databinding.TreatmentsTempbasalsFragmentBinding import info.nightscout.androidaps.databinding.TreatmentsTempbasalsItemBinding @@ -33,7 +36,7 @@ import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.plugins.bus.RxBusWrapper -import info.nightscout.androidaps.plugins.treatments.fragments.TreatmentsTemporaryBasalsFragment.RecyclerViewAdapter.TempBasalsViewHolder +import info.nightscout.androidaps.activities.fragments.TreatmentsTemporaryBasalsFragment.RecyclerViewAdapter.TempBasalsViewHolder import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.T @@ -42,6 +45,7 @@ import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.rx.AapsSchedulers import io.reactivex.disposables.CompositeDisposable import io.reactivex.rxkotlin.plusAssign +import java.util.concurrent.TimeUnit import javax.inject.Inject import kotlin.math.abs @@ -194,21 +198,41 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() { init { binding.remove.setOnClickListener { v: View -> val tempBasal = v.tag as TemporaryBasal + var extendedBolus: ExtendedBolus? = null + val isFakeExtended = tempBasal.type == TemporaryBasal.Type.FAKE_EXTENDED + if (isFakeExtended) { + val eb = repository.getExtendedBolusActiveAt(tempBasal.timestamp).blockingGet() + extendedBolus = if (eb is ValueWrapper.Existing) eb.value else null + } val profile = profileFunction.getProfile(dateUtil.now()) ?: return@setOnClickListener context?.let { OKDialog.showConfirmation(it, resourceHelper.gs(R.string.removerecord), """ - ${resourceHelper.gs(R.string.tempbasal_label)}: ${tempBasal.toStringFull(profile, dateUtil)} + ${if (isFakeExtended) resourceHelper.gs(R.string.extended_bolus) else resourceHelper.gs(R.string.tempbasal_label)}: ${tempBasal.toStringFull(profile, dateUtil)} ${resourceHelper.gs(R.string.date)}: ${dateUtil.dateAndTimeString(tempBasal.timestamp)} """.trimIndent(), { _: DialogInterface?, _: Int -> - uel.log(Action.TEMP_BASAL_REMOVED, Sources.Treatments, - ValueWithUnit.Timestamp(tempBasal.timestamp)) - disposable += repository.runTransactionForResult(InvalidateTemporaryBasalTransaction(tempBasal.id)) - .subscribe( - { aapsLogger.debug(LTag.DATABASE, "Removed temporary basal $tempBasal") }, - { aapsLogger.error(LTag.DATABASE, "Error while invalidating temporary basal", it) }) + if (isFakeExtended && extendedBolus != null) { + uel.log(Action.EXTENDED_BOLUS_REMOVED, Sources.Treatments, + ValueWithUnit.Timestamp(extendedBolus.timestamp), + ValueWithUnit.Insulin(extendedBolus.amount), + ValueWithUnit.UnitPerHour(extendedBolus.rate), + ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(extendedBolus.duration).toInt())) + disposable += repository.runTransactionForResult(InvalidateExtendedBolusTransaction(extendedBolus.id)) + .subscribe( + { aapsLogger.debug(LTag.DATABASE, "Removed extended bolus $extendedBolus") }, + { aapsLogger.error(LTag.DATABASE, "Error while invalidating extended bolus", it) }) + } else if (!isFakeExtended) { + uel.log(Action.TEMP_BASAL_REMOVED, Sources.Treatments, + ValueWithUnit.Timestamp(tempBasal.timestamp), + if (tempBasal.isAbsolute) ValueWithUnit.UnitPerHour(tempBasal.rate) else ValueWithUnit.Percent(tempBasal.rate.toInt()), + ValueWithUnit.Minute(T.msecs(tempBasal.duration).mins().toInt())) + disposable += repository.runTransactionForResult(InvalidateTemporaryBasalTransaction(tempBasal.id)) + .subscribe( + { aapsLogger.debug(LTag.DATABASE, "Removed temporary basal $tempBasal") }, + { aapsLogger.error(LTag.DATABASE, "Error while invalidating temporary basal", it) }) + } }, null) } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsUserEntryFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsUserEntryFragment.kt similarity index 99% rename from app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsUserEntryFragment.kt rename to app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsUserEntryFragment.kt index 9bbc46d63bc..b0936580ae7 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsUserEntryFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/fragments/TreatmentsUserEntryFragment.kt @@ -1,4 +1,4 @@ -package info.nightscout.androidaps.plugins.treatments.fragments +package info.nightscout.androidaps.activities.fragments import android.os.Bundle import android.view.LayoutInflater diff --git a/app/src/main/java/info/nightscout/androidaps/data/defaultProfile/DefaultProfile.kt b/app/src/main/java/info/nightscout/androidaps/data/defaultProfile/DefaultProfile.kt index 2596ef95831..fa8b2774be1 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/defaultProfile/DefaultProfile.kt +++ b/app/src/main/java/info/nightscout/androidaps/data/defaultProfile/DefaultProfile.kt @@ -1,7 +1,5 @@ package info.nightscout.androidaps.data.defaultProfile -import dagger.android.HasAndroidInjector -import info.nightscout.androidaps.data.ProfileImplOld import info.nightscout.androidaps.data.PureProfile import info.nightscout.androidaps.extensions.pureProfileFromJson import info.nightscout.androidaps.interfaces.GlucoseUnit diff --git a/app/src/main/java/info/nightscout/androidaps/data/defaultProfile/DefaultProfileDPV.kt b/app/src/main/java/info/nightscout/androidaps/data/defaultProfile/DefaultProfileDPV.kt index a0a555cc300..78ed97b3350 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/defaultProfile/DefaultProfileDPV.kt +++ b/app/src/main/java/info/nightscout/androidaps/data/defaultProfile/DefaultProfileDPV.kt @@ -1,7 +1,6 @@ package info.nightscout.androidaps.data.defaultProfile import dagger.android.HasAndroidInjector -import info.nightscout.androidaps.data.ProfileImplOld import info.nightscout.androidaps.data.PureProfile import info.nightscout.androidaps.extensions.pureProfileFromJson import info.nightscout.androidaps.interfaces.GlucoseUnit 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 3aa64ddee0c..1f238d014ad 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/CompatDBHelper.kt +++ b/app/src/main/java/info/nightscout/androidaps/db/CompatDBHelper.kt @@ -75,12 +75,16 @@ class CompatDBHelper @Inject constructor( rxBus.send(EventFoodDatabaseChanged()) } it.filterIsInstance().firstOrNull()?.let { - aapsLogger.debug(LTag.DATABASE, "Firing EventProfileNeedsUpdate") + aapsLogger.debug(LTag.DATABASE, "Firing EventProfileSwitchChanged") rxBus.send(EventProfileSwitchChanged()) } it.filterIsInstance().firstOrNull()?.let { - aapsLogger.debug(LTag.DATABASE, "Firing EventProfileNeedsUpdate") + aapsLogger.debug(LTag.DATABASE, "Firing EventProfileSwitchChanged") rxBus.send(EventProfileSwitchChanged()) } + it.filterIsInstance().firstOrNull()?.let { + aapsLogger.debug(LTag.DATABASE, "Firing EventOfflineChange") + rxBus.send(EventOfflineChange()) + } } } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java b/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java index 1520dd0bbd8..c323a475bef 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java +++ b/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java @@ -4,8 +4,6 @@ import android.database.DatabaseUtils; import android.database.sqlite.SQLiteDatabase; -import androidx.annotation.Nullable; - import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper; import com.j256.ormlite.dao.CloseableIterator; import com.j256.ormlite.dao.Dao; @@ -20,19 +18,14 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; import javax.inject.Inject; import info.nightscout.androidaps.events.EventRefreshOverview; import info.nightscout.androidaps.interfaces.ActivePlugin; -import info.nightscout.androidaps.interfaces.DatabaseHelperInterface; import info.nightscout.androidaps.logging.AAPSLogger; import info.nightscout.androidaps.logging.LTag; import info.nightscout.androidaps.plugins.bus.RxBusWrapper; -import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; import info.nightscout.androidaps.plugins.general.openhumans.OpenHumansUploader; import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin; import info.nightscout.androidaps.utils.DateUtil; @@ -51,12 +44,9 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { @Inject VirtualPumpPlugin virtualPumpPlugin; @Inject OpenHumansUploader openHumansUploader; @Inject ActivePlugin activePlugin; - @Inject NSUpload nsUpload; @Inject DateUtil dateUtil; public static final String DATABASE_NAME = "AndroidAPSDb"; - public static final String DATABASE_DANARHISTORY = "DanaRHistory"; - public static final String DATABASE_DBREQUESTS = "DBRequests"; private static final int DATABASE_VERSION = 13; @@ -76,19 +66,8 @@ public DatabaseHelper(Context context) { public void onCreate(SQLiteDatabase database, ConnectionSource connectionSource) { try { aapsLogger.info(LTag.DATABASE, "onCreate"); - TableUtils.createTableIfNotExists(connectionSource, DanaRHistoryRecord.class); - TableUtils.createTableIfNotExists(connectionSource, DbRequest.class); - TableUtils.createTableIfNotExists(connectionSource, TemporaryBasal.class); - TableUtils.createTableIfNotExists(connectionSource, ExtendedBolus.class); - TableUtils.createTableIfNotExists(connectionSource, InsightHistoryOffset.class); - TableUtils.createTableIfNotExists(connectionSource, InsightBolusID.class); - TableUtils.createTableIfNotExists(connectionSource, InsightPumpID.class); TableUtils.createTableIfNotExists(connectionSource, OmnipodHistoryRecord.class); TableUtils.createTableIfNotExists(connectionSource, OHQueueItem.class); - database.execSQL("INSERT INTO sqlite_sequence (name, seq) SELECT \"" + DatabaseHelperInterface.Companion.DATABASE_INSIGHT_BOLUS_IDS + "\", " + System.currentTimeMillis() + " " + - "WHERE NOT EXISTS (SELECT 1 FROM sqlite_sequence WHERE name = \"" + DatabaseHelperInterface.Companion.DATABASE_INSIGHT_BOLUS_IDS + "\")"); - database.execSQL("INSERT INTO sqlite_sequence (name, seq) SELECT \"" + DatabaseHelperInterface.Companion.DATABASE_INSIGHT_PUMP_IDS + "\", " + System.currentTimeMillis() + " " + - "WHERE NOT EXISTS (SELECT 1 FROM sqlite_sequence WHERE name = \"" + DatabaseHelperInterface.Companion.DATABASE_INSIGHT_PUMP_IDS + "\")"); } catch (SQLException e) { aapsLogger.error("Can't create database", e); throw new RuntimeException(e); @@ -103,22 +82,7 @@ public void onUpgrade(SQLiteDatabase database, ConnectionSource connectionSource if (oldVersion < 7) { aapsLogger.info(LTag.DATABASE, "onUpgrade"); - TableUtils.dropTable(connectionSource, DanaRHistoryRecord.class, true); - TableUtils.dropTable(connectionSource, DbRequest.class, true); - TableUtils.dropTable(connectionSource, TemporaryBasal.class, true); - TableUtils.dropTable(connectionSource, ExtendedBolus.class, true); onCreate(database, connectionSource); - } else if (oldVersion < 10) { - TableUtils.createTableIfNotExists(connectionSource, InsightHistoryOffset.class); - TableUtils.createTableIfNotExists(connectionSource, InsightBolusID.class); - TableUtils.createTableIfNotExists(connectionSource, InsightPumpID.class); - database.execSQL("INSERT INTO sqlite_sequence (name, seq) SELECT \"" + DatabaseHelperInterface.Companion.DATABASE_INSIGHT_BOLUS_IDS + "\", " + System.currentTimeMillis() + " " + - "WHERE NOT EXISTS (SELECT 1 FROM sqlite_sequence WHERE name = \"" + DatabaseHelperInterface.Companion.DATABASE_INSIGHT_BOLUS_IDS + "\")"); - database.execSQL("INSERT INTO sqlite_sequence (name, seq) SELECT \"" + DatabaseHelperInterface.Companion.DATABASE_INSIGHT_PUMP_IDS + "\", " + System.currentTimeMillis() + " " + - "WHERE NOT EXISTS (SELECT 1 FROM sqlite_sequence WHERE name = \"" + DatabaseHelperInterface.Companion.DATABASE_INSIGHT_PUMP_IDS + "\")"); - } else if (oldVersion < 11) { - database.execSQL("UPDATE sqlite_sequence SET seq = " + System.currentTimeMillis() + " WHERE name = \"" + DatabaseHelperInterface.Companion.DATABASE_INSIGHT_BOLUS_IDS + "\""); - database.execSQL("UPDATE sqlite_sequence SET seq = " + System.currentTimeMillis() + " WHERE name = \"" + DatabaseHelperInterface.Companion.DATABASE_INSIGHT_PUMP_IDS + "\""); } TableUtils.createTableIfNotExists(connectionSource, OHQueueItem.class); } catch (SQLException e) { @@ -149,15 +113,7 @@ public long size(String database) { public void resetDatabases() { try { - TableUtils.dropTable(connectionSource, DanaRHistoryRecord.class, true); - TableUtils.dropTable(connectionSource, DbRequest.class, true); - TableUtils.dropTable(connectionSource, TemporaryBasal.class, true); - TableUtils.dropTable(connectionSource, ExtendedBolus.class, true); TableUtils.dropTable(connectionSource, OmnipodHistoryRecord.class, true); - TableUtils.createTableIfNotExists(connectionSource, DanaRHistoryRecord.class); - TableUtils.createTableIfNotExists(connectionSource, DbRequest.class); - TableUtils.createTableIfNotExists(connectionSource, TemporaryBasal.class); - TableUtils.createTableIfNotExists(connectionSource, ExtendedBolus.class); TableUtils.createTableIfNotExists(connectionSource, OmnipodHistoryRecord.class); updateEarliestDataChange(0); } catch (SQLException e) { @@ -177,34 +133,6 @@ public void run() { // ------------------ getDao ------------------------------------------- - private Dao getDaoDanaRHistory() throws SQLException { - return getDao(DanaRHistoryRecord.class); - } - - private Dao getDaoDbRequest() throws SQLException { - return getDao(DbRequest.class); - } - - private Dao getDaoTemporaryBasal() throws SQLException { - return getDao(TemporaryBasal.class); - } - - private Dao getDaoExtendedBolus() throws SQLException { - return getDao(ExtendedBolus.class); - } - - private Dao getDaoInsightPumpID() throws SQLException { - return getDao(InsightPumpID.class); - } - - private Dao getDaoInsightBolusID() throws SQLException { - return getDao(InsightBolusID.class); - } - - private Dao getDaoInsightHistoryOffset() throws SQLException { - return getDao(InsightHistoryOffset.class); - } - private Dao getDaoPodHistory() throws SQLException { return getDao(OmnipodHistoryRecord.class); } @@ -213,76 +141,6 @@ private Dao getDaoOpenHumansQueue() throws SQLException { return getDao(OHQueueItem.class); } - public long roundDateToSec(long date) { - long rounded = date - date % 1000; - if (rounded != date) - aapsLogger.debug(LTag.DATABASE, "Rounding " + date + " to " + rounded); - return rounded; - } - - // ------------- DbRequests handling ------------------- - - public void create(DbRequest dbr) throws SQLException { - getDaoDbRequest().create(dbr); - } - - public int delete(DbRequest dbr) { - try { - return getDaoDbRequest().delete(dbr); - } catch (SQLException e) { - aapsLogger.error("Unhandled exception", e); - } - return 0; - } - - public int deleteDbRequest(String nsClientId) { - try { - return getDaoDbRequest().deleteById(nsClientId); - } catch (SQLException e) { - aapsLogger.error("Unhandled exception", e); - } - return 0; - } - - public void deleteDbRequestbyMongoId(String action, String id) { - try { - QueryBuilder queryBuilder = getDaoDbRequest().queryBuilder(); - // By nsID - Where where = queryBuilder.where(); - where.eq("_id", id).and().eq("action", action); - queryBuilder.limit(10L); - PreparedQuery preparedQuery = queryBuilder.prepare(); - List dbList = getDaoDbRequest().query(preparedQuery); - for (DbRequest r : dbList) delete(r); - // By nsClientID - where = queryBuilder.where(); - where.eq("nsClientID", id).and().eq("action", action); - queryBuilder.limit(10L); - preparedQuery = queryBuilder.prepare(); - dbList = getDaoDbRequest().query(preparedQuery); - for (DbRequest r : dbList) delete(r); - } catch (SQLException e) { - aapsLogger.error("Unhandled exception", e); - } - } - - public void deleteAllDbRequests() { - try { - TableUtils.clearTable(connectionSource, DbRequest.class); - } catch (SQLException e) { - aapsLogger.error("Unhandled exception", e); - } - } - - public CloseableIterator getDbRequestIterator() { - try { - return getDaoDbRequest().closeableIterator(); - } catch (SQLException e) { - aapsLogger.error("Unhandled exception", e); - return null; - } - } - public static void updateEarliestDataChange(long newDate) { if (earliestDataChange == null) { earliestDataChange = newDate; @@ -293,495 +151,6 @@ public static void updateEarliestDataChange(long newDate) { } } - // ----------------- DanaRHistory handling -------------------- - - public void createOrUpdate(DanaRHistoryRecord record) { - try { - getDaoDanaRHistory().createOrUpdate(record); - } catch (SQLException e) { - aapsLogger.error("Unhandled exception", e); - } - } - - public List getDanaRHistoryRecordsByType(byte type) { - List historyList; - try { - QueryBuilder queryBuilder = getDaoDanaRHistory().queryBuilder(); - queryBuilder.orderBy("recordDate", false); - Where where = queryBuilder.where(); - where.eq("recordCode", type); - queryBuilder.limit(200L); - PreparedQuery preparedQuery = queryBuilder.prepare(); - historyList = getDaoDanaRHistory().query(preparedQuery); - } catch (SQLException e) { - aapsLogger.error("Unhandled exception", e); - historyList = new ArrayList<>(); - } - return historyList; - } - - // ------------ TemporaryBasal handling --------------- - - //return true if new record was created - public boolean createOrUpdate(TemporaryBasal tempBasal) { - try { - TemporaryBasal old; - tempBasal.date = roundDateToSec(tempBasal.date); - - if (tempBasal.source == Source.PUMP) { - // check for changed from pump change in NS - QueryBuilder queryBuilder = getDaoTemporaryBasal().queryBuilder(); - Where where = queryBuilder.where(); - where.eq("pumpId", tempBasal.pumpId); - PreparedQuery preparedQuery = queryBuilder.prepare(); - List trList = getDaoTemporaryBasal().query(preparedQuery); - if (trList.size() > 0) { - // do nothing, pump history record cannot be changed - aapsLogger.debug(LTag.DATABASE, "TEMPBASAL: Already exists from: " + Source.getString(tempBasal.source) + " " + tempBasal.toString()); - return false; - } - - // search by date (in case its standard record that has become pump record) - QueryBuilder queryBuilder2 = getDaoTemporaryBasal().queryBuilder(); - Where where2 = queryBuilder2.where(); - where2.eq("date", tempBasal.date); - PreparedQuery preparedQuery2 = queryBuilder2.prepare(); - List trList2 = getDaoTemporaryBasal().query(preparedQuery2); - - if (trList2.size() > 0) { - old = trList2.get(0); - - old.copyFromPump(tempBasal); - old.source = Source.PUMP; - - aapsLogger.debug(LTag.DATABASE, "TEMPBASAL: Updated record with Pump Data : " + Source.getString(tempBasal.source) + " " + tempBasal.toString()); - - getDaoTemporaryBasal().update(old); - openHumansUploader.enqueueTemporaryBasal(old); - - updateEarliestDataChange(tempBasal.date); -// scheduleTemporaryBasalChange(); - - return false; - } - - getDaoTemporaryBasal().create(tempBasal); - openHumansUploader.enqueueTemporaryBasal(tempBasal); - aapsLogger.debug(LTag.DATABASE, "TEMPBASAL: New record from: " + Source.getString(tempBasal.source) + " " + tempBasal.toString()); - updateEarliestDataChange(tempBasal.date); -// scheduleTemporaryBasalChange(); - return true; - } - if (tempBasal.source == Source.NIGHTSCOUT) { - old = getDaoTemporaryBasal().queryForId(tempBasal.date); - if (old != null) { - if (!old.isAbsolute && tempBasal.isAbsolute) { // converted to absolute by "ns_sync_use_absolute" - // so far ignore, do not convert back because it may not be accurate - return false; - } - if (!old.isEqual(tempBasal)) { - long oldDate = old.date; - getDaoTemporaryBasal().delete(old); // need to delete/create because date may change too - old.copyFrom(tempBasal); - getDaoTemporaryBasal().create(old); - openHumansUploader.enqueueTemporaryBasal(old); - aapsLogger.debug(LTag.DATABASE, "TEMPBASAL: Updating record by date from: " + Source.getString(tempBasal.source) + " " + old.toString()); - updateEarliestDataChange(oldDate); - updateEarliestDataChange(old.date); -// scheduleTemporaryBasalChange(); - return true; - } - return false; - } - // find by NS _id - if (tempBasal._id != null) { - QueryBuilder queryBuilder = getDaoTemporaryBasal().queryBuilder(); - Where where = queryBuilder.where(); - where.eq("_id", tempBasal._id); - PreparedQuery preparedQuery = queryBuilder.prepare(); - List trList = getDaoTemporaryBasal().query(preparedQuery); - if (trList.size() > 0) { - old = trList.get(0); - if (!old.isEqual(tempBasal)) { - long oldDate = old.date; - getDaoTemporaryBasal().delete(old); // need to delete/create because date may change too - old.copyFrom(tempBasal); - getDaoTemporaryBasal().create(old); - openHumansUploader.enqueueTemporaryBasal(old); - aapsLogger.debug(LTag.DATABASE, "TEMPBASAL: Updating record by _id from: " + Source.getString(tempBasal.source) + " " + old.toString()); - updateEarliestDataChange(oldDate); - updateEarliestDataChange(old.date); -// scheduleTemporaryBasalChange(); - return true; - } - } - } - getDaoTemporaryBasal().create(tempBasal); - openHumansUploader.enqueueTemporaryBasal(tempBasal); - aapsLogger.debug(LTag.DATABASE, "TEMPBASAL: New record from: " + Source.getString(tempBasal.source) + " " + tempBasal.toString()); - updateEarliestDataChange(tempBasal.date); -// scheduleTemporaryBasalChange(); - return true; - } - if (tempBasal.source == Source.USER) { - getDaoTemporaryBasal().create(tempBasal); - openHumansUploader.enqueueTemporaryBasal(tempBasal); - aapsLogger.debug(LTag.DATABASE, "TEMPBASAL: New record from: " + Source.getString(tempBasal.source) + " " + tempBasal.toString()); - updateEarliestDataChange(tempBasal.date); -// scheduleTemporaryBasalChange(); - return true; - } - } catch (SQLException e) { - aapsLogger.error("Unhandled exception", e); - } - return false; - } - - public void delete(TemporaryBasal tempBasal) { - try { - getDaoTemporaryBasal().delete(tempBasal); - openHumansUploader.enqueueTemporaryBasal(tempBasal, true); - updateEarliestDataChange(tempBasal.date); - } catch (SQLException e) { - aapsLogger.error("Unhandled exception", e); - } -// scheduleTemporaryBasalChange(); - } - - public List getTemporaryBasalsDataFromTime(long mills, boolean ascending) { - try { - List tempbasals; - QueryBuilder queryBuilder = getDaoTemporaryBasal().queryBuilder(); - queryBuilder.orderBy("date", ascending); - Where where = queryBuilder.where(); - where.ge("date", mills); - PreparedQuery preparedQuery = queryBuilder.prepare(); - tempbasals = getDaoTemporaryBasal().query(preparedQuery); - return tempbasals; - } catch (SQLException e) { - aapsLogger.error("Unhandled exception", e); - } - return new ArrayList(); - } - - /* - { - "_id": "59232e1ddd032d04218dab00", - "eventType": "Temp Basal", - "duration": 60, - "percent": -50, - "created_at": "2017-05-22T18:29:57Z", - "enteredBy": "AndroidAPS", - "notes": "Basal Temp Start 50% 60.0 min", - "NSCLIENT_ID": 1495477797863, - "mills": 1495477797000, - "mgdl": 194.5, - "endmills": 1495481397000 - } - */ - - public TemporaryBasal findTempBasalByPumpId(Long pumpId) { - try { - QueryBuilder queryBuilder = null; - queryBuilder = getDaoTemporaryBasal().queryBuilder(); - queryBuilder.orderBy("date", false); - Where where = queryBuilder.where(); - where.eq("pumpId", pumpId); - PreparedQuery preparedQuery = queryBuilder.prepare(); - List list = getDaoTemporaryBasal().query(preparedQuery); - - if (list.size() > 0) - return list.get(0); - else - return null; - - } catch (SQLException e) { - aapsLogger.error("Unhandled exception", e); - } - return null; - } - - - // ------------ ExtendedBolus handling --------------- - - public ExtendedBolus getExtendedBolusByPumpId(long pumpId) { - try { - return getDaoExtendedBolus().queryBuilder() - .where().eq("pumpId", pumpId) - .queryForFirst(); - } catch (SQLException e) { - aapsLogger.error("Unhandled exception", e); - } - return null; - } - - public void delete(ExtendedBolus extendedBolus) { - try { - getDaoExtendedBolus().delete(extendedBolus); - openHumansUploader.enqueueExtendedBolus(extendedBolus, true); - updateEarliestDataChange(extendedBolus.date); - } catch (SQLException e) { - aapsLogger.error("Unhandled exception", e); - } -// scheduleExtendedBolusChange(); - } - - /* -{ - "_id": "5924898d577eb0880e355337", - "eventType": "Combo Bolus", - "duration": 120, - "splitNow": 0, - "splitExt": 100, - "enteredinsulin": 1, - "relative": 1, - "created_at": "2017-05-23T19:12:14Z", - "enteredBy": "AndroidAPS", - "NSCLIENT_ID": 1495566734628, - "mills": 1495566734000, - "mgdl": 106 -} - */ - - // ---------------- ProfileSwitch handling --------------- - -/* - public boolean createOrUpdate(ProfileSwitch profileSwitch) { - try { - ProfileSwitch old; - profileSwitch.date = roundDateToSec(profileSwitch.date); - - if (profileSwitch.source == Source.NIGHTSCOUT) { - old = getDaoProfileSwitch().queryForId(profileSwitch.date); - if (old != null) { - if (!old.isEqual(profileSwitch)) { - profileSwitch.source = old.source; - profileSwitch.profileName = old.profileName; // preserver profileName to prevent multiple CPP extension - getDaoProfileSwitch().delete(old); // need to delete/create because date may change too - getDaoProfileSwitch().create(profileSwitch); - aapsLogger.debug(LTag.DATABASE, "PROFILESWITCH: Updating record by date from: " + Source.getString(profileSwitch.source) + " " + old.toString()); - openHumansUploader.enqueueProfileSwitch(profileSwitch); - scheduleProfileSwitchChange(); - return true; - } - return false; - } - // find by NS _id - if (profileSwitch._id != null) { - QueryBuilder queryBuilder = getDaoProfileSwitch().queryBuilder(); - Where where = queryBuilder.where(); - where.eq("_id", profileSwitch._id); - PreparedQuery preparedQuery = queryBuilder.prepare(); - List trList = getDaoProfileSwitch().query(preparedQuery); - if (trList.size() > 0) { - old = trList.get(0); - if (!old.isEqual(profileSwitch)) { - getDaoProfileSwitch().delete(old); // need to delete/create because date may change too - old.copyFrom(profileSwitch); - getDaoProfileSwitch().create(old); - aapsLogger.debug(LTag.DATABASE, "PROFILESWITCH: Updating record by _id from: " + Source.getString(profileSwitch.source) + " " + old.toString()); - openHumansUploader.enqueueProfileSwitch(old); - scheduleProfileSwitchChange(); - return true; - } - } - } - // look for already added percentage from NS - profileSwitch.profileName = PercentageSplitter.INSTANCE.pureName(profileSwitch.profileName); - getDaoProfileSwitch().create(profileSwitch); - aapsLogger.debug(LTag.DATABASE, "PROFILESWITCH: New record from: " + Source.getString(profileSwitch.source) + " " + profileSwitch.toString()); - openHumansUploader.enqueueProfileSwitch(profileSwitch); - scheduleProfileSwitchChange(); - return true; - } - if (profileSwitch.source == Source.USER) { - getDaoProfileSwitch().create(profileSwitch); - aapsLogger.debug(LTag.DATABASE, "PROFILESWITCH: New record from: " + Source.getString(profileSwitch.source) + " " + profileSwitch.toString()); - openHumansUploader.enqueueProfileSwitch(profileSwitch); - scheduleProfileSwitchChange(); - return true; - } - } catch (SQLException e) { - aapsLogger.error("Unhandled exception", e); - } - return false; - } - - public void delete(ProfileSwitch profileSwitch) { - try { - getDaoProfileSwitch().delete(profileSwitch); - openHumansUploader.enqueueProfileSwitch(profileSwitch, true); - scheduleProfileSwitchChange(); - } catch (SQLException e) { - aapsLogger.error("Unhandled exception", e); - } - } - - private void scheduleProfileSwitchChange() { - class PostRunnable implements Runnable { - public void run() { - aapsLogger.debug(LTag.DATABASE, "Firing EventProfileNeedsUpdate"); - rxBus.send(new EventReloadProfileSwitchData()); - rxBus.send(new EventProfileNeedsUpdate()); - scheduledProfileSwitchEventPost = null; - } - } - // prepare task for execution in 1 sec - // cancel waiting task to prevent sending multiple posts - if (scheduledProfileSwitchEventPost != null) - scheduledProfileSwitchEventPost.cancel(false); - Runnable task = new PostRunnable(); - final int sec = 1; - scheduledProfileSwitchEventPost = profileSwitchEventWorker.schedule(task, sec, TimeUnit.SECONDS); - - } -*/ - /* -{ - "_id":"592fa43ed97496a80da913d2", - "created_at":"2017-06-01T05:20:06Z", - "eventType":"Profile Switch", - "profile":"2016 +30%", - "units":"mmol", - "enteredBy":"sony", - "NSCLIENT_ID":1496294454309, -} - */ -/* - - public void createProfileSwitchFromJsonIfNotExists(JSONObject trJson) { - try { - ProfileSwitch profileSwitch = new ProfileSwitch(StaticInjector.Companion.getInstance()); - profileSwitch.date = trJson.getLong("mills"); - if (trJson.has("duration")) - profileSwitch.durationInMinutes = trJson.getInt("duration"); - profileSwitch._id = trJson.getString("_id"); - profileSwitch.profileName = trJson.getString("profile"); - profileSwitch.isCPP = trJson.has("CircadianPercentageProfile"); - profileSwitch.source = Source.NIGHTSCOUT; - if (trJson.has("timeshift")) - profileSwitch.timeshift = trJson.getInt("timeshift"); - if (trJson.has("percentage")) - profileSwitch.percentage = trJson.getInt("percentage"); - if (trJson.has("profileJson")) - profileSwitch.profileJson = trJson.getString("profileJson"); - else { - ProfileSource profileSource = activePlugin.getActiveProfileSource(); - ProfileStore store = profileSource.getProfile(); - if (store != null) { - PureProfile profile = store.getSpecificProfile(profileSwitch.profileName); - if (profile != null) { - profileSwitch.profileJson = profile.getJsonObject().toString(); - aapsLogger.debug(LTag.DATABASE, "Profile switch prefilled with JSON from local store"); - // Update data in NS - nsUpload.updateProfileSwitch(profileSwitch, dateUtil); - } else { - aapsLogger.debug(LTag.DATABASE, "JSON for profile switch doesn't exist. Ignoring: " + trJson.toString()); - return; - } - } else { - aapsLogger.debug(LTag.DATABASE, "Store for profile switch doesn't exist. Ignoring: " + trJson.toString()); - return; - } - } - if (trJson.has("profilePlugin")) - profileSwitch.profilePlugin = trJson.getString("profilePlugin"); - createOrUpdate(profileSwitch); - } catch (JSONException e) { - aapsLogger.error("Unhandled exception: " + trJson.toString(), e); - } - } - - public void deleteProfileSwitchById(String _id) { - ProfileSwitch stored = findProfileSwitchById(_id); - if (stored != null) { - aapsLogger.debug(LTag.DATABASE, "PROFILESWITCH: Removing ProfileSwitch record from database: " + stored.toString()); - delete(stored); - scheduleProfileSwitchChange(); - } - } - - public ProfileSwitch findProfileSwitchById(String _id) { - try { - QueryBuilder queryBuilder = getDaoProfileSwitch().queryBuilder(); - Where where = queryBuilder.where(); - where.eq("_id", _id); - PreparedQuery preparedQuery = queryBuilder.prepare(); - List list = getDaoProfileSwitch().query(preparedQuery); - - if (list.size() == 1) { - return list.get(0); - } else { - return null; - } - } catch (SQLException e) { - aapsLogger.error("Unhandled exception", e); - } - return null; - } -*/ - // ---------------- Insight history handling --------------- - - public void createOrUpdate(InsightHistoryOffset offset) { - try { - getDaoInsightHistoryOffset().createOrUpdate(offset); - } catch (SQLException e) { - aapsLogger.error("Unhandled exception", e); - } - } - - public InsightHistoryOffset getInsightHistoryOffset(String pumpSerial) { - try { - return getDaoInsightHistoryOffset().queryForId(pumpSerial); - } catch (SQLException e) { - aapsLogger.error("Unhandled exception", e); - } - return null; - } - - public void createOrUpdate(InsightBolusID bolusID) { - try { - getDaoInsightBolusID().createOrUpdate(bolusID); - } catch (SQLException e) { - aapsLogger.error("Unhandled exception", e); - } - } - - public InsightBolusID getInsightBolusID(String pumpSerial, int bolusID, long timestamp) { - try { - return getDaoInsightBolusID().queryBuilder() - .where().eq("pumpSerial", pumpSerial) - .and().eq("bolusID", bolusID) - .and().between("timestamp", timestamp - 259200000, timestamp + 259200000) - .queryForFirst(); - } catch (SQLException e) { - aapsLogger.error("Unhandled exception", e); - } - return null; - } - - public void createOrUpdate(InsightPumpID pumpID) { - try { - getDaoInsightPumpID().createOrUpdate(pumpID); - } catch (SQLException e) { - aapsLogger.error("Unhandled exception", e); - } - } - - public InsightPumpID getPumpStoppedEvent(String pumpSerial, long before) { - try { - return getDaoInsightPumpID().queryBuilder() - .orderBy("timestamp", false) - .where().eq("pumpSerial", pumpSerial) - .and().in("eventType", "PumpStopped", "PumpPaused") - .and().lt("timestamp", before) - .queryForFirst(); - } catch (SQLException e) { - aapsLogger.error("Unhandled exception", e); - } - return null; - } - // ---------------- Food handling --------------- // ---------------- PodHistory handling --------------- @@ -924,14 +293,4 @@ public long getOHQueueSize() { } return 0L; } - - public long getCountOfAllRows() { - try { - return getDaoExtendedBolus().countOf() - + getDaoTemporaryBasal().countOf(); - } catch (SQLException e) { - aapsLogger.error("Unhandled exception", e); - } - return 0L; - } } diff --git a/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelperProvider.java b/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelperProvider.java index 4c3816c934b..da0b2479cf2 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelperProvider.java +++ b/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelperProvider.java @@ -5,8 +5,6 @@ import com.j256.ormlite.dao.CloseableIterator; -import org.json.JSONObject; - import java.sql.SQLException; import java.util.List; @@ -23,61 +21,24 @@ public class DatabaseHelperProvider implements DatabaseHelperInterface { @Inject DatabaseHelperProvider() { } - @Override public void createOrUpdate(@NonNull DanaRHistoryRecord record) { - MainApp.Companion.getDbHelper().createOrUpdate(record); - } - @Override public void createOrUpdate(@NonNull OmnipodHistoryRecord record) { MainApp.Companion.getDbHelper().createOrUpdate(record); } - @NonNull @Override public List getDanaRHistoryRecordsByType(byte type) { - return MainApp.Companion.getDbHelper().getDanaRHistoryRecordsByType(type); - } - - @Override public long size(@NonNull String table) { - return MainApp.Companion.getDbHelper().size(table); - } - - @Override public void create(@NonNull DbRequest record) { - try { - MainApp.Companion.getDbHelper().create(record); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - @Override public void deleteAllDbRequests() { - MainApp.Companion.getDbHelper().deleteAllDbRequests(); - } - - @Override public int deleteDbRequest(@NonNull String id) { - return MainApp.Companion.getDbHelper().deleteDbRequest(id); - } - - @Override public void deleteDbRequestbyMongoId(@NonNull String action, @NonNull String _id) { - MainApp.Companion.getDbHelper().deleteDbRequestbyMongoId(action, _id); - } - - @NonNull @Override public CloseableIterator getDbRequestIterator() { - return MainApp.Companion.getDbHelper().getDbRequestIterator(); - } - - @Override public long roundDateToSec(long date) { - return MainApp.Companion.getDbHelper().roundDateToSec(date); - } - @Override public boolean createOrUpdate(@NonNull TemporaryBasal tempBasal) { - return MainApp.Companion.getDbHelper().createOrUpdate(tempBasal); +// return MainApp.Companion.getDbHelper().createOrUpdate(tempBasal); + return false; } @Nullable @Override public TemporaryBasal findTempBasalByPumpId(long id) { - return MainApp.Companion.getDbHelper().findTempBasalByPumpId(id); +// return MainApp.Companion.getDbHelper().findTempBasalByPumpId(id); + return null; } @Deprecated @NonNull @Override public List getTemporaryBasalsDataFromTime(long mills, boolean ascending) { - return MainApp.Companion.getDbHelper().getTemporaryBasalsDataFromTime(mills, ascending); +// return MainApp.Companion.getDbHelper().getTemporaryBasalsDataFromTime(mills, ascending); + return null; } @NonNull @Override public List getAllOmnipodHistoryRecordsFromTimestamp(long timestamp, boolean ascending) { @@ -88,36 +49,13 @@ public class DatabaseHelperProvider implements DatabaseHelperInterface { return MainApp.Companion.getDbHelper().findOmnipodHistoryRecordByPumpId(pumpId); } - @Override public void createOrUpdate(@NonNull InsightBolusID record) { - MainApp.Companion.getDbHelper().createOrUpdate(record); - } - - @Override public void createOrUpdate(@NonNull InsightPumpID record) { - MainApp.Companion.getDbHelper().createOrUpdate(record); - } - - @Override public void createOrUpdate(@NonNull InsightHistoryOffset record) { - MainApp.Companion.getDbHelper().createOrUpdate(record); - } - @Override public void delete(@NonNull ExtendedBolus extendedBolus) { - MainApp.Companion.getDbHelper().delete(extendedBolus); +// MainApp.Companion.getDbHelper().delete(extendedBolus); } @Nullable @Override public ExtendedBolus getExtendedBolusByPumpId(long pumpId) { - return MainApp.Companion.getDbHelper().getExtendedBolusByPumpId(pumpId); - } - - @Nullable @Override public InsightBolusID getInsightBolusID(@NonNull String pumpSerial, int bolusID, long timestamp) { - return MainApp.Companion.getDbHelper().getInsightBolusID(pumpSerial, bolusID, timestamp); - } - - @Nullable @Override public InsightHistoryOffset getInsightHistoryOffset(@NonNull String pumpSerial) { - return MainApp.Companion.getDbHelper().getInsightHistoryOffset(pumpSerial); - } - - @Nullable @Override public InsightPumpID getPumpStoppedEvent(@NonNull String pumpSerial, long before) { - return MainApp.Companion.getDbHelper().getPumpStoppedEvent(pumpSerial, before); +// return MainApp.Companion.getDbHelper().getExtendedBolusByPumpId(pumpId); + return null; } @Override public void resetDatabases() { @@ -140,10 +78,6 @@ public class DatabaseHelperProvider implements DatabaseHelperInterface { MainApp.Companion.getDbHelper().clearOpenHumansQueue(); } - @Override public long getCountOfAllRows() { - return MainApp.Companion.getDbHelper().getCountOfAllRows(); - } - @Override public void removeAllOHQueueItemsWithIdSmallerThan(long id) { MainApp.Companion.getDbHelper().removeAllOHQueueItemsWithIdSmallerThan(id); } diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ActivitiesModule.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ActivitiesModule.kt index 95e0888b798..08e25f456fb 100644 --- a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ActivitiesModule.kt +++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ActivitiesModule.kt @@ -4,7 +4,7 @@ import dagger.Module import dagger.android.ContributesAndroidInjector import info.nightscout.androidaps.MainActivity import info.nightscout.androidaps.activities.* -import info.nightscout.androidaps.historyBrowser.HistoryBrowseActivity +import info.nightscout.androidaps.activities.HistoryBrowseActivity import info.nightscout.androidaps.plugins.general.maintenance.activities.LogSettingActivity import info.nightscout.androidaps.plugins.general.openhumans.OpenHumansLoginActivity import info.nightscout.androidaps.plugins.general.overview.activities.QuickWizardListActivity @@ -15,6 +15,7 @@ import info.nightscout.androidaps.setupwizard.SetupWizardActivity @Suppress("unused") abstract class ActivitiesModule { + @ContributesAndroidInjector abstract fun contributesTreatmentsActivity(): TreatmentsActivity @ContributesAndroidInjector abstract fun contributesHistoryBrowseActivity(): HistoryBrowseActivity @ContributesAndroidInjector abstract fun contributesLogSettingActivity(): LogSettingActivity @ContributesAndroidInjector abstract fun contributeMainActivity(): MainActivity diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/AppComponent.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/AppComponent.kt index 1412f9d5573..0065e19a96a 100644 --- a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/AppComponent.kt +++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/AppComponent.kt @@ -7,12 +7,15 @@ import dagger.android.AndroidInjector import info.nightscout.androidaps.MainApp import info.nightscout.androidaps.automation.di.AutomationModule import info.nightscout.androidaps.combo.di.ComboModule -import info.nightscout.androidaps.di.CoreModule +import info.nightscout.androidaps.dana.di.DanaHistoryModule import info.nightscout.androidaps.dana.di.DanaModule import info.nightscout.androidaps.danar.di.DanaRModule import info.nightscout.androidaps.danars.di.DanaRSModule -import info.nightscout.androidaps.danars.di.InsightModule import info.nightscout.androidaps.database.DatabaseModule +import info.nightscout.androidaps.di.CoreModule +import info.nightscout.androidaps.insight.di.InsightDatabaseModule +import info.nightscout.androidaps.insight.di.InsightModule +import info.nightscout.androidaps.plugins.pump.common.di.PumpCommonModule import info.nightscout.androidaps.plugins.pump.common.di.RileyLinkModule import info.nightscout.androidaps.plugins.pump.medtronic.di.MedtronicModule import info.nightscout.androidaps.plugins.pump.omnipod.dash.dagger.OmnipodDashModule @@ -35,6 +38,7 @@ import javax.inject.Singleton CommandQueueModule::class, ObjectivesModule::class, WizardModule::class, + PumpCommonModule::class, RileyLinkModule::class, MedtronicModule::class, OmnipodDashModule::class, @@ -47,10 +51,12 @@ import javax.inject.Singleton UIModule::class, CoreModule::class, DanaModule::class, + DanaHistoryModule::class, DanaRModule::class, DanaRSModule::class, ComboModule::class, InsightModule::class, + InsightDatabaseModule::class, WorkersModule::class, OHUploaderModule::class ] diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/AppModule.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/AppModule.kt index 740426792c3..639472c4ff7 100644 --- a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/AppModule.kt +++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/AppModule.kt @@ -6,32 +6,34 @@ import dagger.Lazy import dagger.Module import dagger.Provides import dagger.android.HasAndroidInjector -import info.nightscout.androidaps.interfaces.Config import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.db.DatabaseHelperProvider import info.nightscout.androidaps.interfaces.* import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin -import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin import info.nightscout.androidaps.plugins.configBuilder.PluginStore +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctionImplementation import info.nightscout.androidaps.plugins.general.maintenance.ImportExportPrefsImpl import info.nightscout.androidaps.plugins.general.nsclient.DataSyncSelectorImplementation -import info.nightscout.androidaps.plugins.general.nsclient.UploadQueue import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin import info.nightscout.androidaps.plugins.pump.PumpSyncImplementation import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin import info.nightscout.androidaps.queue.CommandQueue +import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.androidNotification.NotificationHolderImpl import info.nightscout.androidaps.utils.buildHelper.ConfigImpl import info.nightscout.androidaps.utils.resources.IconsProviderImplementation +import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.rx.AapsSchedulers import info.nightscout.androidaps.utils.rx.DefaultAapsSchedulers import info.nightscout.androidaps.utils.sharedPreferences.SP import info.nightscout.androidaps.utils.storage.FileStorage import info.nightscout.androidaps.utils.storage.Storage import javax.inject.Singleton + @Suppress("unused") @Module(includes = [ AppModule.AppBindings::class @@ -64,32 +66,32 @@ open class AppModule { @Provides @Singleton - fun providesUploadQueue( - aapsLogger: AAPSLogger, - databaseHelper: DatabaseHelperInterface, - context: Context, - sp: SP, - rxBus: RxBusWrapper - ): UploadQueueAdminInterface = UploadQueue(aapsLogger, databaseHelper, context, sp, rxBus) + fun provideProfileFunction(aapsLogger: AAPSLogger, sp: SP, resourceHelper: ResourceHelper, activePlugin: ActivePlugin, repository: AppRepository, dateUtil: DateUtil): ProfileFunction { + return ProfileFunctionImplementation(aapsLogger, sp, resourceHelper, activePlugin, repository, dateUtil) + } @Module interface AppBindings { + @Binds fun bindContext(mainApp: MainApp): Context @Binds fun bindInjector(mainApp: MainApp): HasAndroidInjector @Binds fun bindActivePluginProvider(pluginStore: PluginStore): ActivePlugin @Binds fun bindCommandQueueProvider(commandQueue: CommandQueue): CommandQueueProvider @Binds fun bindConfigInterface(config: ConfigImpl): Config - @Binds fun bindConfigBuilderInterface(configBuilderPlugin: ConfigBuilderPlugin): ConfigBuilder + + @Binds + fun bindConfigBuilderInterface(configBuilderPlugin: ConfigBuilderPlugin): ConfigBuilder @Binds fun bindTreatmentsInterface(treatmentsPlugin: TreatmentsPlugin): TreatmentsInterface + @Binds fun bindDatabaseHelperInterface(databaseHelperProvider: DatabaseHelperProvider): DatabaseHelperInterface @Binds fun bindNotificationHolderInterface(notificationHolder: NotificationHolderImpl): NotificationHolder @Binds fun bindImportExportPrefsInterface(importExportPrefs: ImportExportPrefsImpl): ImportExportPrefs @Binds fun bindIconsProviderInterface(iconsProvider: IconsProviderImplementation): IconsProvider - @Binds fun bindLoopInterface(loopPlugin: LoopPlugin): LoopInterface + @Binds fun bindLoopInterface(loopPlugin: LoopPlugin): Loop @Binds fun bindIobCobCalculatorInterface(iobCobCalculatorPlugin: IobCobCalculatorPlugin): IobCobCalculator @Binds fun bindSmsCommunicatorInterface(smsCommunicatorPlugin: SmsCommunicatorPlugin): SmsCommunicator - @Binds fun bindUploadQueueAdminInterfaceToUploadQueue(uploadQueueAdminInterface: UploadQueueAdminInterface) : UploadQueueInterface @Binds fun bindDataSyncSelector(dataSyncSelectorImplementation: DataSyncSelectorImplementation): DataSyncSelector + @Binds fun bindPumpSync(pumpSyncImplementation: PumpSyncImplementation): PumpSync } diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/FragmentsModule.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/FragmentsModule.kt index 15298fb768a..84e4ef80fb1 100644 --- a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/FragmentsModule.kt +++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/FragmentsModule.kt @@ -29,11 +29,9 @@ import info.nightscout.androidaps.plugins.general.tidepool.TidepoolFragment import info.nightscout.androidaps.plugins.general.wear.WearFragment import info.nightscout.androidaps.plugins.insulin.InsulinFragment import info.nightscout.androidaps.plugins.profile.local.LocalProfileFragment -import info.nightscout.androidaps.plugins.profile.ns.NSProfileFragment import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpFragment import info.nightscout.androidaps.plugins.source.BGSourceFragment -import info.nightscout.androidaps.plugins.treatments.TreatmentsFragment -import info.nightscout.androidaps.plugins.treatments.fragments.* +import info.nightscout.androidaps.activities.fragments.* import info.nightscout.androidaps.utils.protection.PasswordCheck @Module @@ -46,8 +44,7 @@ abstract class FragmentsModule { @ContributesAndroidInjector abstract fun contributesAutomationFragment(): AutomationFragment @ContributesAndroidInjector abstract fun contributesBGSourceFragment(): BGSourceFragment - @ContributesAndroidInjector - abstract fun contributesConfigBuilderFragment(): ConfigBuilderFragment + @ContributesAndroidInjector abstract fun contributesConfigBuilderFragment(): ConfigBuilderFragment @ContributesAndroidInjector abstract fun contributesFoodFragment(): FoodFragment @ContributesAndroidInjector abstract fun contributesInsulinFragment(): InsulinFragment @@ -58,14 +55,11 @@ abstract class FragmentsModule { @ContributesAndroidInjector abstract fun contributesOverviewFragment(): OverviewFragment @ContributesAndroidInjector abstract fun contributesLoopFragment(): LoopFragment @ContributesAndroidInjector abstract fun contributesMaintenanceFragment(): MaintenanceFragment - @ContributesAndroidInjector abstract fun contributesNSProfileFragment(): NSProfileFragment @ContributesAndroidInjector abstract fun contributesNSClientFragment(): NSClientFragment - @ContributesAndroidInjector - abstract fun contributesSmsCommunicatorFragment(): SmsCommunicatorFragment + @ContributesAndroidInjector abstract fun contributesSmsCommunicatorFragment(): SmsCommunicatorFragment @ContributesAndroidInjector abstract fun contributesWearFragment(): WearFragment @ContributesAndroidInjector abstract fun contributesTidepoolFragment(): TidepoolFragment - @ContributesAndroidInjector abstract fun contributesTreatmentsFragment(): TreatmentsFragment @ContributesAndroidInjector abstract fun contributesTreatmentsBolusFragment(): TreatmentsBolusCarbsFragment @ContributesAndroidInjector abstract fun contributesTreatmentsTemporaryBasalsFragment(): TreatmentsTemporaryBasalsFragment @ContributesAndroidInjector abstract fun contributesTreatmentsTempTargetFragment(): TreatmentsTempTargetFragment @@ -85,8 +79,7 @@ abstract class FragmentsModule { @ContributesAndroidInjector abstract fun contributesEditEventDialog(): EditEventDialog @ContributesAndroidInjector abstract fun contributesEditTriggerDialog(): EditTriggerDialog - @ContributesAndroidInjector - abstract fun contributesEditQuickWizardDialog(): EditQuickWizardDialog + @ContributesAndroidInjector abstract fun contributesEditQuickWizardDialog(): EditQuickWizardDialog @ContributesAndroidInjector abstract fun contributesExtendedBolusDialog(): ExtendedBolusDialog @ContributesAndroidInjector abstract fun contributesFillDialog(): FillDialog @@ -102,8 +95,7 @@ abstract class FragmentsModule { @ContributesAndroidInjector abstract fun contributesWizardDialog(): WizardDialog @ContributesAndroidInjector abstract fun contributesWizardInfoDialog(): WizardInfoDialog - @ContributesAndroidInjector - abstract fun contributesExchangeAuthTokenDialot(): OpenHumansLoginActivity.ExchangeAuthTokenDialog + @ContributesAndroidInjector abstract fun contributesExchangeAuthTokenDialog(): OpenHumansLoginActivity.ExchangeAuthTokenDialog @ContributesAndroidInjector abstract fun contributesPasswordCheck(): PasswordCheck } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/PluginsModule.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/PluginsModule.kt index 3d106034080..89ca5530914 100644 --- a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/PluginsModule.kt +++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/PluginsModule.kt @@ -37,7 +37,6 @@ import info.nightscout.androidaps.plugins.insulin.InsulinOrefRapidActingPlugin import info.nightscout.androidaps.plugins.insulin.InsulinOrefUltraRapidActingPlugin import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin -import info.nightscout.androidaps.plugins.profile.ns.NSProfilePlugin import info.nightscout.androidaps.plugins.pump.combo.ComboPlugin import info.nightscout.androidaps.plugins.pump.insight.LocalInsightPlugin import info.nightscout.androidaps.plugins.pump.mdi.MDIPlugin @@ -151,11 +150,11 @@ abstract class PluginsModule { @IntKey(140) abstract fun bindComboPlugin(plugin: ComboPlugin): PluginBase - @Binds - @PumpDriver - @IntoMap - @IntKey(150) - abstract fun bindMedtronicPumpPlugin(plugin: MedtronicPumpPlugin): PluginBase + // @Binds + // @PumpDriver + // @IntoMap + // @IntKey(150) + // abstract fun bindMedtronicPumpPlugin(plugin: MedtronicPumpPlugin): PluginBase @Binds @PumpDriver @@ -202,12 +201,6 @@ abstract class PluginsModule { @Binds @AllConfigs @IntoMap - @IntKey(230) - abstract fun bindNSProfilePlugin(plugin: NSProfilePlugin): PluginBase - - @Binds - @NotNSClient - @IntoMap @IntKey(240) abstract fun bindLocalProfilePlugin(plugin: LocalProfilePlugin): PluginBase @@ -355,11 +348,11 @@ abstract class PluginsModule { @IntKey(470) abstract fun bindRandomBgPlugin(plugin: RandomBgPlugin): PluginBase - @Binds - @NotNSClient - @IntoMap - @IntKey(480) - abstract fun bindOpenHumansPlugin(plugin: OpenHumansUploader): PluginBase + // @Binds + // @NotNSClient + // @IntoMap + // @IntKey(480) + // abstract fun bindOpenHumansPlugin(plugin: OpenHumansUploader): PluginBase @Binds @AllConfigs @@ -379,4 +372,4 @@ abstract class PluginsModule { @Qualifier annotation class APS -} \ No newline at end of file +} diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/WorkersModule.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/WorkersModule.kt index 0c67611761a..2e7bb25b43b 100644 --- a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/WorkersModule.kt +++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/WorkersModule.kt @@ -9,7 +9,7 @@ import info.nightscout.androidaps.plugins.general.nsclient.NSClientMbgWorker import info.nightscout.androidaps.plugins.general.nsclient.NSClientRemoveWorker import info.nightscout.androidaps.plugins.general.nsclient.NSClientUpdateRemoveAckWorker import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin -import info.nightscout.androidaps.plugins.profile.ns.NSProfilePlugin +import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin import info.nightscout.androidaps.plugins.source.* @Module @@ -24,7 +24,7 @@ abstract class WorkersModule { @ContributesAndroidInjector abstract fun contributesTomatoWorker(): TomatoPlugin.TomatoWorker @ContributesAndroidInjector abstract fun contributesEversenseWorker(): EversensePlugin.EversenseWorker @ContributesAndroidInjector abstract fun contributesNSClientSourceWorker(): NSClientSourcePlugin.NSClientSourceWorker - @ContributesAndroidInjector abstract fun contributesNSProfileWorker(): NSProfilePlugin.NSProfileWorker + @ContributesAndroidInjector abstract fun contributesNSProfileWorker(): LocalProfilePlugin.NSProfileWorker @ContributesAndroidInjector abstract fun contributesSmsCommunicatorWorker(): SmsCommunicatorPlugin.SmsCommunicatorWorker @ContributesAndroidInjector abstract fun contributesNSClientWorker(): NSClientAddUpdateWorker @ContributesAndroidInjector abstract fun contributesNSClientAddAckWorker(): NSClientAddAckWorker 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 9d4e34a89b2..3bfa076da95 100644 --- a/app/src/main/java/info/nightscout/androidaps/dialogs/CarbsDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/CarbsDialog.kt @@ -17,7 +17,7 @@ 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.transactions.InsertTemporaryTargetAndCancelCurrentTransaction +import info.nightscout.androidaps.database.transactions.InsertAndCancelCurrentTemporaryTargetTransaction import info.nightscout.androidaps.databinding.DialogCarbsBinding import info.nightscout.androidaps.extensions.formatColor import info.nightscout.androidaps.interfaces.Constraint @@ -27,7 +27,6 @@ import info.nightscout.androidaps.interfaces.ProfileFunction import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker -import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin import info.nightscout.androidaps.queue.Callback import info.nightscout.androidaps.queue.CommandQueue import info.nightscout.androidaps.utils.* @@ -47,7 +46,6 @@ class CarbsDialog : DialogFragmentWithDate() { @Inject lateinit var resourceHelper: ResourceHelper @Inject lateinit var constraintChecker: ConstraintChecker @Inject lateinit var defaultValueHelper: DefaultValueHelper - @Inject lateinit var treatmentsPlugin: TreatmentsPlugin @Inject lateinit var profileFunction: ProfileFunction @Inject lateinit var iobCobCalculator: IobCobCalculator @Inject lateinit var uel: UserEntryLogger @@ -228,7 +226,7 @@ class CarbsDialog : DialogFragmentWithDate() { ValueWithUnit.TherapyEventTTReason(TemporaryTarget.Reason.ACTIVITY), ValueWithUnit.fromGlucoseUnit(activityTT, units.asText), ValueWithUnit.Minute(activityTTDuration)) - disposable += repository.runTransactionForResult(InsertTemporaryTargetAndCancelCurrentTransaction( + disposable += repository.runTransactionForResult(InsertAndCancelCurrentTemporaryTargetTransaction( timestamp = System.currentTimeMillis(), duration = TimeUnit.MINUTES.toMillis(activityTTDuration.toLong()), reason = TemporaryTarget.Reason.ACTIVITY, @@ -247,7 +245,7 @@ class CarbsDialog : DialogFragmentWithDate() { ValueWithUnit.TherapyEventTTReason(TemporaryTarget.Reason.EATING_SOON), ValueWithUnit.fromGlucoseUnit(eatingSoonTT, units.asText), ValueWithUnit.Minute(eatingSoonTTDuration)) - disposable += repository.runTransactionForResult(InsertTemporaryTargetAndCancelCurrentTransaction( + disposable += repository.runTransactionForResult(InsertAndCancelCurrentTemporaryTargetTransaction( timestamp = System.currentTimeMillis(), duration = TimeUnit.MINUTES.toMillis(eatingSoonTTDuration.toLong()), reason = TemporaryTarget.Reason.EATING_SOON, @@ -266,7 +264,7 @@ class CarbsDialog : DialogFragmentWithDate() { ValueWithUnit.TherapyEventTTReason(TemporaryTarget.Reason.HYPOGLYCEMIA), ValueWithUnit.fromGlucoseUnit(hypoTT, units.asText), ValueWithUnit.Minute(hypoTTDuration)) - disposable += repository.runTransactionForResult(InsertTemporaryTargetAndCancelCurrentTransaction( + disposable += repository.runTransactionForResult(InsertAndCancelCurrentTemporaryTargetTransaction( timestamp = System.currentTimeMillis(), duration = TimeUnit.MINUTES.toMillis(hypoTTDuration.toLong()), reason = TemporaryTarget.Reason.HYPOGLYCEMIA, 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 1d624d09d46..8d55d861a6d 100644 --- a/app/src/main/java/info/nightscout/androidaps/dialogs/InsulinDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/InsulinDialog.kt @@ -16,7 +16,7 @@ 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.transactions.InsertTemporaryTargetAndCancelCurrentTransaction +import info.nightscout.androidaps.database.transactions.InsertAndCancelCurrentTemporaryTargetTransaction import info.nightscout.androidaps.databinding.DialogInsulinBinding import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.logging.UserEntryLogger @@ -186,7 +186,7 @@ class InsulinDialog : DialogFragmentWithDate() { ValueWithUnit.TherapyEventTTReason(TemporaryTarget.Reason.EATING_SOON), ValueWithUnit.fromGlucoseUnit(eatingSoonTT, units.asText), ValueWithUnit.Minute(eatingSoonTTDuration)) - disposable += repository.runTransactionForResult(InsertTemporaryTargetAndCancelCurrentTransaction( + disposable += repository.runTransactionForResult(InsertAndCancelCurrentTemporaryTargetTransaction( timestamp = System.currentTimeMillis(), duration = TimeUnit.MINUTES.toMillis(eatingSoonTTDuration.toLong()), reason = TemporaryTarget.Reason.EATING_SOON, 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 c5dce440b9d..cfe1a210a3c 100644 --- a/app/src/main/java/info/nightscout/androidaps/dialogs/LoopDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/LoopDialog.kt @@ -12,9 +12,13 @@ import androidx.fragment.app.FragmentManager import dagger.android.support.DaggerDialogFragment import info.nightscout.androidaps.R import info.nightscout.androidaps.activities.ErrorHelperActivity +import info.nightscout.androidaps.database.AppRepository +import info.nightscout.androidaps.database.entities.OfflineEvent import info.nightscout.androidaps.database.entities.ValueWithUnit import info.nightscout.androidaps.database.entities.UserEntry.Action import info.nightscout.androidaps.database.entities.UserEntry.Sources +import info.nightscout.androidaps.database.transactions.CancelCurrentOfflineEventIfAnyTransaction +import info.nightscout.androidaps.database.transactions.InsertAndCancelCurrentOfflineEventTransaction import info.nightscout.androidaps.databinding.DialogLoopBinding import info.nightscout.androidaps.events.EventPreferenceChange import info.nightscout.androidaps.events.EventRefreshOverview @@ -29,8 +33,13 @@ import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.ToastUtils import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.extensions.toVisibility +import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.T import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.sharedPreferences.SP +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.rxkotlin.plusAssign import javax.inject.Inject class LoopDialog : DaggerDialogFragment() { @@ -48,6 +57,8 @@ class LoopDialog : DaggerDialogFragment() { @Inject lateinit var commandQueue: CommandQueueProvider @Inject lateinit var configBuilder: ConfigBuilder @Inject lateinit var uel: UserEntryLogger + @Inject lateinit var dateUtil: DateUtil + @Inject lateinit var repository: AppRepository private var showOkCancel: Boolean = true private var _binding: DialogLoopBinding? = null @@ -58,6 +69,8 @@ class LoopDialog : DaggerDialogFragment() { // onDestroyView. private val binding get() = _binding!! + val disposable = CompositeDisposable() + override fun onStart() { super.onStart() dialog?.window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) @@ -118,6 +131,7 @@ class LoopDialog : DaggerDialogFragment() { super.onDestroyView() _binding = null loopHandler.removeCallbacksAndMessages(null) + disposable.clear() } var task: Runnable? = null @@ -196,6 +210,7 @@ class LoopDialog : DaggerDialogFragment() { binding.overviewDisconnectButtons.visibility = View.GONE binding.overviewReconnect.visibility = View.VISIBLE } + binding.overviewLoop.visibility = (!loopPlugin.isSuspended && !loopPlugin.isDisconnected).toVisibility() } val profile = profileFunction.getProfile() val profileStore = activePlugin.activeProfileSource.profile @@ -211,22 +226,22 @@ class LoopDialog : DaggerDialogFragment() { private fun onClickOkCancelEnabled(v: View): Boolean { var description = "" when (v.id) { - R.id.overview_closeloop -> description = resourceHelper.gs(R.string.closedloop) - R.id.overview_lgsloop -> description = resourceHelper.gs(R.string.lowglucosesuspend) - R.id.overview_openloop -> description = resourceHelper.gs(R.string.openloop) - R.id.overview_disable -> description = resourceHelper.gs(R.string.disableloop) - R.id.overview_enable -> description = resourceHelper.gs(R.string.enableloop) - R.id.overview_resume -> description = resourceHelper.gs(R.string.resume) - R.id.overview_reconnect -> description = resourceHelper.gs(R.string.reconnect) - R.id.overview_suspend_1h -> description = resourceHelper.gs(R.string.suspendloopfor1h) - R.id.overview_suspend_2h -> description = resourceHelper.gs(R.string.suspendloopfor2h) - R.id.overview_suspend_3h -> description = resourceHelper.gs(R.string.suspendloopfor3h) - R.id.overview_suspend_10h -> description = resourceHelper.gs(R.string.suspendloopfor10h) + R.id.overview_closeloop -> description = resourceHelper.gs(R.string.closedloop) + R.id.overview_lgsloop -> description = resourceHelper.gs(R.string.lowglucosesuspend) + R.id.overview_openloop -> description = resourceHelper.gs(R.string.openloop) + R.id.overview_disable -> description = resourceHelper.gs(R.string.disableloop) + R.id.overview_enable -> description = resourceHelper.gs(R.string.enableloop) + R.id.overview_resume -> description = resourceHelper.gs(R.string.resume) + R.id.overview_reconnect -> description = resourceHelper.gs(R.string.reconnect) + R.id.overview_suspend_1h -> description = resourceHelper.gs(R.string.suspendloopfor1h) + R.id.overview_suspend_2h -> description = resourceHelper.gs(R.string.suspendloopfor2h) + R.id.overview_suspend_3h -> description = resourceHelper.gs(R.string.suspendloopfor3h) + R.id.overview_suspend_10h -> description = resourceHelper.gs(R.string.suspendloopfor10h) R.id.overview_disconnect_15m -> description = resourceHelper.gs(R.string.disconnectpumpfor15m) R.id.overview_disconnect_30m -> description = resourceHelper.gs(R.string.disconnectpumpfor30m) - R.id.overview_disconnect_1h -> description = resourceHelper.gs(R.string.disconnectpumpfor1h) - R.id.overview_disconnect_2h -> description = resourceHelper.gs(R.string.disconnectpumpfor2h) - R.id.overview_disconnect_3h -> description = resourceHelper.gs(R.string.disconnectpumpfor3h) + R.id.overview_disconnect_1h -> description = resourceHelper.gs(R.string.disconnectpumpfor1h) + R.id.overview_disconnect_2h -> description = resourceHelper.gs(R.string.disconnectpumpfor2h) + R.id.overview_disconnect_3h -> description = resourceHelper.gs(R.string.disconnectpumpfor3h) } activity?.let { activity -> OKDialog.showConfirmation(activity, resourceHelper.gs(R.string.confirm), description, Runnable { @@ -237,30 +252,29 @@ class LoopDialog : DaggerDialogFragment() { } fun onClick(v: View): Boolean { - val profile = profileFunction.getProfile() ?: return true when (v.id) { - R.id.overview_closeloop -> { + R.id.overview_closeloop -> { uel.log(Action.CLOSED_LOOP_MODE, Sources.LoopDialog) sp.putString(R.string.key_aps_mode, "closed") rxBus.send(EventPreferenceChange(resourceHelper.gs(R.string.closedloop))) return true } - R.id.overview_lgsloop -> { + R.id.overview_lgsloop -> { uel.log(Action.LGS_LOOP_MODE, Sources.LoopDialog) sp.putString(R.string.key_aps_mode, "lgs") rxBus.send(EventPreferenceChange(resourceHelper.gs(R.string.lowglucosesuspend))) return true } - R.id.overview_openloop -> { + R.id.overview_openloop -> { uel.log(Action.OPEN_LOOP_MODE, Sources.LoopDialog) sp.putString(R.string.key_aps_mode, "open") rxBus.send(EventPreferenceChange(resourceHelper.gs(R.string.lowglucosesuspend))) return true } - R.id.overview_disable -> { + R.id.overview_disable -> { uel.log(Action.LOOP_DISABLED, Sources.LoopDialog) loopPlugin.setPluginEnabled(PluginType.LOOP, false) loopPlugin.setFragmentVisible(PluginType.LOOP, false) @@ -273,23 +287,39 @@ class LoopDialog : DaggerDialogFragment() { } } }) - loopPlugin.createOfflineEvent(24 * 60) // upload 24h, we don't know real duration + disposable += repository.runTransactionForResult(InsertAndCancelCurrentOfflineEventTransaction(dateUtil.now(), T.days(365).msecs(), OfflineEvent.Reason.DISABLE_LOOP)) + .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) + }) return true } - R.id.overview_enable -> { + R.id.overview_enable -> { uel.log(Action.LOOP_ENABLED, Sources.LoopDialog) loopPlugin.setPluginEnabled(PluginType.LOOP, true) loopPlugin.setFragmentVisible(PluginType.LOOP, true) configBuilder.storeSettings("EnablingLoop") rxBus.send(EventRefreshOverview("suspendmenu")) - loopPlugin.createOfflineEvent(0) + disposable += repository.runTransactionForResult(CancelCurrentOfflineEventIfAnyTransaction(dateUtil.now())) + .subscribe({ result -> + result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated OfflineEvent $it") } + }, { + aapsLogger.error(LTag.DATABASE, "Error while saving OfflineEvent", it) + }) return true } R.id.overview_resume, R.id.overview_reconnect -> { - uel.log(if (v.id==R.id.overview_resume) Action.RESUME else Action.RECONNECT, Sources.LoopDialog) - loopPlugin.suspendTo(0L) + uel.log(if (v.id == R.id.overview_resume) Action.RESUME else Action.RECONNECT, Sources.LoopDialog) + disposable += repository.runTransactionForResult(CancelCurrentOfflineEventIfAnyTransaction(dateUtil.now())) + .subscribe({ result -> + result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated OfflineEvent $it") } + }, { + aapsLogger.error(LTag.DATABASE, "Error while saving OfflineEvent", it) + }) rxBus.send(EventRefreshOverview("suspendmenu")) commandQueue.cancelTempBasal(true, object : Callback() { override fun run() { @@ -299,70 +329,123 @@ class LoopDialog : DaggerDialogFragment() { } }) sp.putBoolean(R.string.key_objectiveusereconnect, true) - loopPlugin.createOfflineEvent(0) return true } - R.id.overview_suspend_1h -> { + R.id.overview_suspend_1h -> { uel.log(Action.SUSPEND, Sources.LoopDialog, ValueWithUnit.Hour(1)) - loopPlugin.suspendLoop(60) + disposable += repository.runTransactionForResult(InsertAndCancelCurrentOfflineEventTransaction(dateUtil.now(), T.hours(1).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) + }) rxBus.send(EventRefreshOverview("suspendmenu")) return true } - R.id.overview_suspend_2h -> { + R.id.overview_suspend_2h -> { uel.log(Action.SUSPEND, Sources.LoopDialog, ValueWithUnit.Hour(2)) - loopPlugin.suspendLoop(120) + disposable += repository.runTransactionForResult(InsertAndCancelCurrentOfflineEventTransaction(dateUtil.now(), T.hours(2).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) + }) rxBus.send(EventRefreshOverview("suspendmenu")) return true } - R.id.overview_suspend_3h -> { + R.id.overview_suspend_3h -> { uel.log(Action.SUSPEND, Sources.LoopDialog, ValueWithUnit.Hour(3)) - loopPlugin.suspendLoop(180) + disposable += repository.runTransactionForResult(InsertAndCancelCurrentOfflineEventTransaction(dateUtil.now(), T.hours(3).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) + }) rxBus.send(EventRefreshOverview("suspendmenu")) return true } - R.id.overview_suspend_10h -> { + R.id.overview_suspend_10h -> { uel.log(Action.SUSPEND, Sources.LoopDialog, ValueWithUnit.Hour(10)) - loopPlugin.suspendLoop(600) + disposable += repository.runTransactionForResult(InsertAndCancelCurrentOfflineEventTransaction(dateUtil.now(), T.hours(10).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) + }) rxBus.send(EventRefreshOverview("suspendmenu")) return true } - R.id.overview_disconnect_15m -> { + R.id.overview_disconnect_15m -> { uel.log(Action.DISCONNECT, Sources.LoopDialog, ValueWithUnit.Minute(15)) - loopPlugin.disconnectPump(15, profile) + disposable += repository.runTransactionForResult(InsertAndCancelCurrentOfflineEventTransaction(dateUtil.now(), T.mins(15).msecs(), OfflineEvent.Reason.DISCONNECT_PUMP)) + .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) + }) rxBus.send(EventRefreshOverview("suspendmenu")) return true } - R.id.overview_disconnect_30m -> { + R.id.overview_disconnect_30m -> { uel.log(Action.DISCONNECT, Sources.LoopDialog, ValueWithUnit.Minute(30)) - loopPlugin.disconnectPump(30, profile) + disposable += repository.runTransactionForResult(InsertAndCancelCurrentOfflineEventTransaction(dateUtil.now(), T.mins(30).msecs(), OfflineEvent.Reason.DISCONNECT_PUMP)) + .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) + }) rxBus.send(EventRefreshOverview("suspendmenu")) return true } - R.id.overview_disconnect_1h -> { + R.id.overview_disconnect_1h -> { uel.log(Action.DISCONNECT, Sources.LoopDialog, ValueWithUnit.Hour(1)) - loopPlugin.disconnectPump(60, profile) + disposable += repository.runTransactionForResult(InsertAndCancelCurrentOfflineEventTransaction(dateUtil.now(), T.mins(60).msecs(), OfflineEvent.Reason.DISCONNECT_PUMP)) + .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) + }) sp.putBoolean(R.string.key_objectiveusedisconnect, true) rxBus.send(EventRefreshOverview("suspendmenu")) return true } - R.id.overview_disconnect_2h -> { + R.id.overview_disconnect_2h -> { uel.log(Action.DISCONNECT, Sources.LoopDialog, ValueWithUnit.Hour(2)) - loopPlugin.disconnectPump(120, profile) + disposable += repository.runTransactionForResult(InsertAndCancelCurrentOfflineEventTransaction(dateUtil.now(), T.mins(120).msecs(), OfflineEvent.Reason.DISCONNECT_PUMP)) + .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) + }) rxBus.send(EventRefreshOverview("suspendmenu")) return true } - R.id.overview_disconnect_3h -> { + R.id.overview_disconnect_3h -> { uel.log(Action.DISCONNECT, Sources.LoopDialog, ValueWithUnit.Hour(3)) - loopPlugin.disconnectPump(180, profile) + disposable += repository.runTransactionForResult(InsertAndCancelCurrentOfflineEventTransaction(dateUtil.now(), T.mins(180).msecs(), OfflineEvent.Reason.DISCONNECT_PUMP)) + .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) + }) rxBus.send(EventRefreshOverview("suspendmenu")) return true } 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 699d3c43c1c..36c157df722 100644 --- a/app/src/main/java/info/nightscout/androidaps/dialogs/TempTargetDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/TempTargetDialog.kt @@ -17,7 +17,7 @@ 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.transactions.CancelCurrentTemporaryTargetIfAnyTransaction -import info.nightscout.androidaps.database.transactions.InsertTemporaryTargetAndCancelCurrentTransaction +import info.nightscout.androidaps.database.transactions.InsertAndCancelCurrentTemporaryTargetTransaction import info.nightscout.androidaps.databinding.DialogTemptargetBinding import info.nightscout.androidaps.interfaces.GlucoseUnit import info.nightscout.androidaps.interfaces.ProfileFunction @@ -196,7 +196,7 @@ class TempTargetDialog : DialogFragmentWithDate() { aapsLogger.error(LTag.DATABASE, "Error while saving temporary target", it) }) } else { - disposable += repository.runTransactionForResult(InsertTemporaryTargetAndCancelCurrentTransaction( + disposable += repository.runTransactionForResult(InsertAndCancelCurrentTemporaryTargetTransaction( timestamp = eventTime, duration = TimeUnit.MINUTES.toMillis(duration.toLong()), reason = when (reason) { diff --git a/app/src/main/java/info/nightscout/androidaps/historyBrowser/HistoryBrowseActivity.kt b/app/src/main/java/info/nightscout/androidaps/historyBrowser/HistoryBrowseActivity.kt deleted file mode 100644 index a32bf3089d0..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/historyBrowser/HistoryBrowseActivity.kt +++ /dev/null @@ -1,379 +0,0 @@ -package info.nightscout.androidaps.historyBrowser - -import android.app.DatePickerDialog -import android.graphics.Color -import android.os.Bundle -import android.util.DisplayMetrics -import android.view.ViewGroup -import android.widget.LinearLayout -import android.widget.RelativeLayout -import android.widget.TextView -import androidx.lifecycle.lifecycleScope -import com.jjoe64.graphview.GraphView -import dagger.android.HasAndroidInjector -import info.nightscout.androidaps.R -import info.nightscout.androidaps.activities.NoSplashAppCompatActivity -import info.nightscout.androidaps.databinding.ActivityHistorybrowseBinding -import info.nightscout.androidaps.events.EventAutosensCalculationFinished -import info.nightscout.androidaps.events.EventCustomCalculationFinished -import info.nightscout.androidaps.events.EventRefreshOverview -import info.nightscout.androidaps.extensions.toVisibility -import info.nightscout.androidaps.interfaces.ActivePlugin -import info.nightscout.androidaps.interfaces.ProfileFunction -import info.nightscout.androidaps.logging.AAPSLogger -import info.nightscout.androidaps.logging.LTag -import info.nightscout.androidaps.plugins.bus.RxBusWrapper -import info.nightscout.androidaps.plugins.general.overview.OverviewMenus -import info.nightscout.androidaps.plugins.general.overview.graphData.GraphData -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress -import info.nightscout.androidaps.utils.DateUtil -import info.nightscout.androidaps.utils.DefaultValueHelper -import info.nightscout.androidaps.utils.FabricPrivacy -import info.nightscout.androidaps.utils.T -import info.nightscout.androidaps.utils.buildHelper.BuildHelper -import info.nightscout.androidaps.utils.rx.AapsSchedulers -import info.nightscout.androidaps.utils.sharedPreferences.SP -import io.reactivex.disposables.CompositeDisposable -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import java.util.* -import javax.inject.Inject - -class HistoryBrowseActivity : NoSplashAppCompatActivity() { - - @Inject lateinit var injector: HasAndroidInjector - @Inject lateinit var aapsLogger: AAPSLogger - @Inject lateinit var aapsSchedulers: AapsSchedulers - @Inject lateinit var rxBus: RxBusWrapper - @Inject lateinit var sp: SP - @Inject lateinit var profileFunction: ProfileFunction - @Inject lateinit var defaultValueHelper: DefaultValueHelper - @Inject lateinit var iobCobCalculatorPluginHistory: IobCobCalculatorPluginHistory - @Inject lateinit var activePlugin: ActivePlugin - @Inject lateinit var buildHelper: BuildHelper - @Inject lateinit var fabricPrivacy: FabricPrivacy - @Inject lateinit var overviewMenus: OverviewMenus - @Inject lateinit var dateUtil: DateUtil - - private val disposable = CompositeDisposable() - - private val secondaryGraphs = ArrayList() - private val secondaryGraphsLabel = ArrayList() - - private var axisWidth: Int = 0 - private var rangeToDisplay = 24 // for graph - private var start: Long = 0 - - private val graphLock = Object() - - private var eventCustomCalculationFinished = EventCustomCalculationFinished() - - private lateinit var binding: ActivityHistorybrowseBinding - private var destroyed = false - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - binding = ActivityHistorybrowseBinding.inflate(layoutInflater) - setContentView(binding.root) - - binding.left.setOnClickListener { - start -= T.hours(rangeToDisplay.toLong()).msecs() - runCalculation("onClickLeft") - } - binding.right.setOnClickListener { - start += T.hours(rangeToDisplay.toLong()).msecs() - runCalculation("onClickRight") - } - binding.end.setOnClickListener { - val calendar = Calendar.getInstance() - calendar.timeInMillis = System.currentTimeMillis() - calendar[Calendar.MILLISECOND] = 0 - calendar[Calendar.SECOND] = 0 - calendar[Calendar.MINUTE] = 0 - calendar[Calendar.HOUR_OF_DAY] = 0 - start = calendar.timeInMillis - runCalculation("onClickEnd") - } - binding.zoom.setOnClickListener { - rangeToDisplay += 6 - rangeToDisplay = if (rangeToDisplay > 24) 6 else rangeToDisplay - updateGUI("rangeChange", false) - } - binding.zoom.setOnLongClickListener { - val calendar = Calendar.getInstance() - calendar.timeInMillis = start - calendar[Calendar.MILLISECOND] = 0 - calendar[Calendar.SECOND] = 0 - calendar[Calendar.MINUTE] = 0 - calendar[Calendar.HOUR_OF_DAY] = 0 - start = calendar.timeInMillis - runCalculation("onLongClickZoom") - true - } - - // create an OnDateSetListener - val dateSetListener = DatePickerDialog.OnDateSetListener { _, year, monthOfYear, dayOfMonth -> - val cal = Calendar.getInstance() - cal.timeInMillis = start - cal[Calendar.YEAR] = year - cal[Calendar.MONTH] = monthOfYear - cal[Calendar.DAY_OF_MONTH] = dayOfMonth - cal[Calendar.MILLISECOND] = 0 - cal[Calendar.SECOND] = 0 - cal[Calendar.MINUTE] = 0 - cal[Calendar.HOUR_OF_DAY] = 0 - start = cal.timeInMillis - binding.date.text = dateUtil.dateAndTimeString(start) - runCalculation("onClickDate") - } - - binding.date.setOnClickListener { - val cal = Calendar.getInstance() - cal.timeInMillis = start - DatePickerDialog(this, dateSetListener, - cal.get(Calendar.YEAR), - cal.get(Calendar.MONTH), - cal.get(Calendar.DAY_OF_MONTH) - ).show() - } - - val dm = DisplayMetrics() - 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 = resourceHelper.gc(R.color.graphgrid) - binding.bggraph.gridLabelRenderer?.reloadStyles() - binding.bggraph.gridLabelRenderer?.labelVerticalWidth = axisWidth - - overviewMenus.setupChartMenu(binding.chartMenuButton) - prepareGraphsIfNeeded(overviewMenus.setting.size) - savedInstanceState?.let { bundle -> - rangeToDisplay = bundle.getInt("rangeToDisplay", 0) - start = bundle.getLong("start", 0) - } - - } - - public override fun onPause() { - super.onPause() - disposable.clear() - iobCobCalculatorPluginHistory.stopCalculation("onPause") - } - - @Synchronized - override fun onDestroy() { - destroyed = true - super.onDestroy() - } - - public override fun onResume() { - super.onResume() - disposable.add(rxBus - .toObservable(EventAutosensCalculationFinished::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ - // catch only events from iobCobCalculatorPluginHistory - if (it.cause is EventCustomCalculationFinished) { - updateGUI("EventAutosensCalculationFinished", bgOnly = false) - } - }, fabricPrivacy::logException) - ) - disposable.add(rxBus - .toObservable(EventIobCalculationProgress::class.java) - .observeOn(aapsSchedulers.main) - .subscribe({ binding.overviewIobcalculationprogess.text = it.progress }, fabricPrivacy::logException) - ) - disposable.add(rxBus - .toObservable(EventRefreshOverview::class.java) - .observeOn(aapsSchedulers.main) - .subscribe({ - if (it.now) { - updateGUI("EventRefreshOverview", bgOnly = false) - } - }, fabricPrivacy::logException) - ) - if (start == 0L) { - // set start of current day - val calendar = Calendar.getInstance() - calendar.timeInMillis = System.currentTimeMillis() - calendar[Calendar.MILLISECOND] = 0 - calendar[Calendar.SECOND] = 0 - calendar[Calendar.MINUTE] = 0 - calendar[Calendar.HOUR_OF_DAY] = 0 - start = calendar.timeInMillis - runCalculation("onResume") - } else { - updateGUI("onResume", bgOnly = false) - } - } - - override fun onSaveInstanceState(outState: Bundle) { - super.onSaveInstanceState(outState) - outState.putInt("rangeToDisplay", rangeToDisplay) - outState.putLong("start", start) - - } - - private fun prepareGraphsIfNeeded(numOfGraphs: Int) { - synchronized(graphLock) { - if (numOfGraphs != secondaryGraphs.size - 1) { - //aapsLogger.debug("New secondary graph count ${numOfGraphs-1}") - // rebuild needed - secondaryGraphs.clear() - secondaryGraphsLabel.clear() - binding.iobGraph.removeAllViews() - for (i in 1 until numOfGraphs) { - val relativeLayout = RelativeLayout(this) - relativeLayout.layoutParams = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) - - val graph = GraphView(this) - graph.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, resourceHelper.dpToPx(100)).also { it.setMargins(0, resourceHelper.dpToPx(15), 0, resourceHelper.dpToPx(10)) } - graph.gridLabelRenderer?.gridColor = resourceHelper.gc(R.color.graphgrid) - graph.gridLabelRenderer?.reloadStyles() - graph.gridLabelRenderer?.isHorizontalLabelsVisible = false - graph.gridLabelRenderer?.labelVerticalWidth = axisWidth - graph.gridLabelRenderer?.numVerticalLabels = 3 - graph.viewport.backgroundColor = Color.argb(20, 255, 255, 255) // 8% of gray - relativeLayout.addView(graph) - - val label = TextView(this) - val layoutParams = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT).also { it.setMargins(resourceHelper.dpToPx(30), resourceHelper.dpToPx(25), 0, 0) } - layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP) - layoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT) - label.layoutParams = layoutParams - relativeLayout.addView(label) - secondaryGraphsLabel.add(label) - - binding.iobGraph.addView(relativeLayout) - secondaryGraphs.add(graph) - } - } - } - } - - private fun runCalculation(from: String) { - lifecycleScope.launch(Dispatchers.Default) { - val end = start + T.hours(rangeToDisplay.toLong()).msecs() - iobCobCalculatorPluginHistory.stopCalculation(from) - iobCobCalculatorPluginHistory.clearCache() - iobCobCalculatorPluginHistory.runCalculation(from, end, bgDataReload = true, limitDataToOldestAvailable = false, cause = eventCustomCalculationFinished) - } - } - - @Synchronized - fun updateGUI(from: String, bgOnly: Boolean) { - val menuChartSettings = overviewMenus.setting - prepareGraphsIfNeeded(menuChartSettings.size) - aapsLogger.debug(LTag.UI, "updateGUI from: $from") - val pump = activePlugin.activePump - val profile = profileFunction.getProfile() - - val lowLine = defaultValueHelper.determineLowLine() - val highLine = defaultValueHelper.determineHighLine() - - lifecycleScope.launch(Dispatchers.Main) { - binding.noprofile.visibility = (profile == null).toVisibility() - profile ?: return@launch - - if (destroyed) return@launch - binding.date.text = dateUtil.dateAndTimeString(start) - binding.zoom.text = rangeToDisplay.toString() - val graphData = GraphData(injector, binding.bggraph, iobCobCalculatorPluginHistory) - val secondaryGraphsData: ArrayList = ArrayList() - - // do preparation in different thread - withContext(Dispatchers.Default) { - val fromTime: Long = start + T.secs(100).msecs() - val toTime: Long = start + T.hours(rangeToDisplay.toLong()).msecs() + T.secs(100).msecs() - aapsLogger.debug(LTag.UI, "Period: " + dateUtil.dateAndTimeString(fromTime) + " - " + dateUtil.dateAndTimeString(toTime)) - val pointer = System.currentTimeMillis() - - // **** In range Area **** - graphData.addInRangeArea(fromTime, toTime, lowLine, highLine) - - // **** BG **** - graphData.addBgReadings(fromTime, toTime, highLine, null) - if (buildHelper.isDev()) graphData.addBucketedData(fromTime, toTime) - - // add target line - graphData.addTargetLine(fromTime, toTime, profile, null) - - // **** NOW line **** - graphData.addNowLine(pointer) - - if (!bgOnly) { - // Treatments - graphData.addTreatments(fromTime, toTime) - if (menuChartSettings[0][OverviewMenus.CharType.ACT.ordinal]) - graphData.addActivity(fromTime, toTime, false, 0.8) - - // add basal data - if (pump.pumpDescription.isTempBasalCapable && menuChartSettings[0][OverviewMenus.CharType.BAS.ordinal]) { - graphData.addBasals(fromTime, toTime, lowLine / graphData.maxY / 1.2) - } - // ------------------ 2nd graph - synchronized(graphLock) { - for (g in 0 until secondaryGraphs.size) { - val secondGraphData = GraphData(injector, secondaryGraphs[g], iobCobCalculatorPluginHistory) - var useIobForScale = false - var useCobForScale = false - var useDevForScale = false - var useRatioForScale = false - var useDSForScale = false - var useBGIForScale = false - var useABSForScale = false - when { - 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.SEN.ordinal] -> useRatioForScale = true - menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] -> useBGIForScale = true - menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] -> useABSForScale = true - menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] -> useDSForScale = true - } - - val alignIobScale = menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal] - val alignDevBgiScale = menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] - - if (menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal]) secondGraphData.addAbsIob(fromTime, toTime, useABSForScale, 1.0) - if (menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal]) secondGraphData.addIob(fromTime, toTime, useIobForScale, 1.0, menuChartSettings[g + 1][OverviewMenus.CharType.PRE.ordinal], alignIobScale) - if (menuChartSettings[g + 1][OverviewMenus.CharType.COB.ordinal]) secondGraphData.addCob(fromTime, toTime, useCobForScale, if (useCobForScale) 1.0 else 0.5) - if (menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal]) secondGraphData.addDeviations(fromTime, toTime, useDevForScale, 1.0, alignDevBgiScale) - if (menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal]) secondGraphData.addRatio(fromTime, toTime, useRatioForScale, 1.0) - if (menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal]) secondGraphData.addMinusBGI(fromTime, toTime, useBGIForScale, if (alignDevBgiScale) 1.0 else 0.8, alignDevBgiScale) - if (menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] && buildHelper.isDev()) secondGraphData.addDeviationSlope(fromTime, toTime, useDSForScale, 1.0) - - // set manual x bounds to have nice steps - secondGraphData.formatAxis(fromTime, toTime) - secondGraphData.addNowLine(pointer) - secondaryGraphsData.add(secondGraphData) - } - } - } - - // set manual x bounds to have nice steps - graphData.setNumVerticalLabels() - graphData.formatAxis(fromTime, toTime) - } - // finally enforce drawing of graphs in UI thread - graphData.performUpdate() - if (!bgOnly) - synchronized(graphLock) { - for (g in 0 until secondaryGraphs.size) { - secondaryGraphsLabel[g].text = overviewMenus.enabledTypes(g + 1) - secondaryGraphs[g].visibility = (!bgOnly && ( - menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal] || - menuChartSettings[g + 1][OverviewMenus.CharType.COB.ordinal] || - menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] || - menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal] || - menuChartSettings[g + 1][OverviewMenus.CharType.ACT.ordinal] || - menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] || - menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] - )).toVisibility() - secondaryGraphsData[g].performUpdate() - } - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/historyBrowser/IobCobCalculatorPluginHistory.kt b/app/src/main/java/info/nightscout/androidaps/historyBrowser/IobCobCalculatorPluginHistory.kt deleted file mode 100644 index 1dfc35f70a3..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/historyBrowser/IobCobCalculatorPluginHistory.kt +++ /dev/null @@ -1,42 +0,0 @@ -package info.nightscout.androidaps.historyBrowser - -import dagger.android.HasAndroidInjector -import info.nightscout.androidaps.database.AppRepository -import info.nightscout.androidaps.interfaces.ActivePlugin -import info.nightscout.androidaps.interfaces.ProfileFunction -import info.nightscout.androidaps.logging.AAPSLogger -import info.nightscout.androidaps.plugins.bus.RxBusWrapper -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin -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.FabricPrivacy -import info.nightscout.androidaps.utils.resources.ResourceHelper -import info.nightscout.androidaps.utils.rx.AapsSchedulers -import info.nightscout.androidaps.utils.sharedPreferences.SP -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class IobCobCalculatorPluginHistory @Inject constructor( - injector: HasAndroidInjector, - aapsLogger: AAPSLogger, - aapsSchedulers: AapsSchedulers, - rxBus: RxBusWrapper, - sp: SP, - resourceHelper: ResourceHelper, - profileFunction: ProfileFunction, - activePlugin: ActivePlugin, - sensitivityOref1Plugin: SensitivityOref1Plugin, - sensitivityAAPSPlugin: SensitivityAAPSPlugin, - sensitivityWeightedAveragePlugin: SensitivityWeightedAveragePlugin, - fabricPrivacy: FabricPrivacy, - dateUtil: DateUtil, - repository: AppRepository -) : IobCobCalculatorPlugin(injector, aapsLogger, aapsSchedulers, rxBus, sp, resourceHelper, profileFunction, - activePlugin, sensitivityOref1Plugin, sensitivityAAPSPlugin, sensitivityWeightedAveragePlugin, fabricPrivacy, dateUtil, repository) { - - override fun onStart() { // do not attach to rxbus - } -} \ 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 new file mode 100644 index 00000000000..e90e8cfb888 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/events/EventLoopInvoked.kt @@ -0,0 +1,5 @@ +package info.nightscout.androidaps.plugins.aps.events + +import info.nightscout.androidaps.events.Event + +class EventLoopInvoked : Event() \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopPlugin.kt index 0dcb282da6a..b2a13d8386f 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 @@ -16,18 +16,19 @@ 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.entities.TherapyEvent +import info.nightscout.androidaps.database.ValueWrapper +import info.nightscout.androidaps.database.entities.OfflineEvent 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.InsertIfNewByTimestampTherapyEventTransaction +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.interfaces.* -import info.nightscout.androidaps.interfaces.LoopInterface.LastRun +import info.nightscout.androidaps.interfaces.Loop.LastRun import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.logging.UserEntryLogger @@ -54,6 +55,7 @@ import info.nightscout.androidaps.extensions.buildDeviceStatus import info.nightscout.androidaps.extensions.convertedToAbsolute import info.nightscout.androidaps.extensions.convertedToPercent import info.nightscout.androidaps.extensions.plannedRemainingMinutes +import info.nightscout.androidaps.plugins.aps.events.EventLoopInvoked import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.rx.AapsSchedulers import info.nightscout.androidaps.utils.sharedPreferences.SP @@ -95,7 +97,7 @@ open class LoopPlugin @Inject constructor( .enableByDefault(config.APS) .description(R.string.description_loop), aapsLogger, resourceHelper, injector -), LoopInterface { +), Loop { private val disposable = CompositeDisposable() private var lastBgTriggeredRun: Long = 0 @@ -157,48 +159,19 @@ open class LoopPlugin @Inject constructor( } } - override fun suspendTo(endTime: Long) { - sp.putLong("loopSuspendedTill", endTime) - sp.putBoolean("isSuperBolus", false) - sp.putBoolean("isDisconnected", false) + override fun minutesToEndOfSuspend(): Int { + val offlineEventWrapped = repository.getOfflineEventActiveAt(dateUtil.now()).blockingGet() + return if (offlineEventWrapped is ValueWrapper.Existing) T.msecs(offlineEventWrapped.value.timestamp + offlineEventWrapped.value.duration - dateUtil.now()).mins().toInt() + else 0 } - fun superBolusTo(endTime: Long) { - sp.putLong("loopSuspendedTill", endTime) - sp.putBoolean("isSuperBolus", true) - sp.putBoolean("isDisconnected", false) - } - - private fun disconnectTo(endTime: Long) { - sp.putLong("loopSuspendedTill", endTime) - sp.putBoolean("isSuperBolus", false) - sp.putBoolean("isDisconnected", true) - } + override val isSuspended: Boolean + get() = repository.getOfflineEventActiveAt(dateUtil.now()).blockingGet() is ValueWrapper.Existing - fun minutesToEndOfSuspend(): Int { - val loopSuspendedTill = sp.getLong("loopSuspendedTill", 0L) - if (loopSuspendedTill == 0L) return 0 - val now = System.currentTimeMillis() - val millisDiff = loopSuspendedTill - now - if (loopSuspendedTill <= now) { // time exceeded - suspendTo(0L) - return 0 - } - return (millisDiff / 60.0 / 1000.0).toInt() - } + override var enabled: Boolean + get() = isEnabled() + set(value) { setPluginEnabled(PluginType.LOOP, value)} - // time exceeded - override val isSuspended: Boolean - get() { - val loopSuspendedTill = sp.getLong("loopSuspendedTill", 0L) - if (loopSuspendedTill == 0L) return false - val now = System.currentTimeMillis() - if (loopSuspendedTill <= now) { // time exceeded - suspendTo(0L) - return false - } - return true - } val isLGS: Boolean get() { val closedLoopEnabled = constraintChecker.isClosedLoopAllowed() @@ -210,30 +183,16 @@ open class LoopPlugin @Inject constructor( return isLGS } - // time exceeded val isSuperBolus: Boolean get() { - val loopSuspendedTill = sp.getLong("loopSuspendedTill", 0L) - if (loopSuspendedTill == 0L) return false - val now = System.currentTimeMillis() - if (loopSuspendedTill <= now) { // time exceeded - suspendTo(0L) - return false - } - return sp.getBoolean("isSuperBolus", false) + val offlineEventWrapped = repository.getOfflineEventActiveAt(dateUtil.now()).blockingGet() + return offlineEventWrapped is ValueWrapper.Existing && offlineEventWrapped.value.reason == OfflineEvent.Reason.SUPER_BOLUS } - // time exceeded val isDisconnected: Boolean get() { - val loopSuspendedTill = sp.getLong("loopSuspendedTill", 0L) - if (loopSuspendedTill == 0L) return false - val now = System.currentTimeMillis() - if (loopSuspendedTill <= now) { // time exceeded - suspendTo(0L) - return false - } - return sp.getBoolean("isDisconnected", false) + val offlineEventWrapped = repository.getOfflineEventActiveAt(dateUtil.now()).blockingGet() + return offlineEventWrapped is ValueWrapper.Existing && offlineEventWrapped.value.reason == OfflineEvent.Reason.DISCONNECT_PUMP } @Suppress("SameParameterValue") @@ -286,7 +245,7 @@ open class LoopPlugin @Inject constructor( if (apsResult == null) { rxBus.send(EventLoopSetLastRunGui(resourceHelper.gs(R.string.noapsselected))) return - } + } else rxBus.send(EventLoopInvoked()) // Prepare for pumps using % basals if (pump.pumpDescription.tempBasalStyle == PumpDescription.PERCENT && allowPercentage()) { @@ -403,7 +362,7 @@ open class LoopPlugin @Inject constructor( val waiting = PumpEnactResult(injector) waiting.queued = true if (resultAfterConstraints.tempBasalRequested) lastRun.tbrSetByPump = waiting - if (resultAfterConstraints.bolusRequested) lastRun.smbSetByPump = waiting + if (resultAfterConstraints.bolusRequested()) lastRun.smbSetByPump = waiting rxBus.send(EventLoopUpdateGui()) fabricPrivacy.logCustom("APSRequest") applyTBRRequest(resultAfterConstraints, profile, object : Callback() { @@ -600,7 +559,7 @@ open class LoopPlugin @Inject constructor( } private fun applySMBRequest(request: APSResult, callback: Callback?) { - if (!request.bolusRequested) { + if (!request.bolusRequested()) { return } val pump = activePlugin.activePump @@ -641,11 +600,17 @@ open class LoopPlugin @Inject constructor( return virtualPumpPlugin.isEnabled(PluginType.PUMP) } - fun disconnectPump(durationInMinutes: Int, profile: Profile?) { + override fun goToZeroTemp(durationInMinutes: Int, profile: Profile, reason: OfflineEvent.Reason) { val pump = activePlugin.activePump - disconnectTo(System.currentTimeMillis() + durationInMinutes * 60 * 1000L) + 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) + }) if (pump.pumpDescription.tempBasalStyle == PumpDescription.ABSOLUTE) { - commandQueue.tempBasalAbsolute(0.0, durationInMinutes, true, profile!!, PumpSync.TemporaryBasalType.EMULATED_PUMP_SUSPEND, object : Callback() { + commandQueue.tempBasalAbsolute(0.0, durationInMinutes, true, profile, PumpSync.TemporaryBasalType.EMULATED_PUMP_SUSPEND, object : Callback() { override fun run() { if (!result.success) { ErrorHelperActivity.runAlarm(context, result.comment, resourceHelper.gs(R.string.tempbasaldeliveryerror), info.nightscout.androidaps.dana.R.raw.boluserror) @@ -653,7 +618,7 @@ open class LoopPlugin @Inject constructor( } }) } else { - commandQueue.tempBasalPercent(0, durationInMinutes, true, profile!!, PumpSync.TemporaryBasalType.EMULATED_PUMP_SUSPEND, object : Callback() { + commandQueue.tempBasalPercent(0, durationInMinutes, true, profile, PumpSync.TemporaryBasalType.EMULATED_PUMP_SUSPEND, object : Callback() { override fun run() { if (!result.success) { ErrorHelperActivity.runAlarm(context, result.comment, resourceHelper.gs(R.string.tempbasaldeliveryerror), info.nightscout.androidaps.dana.R.raw.boluserror) @@ -670,11 +635,16 @@ open class LoopPlugin @Inject constructor( } }) } - createOfflineEvent(durationInMinutes) } override fun suspendLoop(durationInMinutes: Int) { - suspendTo(System.currentTimeMillis() + durationInMinutes * 60 * 1000) + 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) + }) commandQueue.cancelTempBasal(true, object : Callback() { override fun run() { if (!result.success) { @@ -682,20 +652,6 @@ open class LoopPlugin @Inject constructor( } } }) - createOfflineEvent(durationInMinutes) - } - - override fun createOfflineEvent(durationInMinutes: Int) { - disposable += repository.runTransactionForResult(InsertIfNewByTimestampTherapyEventTransaction( - timestamp = dateUtil.now(), - type = TherapyEvent.Type.APS_OFFLINE, - duration = T.mins(durationInMinutes.toLong()).msecs(), - enteredBy = "openaps://" + "AndroidAPS", - glucoseUnit = TherapyEvent.GlucoseUnit.MGDL - )).subscribe( - { result -> result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted therapy event $it") } }, - { aapsLogger.error(LTag.DATABASE, "Error while saving therapy event", it) } - ) } companion object { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/DetermineBasalResultAMA.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/DetermineBasalResultAMA.kt index 6f66dd235f0..5411bd2fa3b 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/DetermineBasalResultAMA.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/DetermineBasalResultAMA.kt @@ -40,7 +40,6 @@ class DetermineBasalResultAMA private constructor(injector: HasAndroidInjector) tempBasalRequested = false } } - bolusRequested = false } override fun newAndClone(injector: HasAndroidInjector): DetermineBasalResultAMA { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAFragment.kt index acbbf1191bb..1c4de779077 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAFragment.kt @@ -58,6 +58,7 @@ class OpenAPSAMAFragment : DaggerFragment() { } @Synchronized + @kotlin.ExperimentalStdlibApi override fun onResume() { super.onResume() @@ -90,6 +91,7 @@ class OpenAPSAMAFragment : DaggerFragment() { } @Synchronized + @kotlin.ExperimentalStdlibApi private fun updateGUI() { if (_binding == null) return openAPSAMAPlugin.lastAPSResult?.let { lastAPSResult -> 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 996621b8dbd..aa5dff4b38f 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 @@ -35,7 +35,6 @@ class DetermineBasalResultSMB private constructor(injector: HasAndroidInjector) duration = -1 } if (result.has("units")) { - bolusRequested = true smb = result.getDouble("units") } else { smb = 0.0 diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBFragment.kt index 61e52b08210..f5e03f602ff 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBFragment.kt @@ -59,6 +59,7 @@ class OpenAPSSMBFragment : DaggerFragment() { } @Synchronized + @kotlin.ExperimentalStdlibApi override fun onResume() { super.onResume() disposable += rxBus @@ -90,6 +91,7 @@ class OpenAPSSMBFragment : DaggerFragment() { } @Synchronized + @kotlin.ExperimentalStdlibApi fun updateGUI() { if (_binding == null) return openAPSSMBPlugin.lastAPSResult?.let { lastAPSResult -> 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 75946070ed1..0f6029064ba 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 @@ -184,4 +184,4 @@ class PluginStore @Inject constructor( override fun getPluginsList(): ArrayList = ArrayList(plugins) -} \ No newline at end of file +} diff --git a/core/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ProfileFunctionImplementation.kt b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ProfileFunctionImplementation.kt similarity index 88% rename from core/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ProfileFunctionImplementation.kt rename to app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ProfileFunctionImplementation.kt index b945124a5db..26a478240a6 100644 --- a/core/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ProfileFunctionImplementation.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ProfileFunctionImplementation.kt @@ -1,5 +1,6 @@ package info.nightscout.androidaps.plugins.configBuilder +import androidx.collection.LongSparseArray import info.nightscout.androidaps.Constants import info.nightscout.androidaps.core.R import info.nightscout.androidaps.data.ProfileSealed @@ -35,6 +36,8 @@ class ProfileFunctionImplementation @Inject constructor( private val dateUtil: DateUtil ) : ProfileFunction { + val cache = LongSparseArray() + private val disposable = CompositeDisposable() override fun getProfileName(): String = @@ -65,10 +68,20 @@ class ProfileFunctionImplementation @Inject constructor( getProfile(dateUtil.now()) override fun getProfile(time: Long): Profile? { - // aapsLogger.debug("XXXXXXXXXXXXXXX getProfile called for $time") + val rounded = time - time % 1000 + val cached = cache[rounded] + if (cached != null) { +// aapsLogger.debug("XXXXXXXXXXXXXXX HIT getProfile for $time $rounded") + return cached + } +// aapsLogger.debug("XXXXXXXXXXXXXXX getProfile called for $time") val ps = repository.getEffectiveProfileSwitchActiveAt(time).blockingGet() - return if (ps is ValueWrapper.Existing) ProfileSealed.EPS(ps.value) - else null + if (ps is ValueWrapper.Existing) { + val sealed = ProfileSealed.EPS(ps.value) + cache.put(rounded, sealed) + return sealed + } + return null } override fun getRequestedProfile(): ProfileSwitch? = repository.getActiveProfileSwitch(dateUtil.now()) @@ -91,7 +104,8 @@ class ProfileFunctionImplementation @Inject constructor( timeshift = T.hours(timeShiftInHours.toLong()).msecs(), percentage = percentage, duration = T.mins(durationInMinutes.toLong()).msecs(), - insulinConfiguration = activePlugin.activeInsulin.insulinConfiguration) + insulinConfiguration = activePlugin.activeInsulin.insulinConfiguration.also { it.insulinEndTime = (pureProfile.dia * 3600 * 1000).toLong() } + ) disposable += repository.runTransactionForResult(InsertOrUpdateProfileSwitch(ps)) .subscribe({ result -> result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted ProfileSwitch $it") } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesPlugin.kt index 0a7462b099c..2e1061ff7b7 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesPlugin.kt @@ -120,9 +120,10 @@ class ObjectivesPlugin @Inject constructor( sp.putBoolean(R.string.key_objectiveusescale, false) } + @kotlin.ExperimentalStdlibApi fun completeObjectives(activity: FragmentActivity, request: String) { val requestCode = sp.getString(R.string.key_objectives_request_code, "") - var url = sp.getString(R.string.key_nsclientinternal_url, "").toLowerCase(Locale.getDefault()) + var url = sp.getString(R.string.key_nsclientinternal_url, "").lowercase(Locale.getDefault()) if (!url.endsWith("/")) url = "$url/" @Suppress("DEPRECATION") val hashNS = Hashing.sha1().hashString(url + BuildConfig.APPLICATION_ID + "/" + requestCode, Charsets.UTF_8).toString() if (request.equals(hashNS.substring(0, 10), ignoreCase = true)) { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/objectives/Objective3.kt b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/objectives/Objective3.kt index da4fb503a15..5247e18facf 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/objectives/Objective3.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/objectives/Objective3.kt @@ -5,7 +5,6 @@ import dagger.android.HasAndroidInjector import info.nightscout.androidaps.R import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesPlugin import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin -import info.nightscout.androidaps.plugins.general.nsclient.services.NSClientService import info.nightscout.androidaps.utils.T import javax.inject.Inject @@ -28,8 +27,9 @@ class Objective3 @Inject constructor(injector: HasAndroidInjector) : Objective(i } override fun specialActionEnabled(): Boolean = - NSClientService.isConnected && NSClientService.hasWriteAuth + nsClientPlugin.nsClientService?.isConnected == true && nsClientPlugin.nsClientService?.hasWriteAuth == true + @kotlin.ExperimentalStdlibApi override fun specialAction(activity: FragmentActivity, input: String) { objectivesPlugin.completeObjectives(activity, input) } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/signatureVerifier/SignatureVerifierPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/signatureVerifier/SignatureVerifierPlugin.kt index dd7245601ce..7c41902a089 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/signatureVerifier/SignatureVerifierPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/signatureVerifier/SignatureVerifierPlugin.kt @@ -152,12 +152,13 @@ class SignatureVerifierPlugin @Inject constructor( return sb.toString() } + @kotlin.ExperimentalStdlibApi fun singleCharUnMap(shortHash: String): String { val array = ByteArray(shortHash.length) val sb = StringBuilder() for (i in array.indices) { if (i != 0) sb.append(":") - sb.append(String.format("%02X", 0xFF and map[map.indexOf(shortHash[i])].toInt())) + sb.append(String.format("%02X", 0xFF and map[map.indexOf(shortHash[i])].code)) } return sb.toString() } 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 3d0eb423cb5..8ffbd696eec 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/actions/ActionsFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/actions/ActionsFragment.kt @@ -11,7 +11,6 @@ import android.widget.LinearLayout import android.widget.TextView import androidx.core.content.ContextCompat import dagger.android.support.DaggerFragment -import info.nightscout.androidaps.interfaces.Config import info.nightscout.androidaps.Constants import info.nightscout.androidaps.R import info.nightscout.androidaps.activities.ErrorHelperActivity @@ -21,13 +20,18 @@ import info.nightscout.androidaps.database.ValueWrapper import info.nightscout.androidaps.database.entities.UserEntry.Action import info.nightscout.androidaps.database.entities.UserEntry.Sources import info.nightscout.androidaps.dialogs.* -import info.nightscout.androidaps.events.* +import info.nightscout.androidaps.events.EventCustomActionsChanged +import info.nightscout.androidaps.events.EventExtendedBolusChange +import info.nightscout.androidaps.events.EventInitializationChanged +import info.nightscout.androidaps.events.EventTempBasalChange +import info.nightscout.androidaps.events.EventTherapyEventChange import info.nightscout.androidaps.extensions.toStringMedium import info.nightscout.androidaps.extensions.toStringShort import info.nightscout.androidaps.extensions.toVisibility -import info.nightscout.androidaps.historyBrowser.HistoryBrowseActivity +import info.nightscout.androidaps.activities.HistoryBrowseActivity import info.nightscout.androidaps.interfaces.ActivePlugin import info.nightscout.androidaps.interfaces.CommandQueueProvider +import info.nightscout.androidaps.interfaces.Config import info.nightscout.androidaps.interfaces.IobCobCalculator import info.nightscout.androidaps.interfaces.ProfileFunction import info.nightscout.androidaps.logging.AAPSLogger @@ -229,10 +233,6 @@ class ActionsFragment : DaggerFragment() { .toObservable(EventInitializationChanged::class.java) .observeOn(aapsSchedulers.main) .subscribe({ updateGui() }, fabricPrivacy::logException) - disposable += rxBus - .toObservable(EventRefreshOverview::class.java) - .observeOn(aapsSchedulers.main) - .subscribe({ updateGui() }, fabricPrivacy::logException) disposable += rxBus .toObservable(EventExtendedBolusChange::class.java) .observeOn(aapsSchedulers.main) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/food/FoodFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/food/FoodFragment.kt index 4d8105a655c..364f64e1807 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 @@ -67,6 +67,7 @@ class FoodFragment : DaggerFragment() { return binding.root } + @kotlin.ExperimentalStdlibApi override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -128,6 +129,7 @@ class FoodFragment : DaggerFragment() { } @Synchronized + @kotlin.ExperimentalStdlibApi override fun onResume() { super.onResume() disposable.add(rxBus @@ -139,6 +141,7 @@ class FoodFragment : DaggerFragment() { swapAdapter() } + @kotlin.ExperimentalStdlibApi private fun swapAdapter() { disposable += repository .getFoodData() @@ -199,6 +202,7 @@ class FoodFragment : DaggerFragment() { } } + @kotlin.ExperimentalStdlibApi private fun filterData() { val textFilter = binding.filter.text.toString() val categoryFilter = binding.category.selectedItem?.toString() @@ -210,7 +214,7 @@ class FoodFragment : DaggerFragment() { if (f.category == null || f.subCategory == null) continue if (subcategoryFilter != resourceHelper.gs(R.string.none) && f.subCategory != subcategoryFilter) continue if (categoryFilter != resourceHelper.gs(R.string.none) && f.category != categoryFilter) continue - if (textFilter != "" && !f.name.toLowerCase(Locale.getDefault()).contains(textFilter.toLowerCase(Locale.getDefault()))) continue + if (textFilter != "" && !f.name.lowercase(Locale.getDefault()).contains(textFilter.lowercase(Locale.getDefault()))) continue newFiltered.add(f) } filtered = newFiltered 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 43378081062..36df7051476 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,13 +7,14 @@ import android.view.View import android.view.ViewGroup import dagger.android.support.DaggerFragment import info.nightscout.androidaps.R +import info.nightscout.androidaps.dana.database.DanaHistoryDatabase import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.entities.UserEntry.Action import info.nightscout.androidaps.database.entities.UserEntry.Sources import info.nightscout.androidaps.databinding.MaintenanceFragmentBinding import info.nightscout.androidaps.events.EventNewBG +import info.nightscout.androidaps.insight.database.InsightDatabase import info.nightscout.androidaps.interfaces.DataSyncSelector -import info.nightscout.androidaps.interfaces.DatabaseHelperInterface import info.nightscout.androidaps.interfaces.ImportExportPrefs import info.nightscout.androidaps.interfaces.PumpSync import info.nightscout.androidaps.logging.AAPSLogger @@ -38,7 +39,8 @@ class MaintenanceFragment : DaggerFragment() { @Inject lateinit var importExportPrefs: ImportExportPrefs @Inject lateinit var aapsSchedulers: AapsSchedulers @Inject lateinit var repository: AppRepository - @Inject lateinit var databaseHelper: DatabaseHelperInterface + @Inject lateinit var danaHistoryDatabase: DanaHistoryDatabase + @Inject lateinit var insightDatabase: InsightDatabase @Inject lateinit var uel: UserEntryLogger @Inject lateinit var dataSyncSelector: DataSyncSelector @Inject lateinit var pumpSync: PumpSync @@ -68,8 +70,9 @@ class MaintenanceFragment : DaggerFragment() { OKDialog.showConfirmation(activity, resourceHelper.gs(R.string.maintenance), resourceHelper.gs(R.string.reset_db_confirm), Runnable { compositeDisposable.add( fromAction { - databaseHelper.resetDatabases() repository.clearDatabases() + danaHistoryDatabase.clearAllTables() + insightDatabase.clearAllTables() dataSyncSelector.resetToNextFullSync() pumpSync.connectNewPump() } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/DataSyncSelectorImplementation.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/DataSyncSelectorImplementation.kt index 99b9febf03d..c5a41f057a2 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/DataSyncSelectorImplementation.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/DataSyncSelectorImplementation.kt @@ -2,14 +2,15 @@ package info.nightscout.androidaps.plugins.general.nsclient import info.nightscout.androidaps.R import info.nightscout.androidaps.database.AppRepository -import info.nightscout.androidaps.database.entities.DeviceStatus +import info.nightscout.androidaps.database.ValueWrapper import info.nightscout.androidaps.database.entities.* +import info.nightscout.androidaps.extensions.toJson import info.nightscout.androidaps.interfaces.ActivePlugin import info.nightscout.androidaps.interfaces.DataSyncSelector import info.nightscout.androidaps.interfaces.ProfileFunction import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.LTag -import info.nightscout.androidaps.extensions.toJson +import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.sharedPreferences.SP import javax.inject.Inject @@ -21,9 +22,28 @@ class DataSyncSelectorImplementation @Inject constructor( private val profileFunction: ProfileFunction, private val nsClientPlugin: NSClientPlugin, private val activePlugin: ActivePlugin, - private val appRepository: AppRepository + private val appRepository: AppRepository, + private val localProfilePlugin: LocalProfilePlugin ) : DataSyncSelector { + override fun doUpload() { + if (sp.getBoolean(R.string.key_ns_upload, true)) { + processChangedBolusesCompat() + processChangedCarbsCompat() + processChangedBolusCalculatorResultsCompat() + processChangedTemporaryBasalsCompat() + processChangedExtendedBolusesCompat() + processChangedProfileSwitchesCompat() + processChangedGlucoseValuesCompat() + processChangedTempTargetsCompat() + processChangedFoodsCompat() + processChangedTherapyEventsCompat() + processChangedDeviceStatusesCompat() + processChangedOfflineEventsCompat() + processChangedProfileStore() + } + } + override fun resetToNextFullSync() { sp.remove(R.string.key_ns_temporary_target_last_synced_id) sp.remove(R.string.key_ns_glucose_value_last_synced_id) @@ -36,6 +56,8 @@ class DataSyncSelectorImplementation @Inject constructor( sp.remove(R.string.key_ns_extended_bolus_last_synced_id) sp.remove(R.string.key_ns_therapy_event_last_synced_id) sp.remove(R.string.key_ns_profile_switch_last_synced_id) + sp.remove(R.string.key_ns_offline_event_last_synced_id) + sp.remove(R.string.key_ns_profile_store_last_synced_timestamp) } override fun confirmLastBolusIdIfGreater(lastSynced: Long) { @@ -56,22 +78,28 @@ class DataSyncSelectorImplementation @Inject constructor( } } + private var lastBolusId = -1L + private var lastBolusTime = -1L override fun processChangedBolusesCompat(): Boolean { - val startId = sp.getLong(R.string.key_ns_bolus_last_synced_id, 0) + val lastDbIdWrapped = appRepository.getLastBolusIdWrapped().blockingGet() + val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L + var startId = sp.getLong(R.string.key_ns_bolus_last_synced_id, 0) + if (startId > lastDbId) { + sp.putLong(R.string.key_ns_bolus_last_synced_id, 0) + startId = 0 + } + if (startId == lastBolusId && dateUtil.now() - lastBolusTime < 5000) return false + lastBolusId = startId + lastBolusTime = dateUtil.now() appRepository.getNextSyncElementBolus(startId).blockingGet()?.let { bolus -> aapsLogger.info(LTag.DATABASE, "Loading Bolus data Start: $startId ID: ${bolus.first.id} HistoryID: ${bolus.second} ") when { - // removed and not uploaded yet = ignore - !bolus.first.isValid && bolus.first.interfaceIDs.nightscoutId == null -> Any() - // removed and already uploaded = send for removal - !bolus.first.isValid && bolus.first.interfaceIDs.nightscoutId != null -> - nsClientPlugin.nsClientService?.dbRemove("treatments", bolus.first.interfaceIDs.nightscoutId, DataSyncSelector.PairBolus(bolus.first, bolus.second)) - // existing without nsId = create new - bolus.first.isValid && bolus.first.interfaceIDs.nightscoutId == null -> - nsClientPlugin.nsClientService?.dbAdd("treatments", bolus.first.toJson(dateUtil), DataSyncSelector.PairBolus(bolus.first, bolus.second)) - // existing with nsId = update - bolus.first.isValid && bolus.first.interfaceIDs.nightscoutId != null -> - nsClientPlugin.nsClientService?.dbUpdate("treatments", bolus.first.interfaceIDs.nightscoutId, bolus.first.toJson(dateUtil), DataSyncSelector.PairBolus(bolus.first, bolus.second)) + // without nsId = create new + bolus.first.interfaceIDs.nightscoutId == null -> + nsClientPlugin.nsClientService?.dbAdd("treatments", bolus.first.toJson(true, dateUtil), DataSyncSelector.PairBolus(bolus.first, bolus.second), "$startId/$lastDbId") + // with nsId = update + bolus.first.interfaceIDs.nightscoutId != null -> + nsClientPlugin.nsClientService?.dbUpdate("treatments", bolus.first.interfaceIDs.nightscoutId, bolus.first.toJson(false, dateUtil), DataSyncSelector.PairBolus(bolus.first, bolus.second), "$startId/$lastDbId") } return true } @@ -93,22 +121,28 @@ class DataSyncSelectorImplementation @Inject constructor( } } + private var lastCarbsId = -1L + private var lastCarbsTime = -1L override fun processChangedCarbsCompat(): Boolean { - val startId = sp.getLong(R.string.key_ns_carbs_last_synced_id, 0) + val lastDbIdWrapped = appRepository.getLastCarbsIdWrapped().blockingGet() + val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L + var startId = sp.getLong(R.string.key_ns_carbs_last_synced_id, 0) + if (startId > lastDbId) { + sp.putLong(R.string.key_ns_carbs_last_synced_id, 0) + startId = 0 + } + if (startId == lastCarbsId && dateUtil.now() - lastCarbsTime < 5000) return false + lastCarbsId = startId + lastCarbsTime = dateUtil.now() appRepository.getNextSyncElementCarbs(startId).blockingGet()?.let { carb -> aapsLogger.info(LTag.DATABASE, "Loading Carbs data Start: $startId ID: ${carb.first.id} HistoryID: ${carb.second} ") when { - // removed and not uploaded yet = ignore - !carb.first.isValid && carb.first.interfaceIDs.nightscoutId == null -> Any() - // removed and already uploaded = send for removal - !carb.first.isValid && carb.first.interfaceIDs.nightscoutId != null -> - nsClientPlugin.nsClientService?.dbRemove("treatments", carb.first.interfaceIDs.nightscoutId, DataSyncSelector.PairCarbs(carb.first, carb.second)) - // existing without nsId = create new - carb.first.isValid && carb.first.interfaceIDs.nightscoutId == null -> - nsClientPlugin.nsClientService?.dbAdd("treatments", carb.first.toJson(dateUtil), DataSyncSelector.PairCarbs(carb.first, carb.second)) - // existing with nsId = update - carb.first.isValid && carb.first.interfaceIDs.nightscoutId != null -> - nsClientPlugin.nsClientService?.dbUpdate("treatments", carb.first.interfaceIDs.nightscoutId, carb.first.toJson(dateUtil), DataSyncSelector.PairCarbs(carb.first, carb.second)) + // without nsId = create new + carb.first.interfaceIDs.nightscoutId == null -> + nsClientPlugin.nsClientService?.dbAdd("treatments", carb.first.toJson(true, dateUtil), DataSyncSelector.PairCarbs(carb.first, carb.second), "$startId/$lastDbId") + // with nsId = update + carb.first.interfaceIDs.nightscoutId != null -> + nsClientPlugin.nsClientService?.dbUpdate("treatments", carb.first.interfaceIDs.nightscoutId, carb.first.toJson(false, dateUtil), DataSyncSelector.PairCarbs(carb.first, carb.second), "$startId/$lastDbId") } return true } @@ -130,22 +164,28 @@ class DataSyncSelectorImplementation @Inject constructor( } } + private var lastBcrId = -1L + private var lastBcrTime = -1L override fun processChangedBolusCalculatorResultsCompat(): Boolean { - val startId = sp.getLong(R.string.key_ns_bolus_calculator_result_last_synced_id, 0) + val lastDbIdWrapped = appRepository.getLastBolusCalculatorResultIdWrapped().blockingGet() + val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L + var startId = sp.getLong(R.string.key_ns_bolus_calculator_result_last_synced_id, 0) + if (startId > lastDbId) { + sp.putLong(R.string.key_ns_bolus_calculator_result_last_synced_id, 0) + startId = 0 + } + if (startId == lastBcrId && dateUtil.now() - lastBcrTime < 5000) return false + lastBcrId = startId + lastBcrTime = dateUtil.now() appRepository.getNextSyncElementBolusCalculatorResult(startId).blockingGet()?.let { bolusCalculatorResult -> aapsLogger.info(LTag.DATABASE, "Loading BolusCalculatorResult data Start: $startId ID: ${bolusCalculatorResult.first.id} HistoryID: ${bolusCalculatorResult.second} ") when { - // removed and not uploaded yet = ignore - !bolusCalculatorResult.first.isValid && bolusCalculatorResult.first.interfaceIDs.nightscoutId == null -> Any() - // removed and already uploaded = send for removal - !bolusCalculatorResult.first.isValid && bolusCalculatorResult.first.interfaceIDs.nightscoutId != null -> - nsClientPlugin.nsClientService?.dbRemove("treatments", bolusCalculatorResult.first.interfaceIDs.nightscoutId, DataSyncSelector.PairBolusCalculatorResult(bolusCalculatorResult.first, bolusCalculatorResult.second)) - // existing without nsId = create new - bolusCalculatorResult.first.isValid && bolusCalculatorResult.first.interfaceIDs.nightscoutId == null -> - nsClientPlugin.nsClientService?.dbAdd("treatments", bolusCalculatorResult.first.toJson(dateUtil), DataSyncSelector.PairBolusCalculatorResult(bolusCalculatorResult.first, bolusCalculatorResult.second)) - // existing with nsId = update - bolusCalculatorResult.first.isValid && bolusCalculatorResult.first.interfaceIDs.nightscoutId != null -> - nsClientPlugin.nsClientService?.dbUpdate("treatments", bolusCalculatorResult.first.interfaceIDs.nightscoutId, bolusCalculatorResult.first.toJson(dateUtil), DataSyncSelector.PairBolusCalculatorResult(bolusCalculatorResult.first, bolusCalculatorResult.second)) + // without nsId = create new + bolusCalculatorResult.first.interfaceIDs.nightscoutId == null -> + nsClientPlugin.nsClientService?.dbAdd("treatments", bolusCalculatorResult.first.toJson(true, dateUtil), DataSyncSelector.PairBolusCalculatorResult(bolusCalculatorResult.first, bolusCalculatorResult.second), "$startId/$lastDbId") + // with nsId = update + bolusCalculatorResult.first.interfaceIDs.nightscoutId != null -> + nsClientPlugin.nsClientService?.dbUpdate("treatments", bolusCalculatorResult.first.interfaceIDs.nightscoutId, bolusCalculatorResult.first.toJson(false, dateUtil), DataSyncSelector.PairBolusCalculatorResult(bolusCalculatorResult.first, bolusCalculatorResult.second), "$startId/$lastDbId") } return true } @@ -167,22 +207,28 @@ class DataSyncSelectorImplementation @Inject constructor( } } + private var lastTtId = -1L + private var lastTtTime = -1L override fun processChangedTempTargetsCompat(): Boolean { - val startId = sp.getLong(R.string.key_ns_temporary_target_last_synced_id, 0) + val lastDbIdWrapped = appRepository.getLastTempTargetIdWrapped().blockingGet() + val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L + var startId = sp.getLong(R.string.key_ns_temporary_target_last_synced_id, 0) + if (startId > lastDbId) { + sp.putLong(R.string.key_ns_temporary_target_last_synced_id, 0) + startId = 0 + } + if (startId == lastTtId && dateUtil.now() - lastTtTime < 5000) return false + lastTtId = startId + lastTtTime = dateUtil.now() appRepository.getNextSyncElementTemporaryTarget(startId).blockingGet()?.let { tt -> aapsLogger.info(LTag.DATABASE, "Loading TemporaryTarget data Start: $startId ID: ${tt.first.id} HistoryID: ${tt.second} ") when { - // removed and not uploaded yet = ignore - !tt.first.isValid && tt.first.interfaceIDs.nightscoutId == null -> Any() - // removed and already uploaded = send for removal - !tt.first.isValid && tt.first.interfaceIDs.nightscoutId != null -> - nsClientPlugin.nsClientService?.dbRemove("treatments", tt.first.interfaceIDs.nightscoutId, DataSyncSelector.PairTemporaryTarget(tt.first, tt.second)) - // existing without nsId = create new - tt.first.isValid && tt.first.interfaceIDs.nightscoutId == null -> - nsClientPlugin.nsClientService?.dbAdd("treatments", tt.first.toJson(profileFunction.getUnits(), dateUtil), DataSyncSelector.PairTemporaryTarget(tt.first, tt.second)) + // without nsId = create new + tt.first.interfaceIDs.nightscoutId == null -> + nsClientPlugin.nsClientService?.dbAdd("treatments", tt.first.toJson(true, profileFunction.getUnits(), dateUtil), DataSyncSelector.PairTemporaryTarget(tt.first, tt.second), "$startId/$lastDbId") // existing with nsId = update - tt.first.isValid && tt.first.interfaceIDs.nightscoutId != null -> - nsClientPlugin.nsClientService?.dbUpdate("treatments", tt.first.interfaceIDs.nightscoutId, tt.first.toJson(profileFunction.getUnits(), dateUtil), DataSyncSelector.PairTemporaryTarget(tt.first, tt.second)) + tt.first.interfaceIDs.nightscoutId != null -> + nsClientPlugin.nsClientService?.dbUpdate("treatments", tt.first.interfaceIDs.nightscoutId, tt.first.toJson(false, profileFunction.getUnits(), dateUtil), DataSyncSelector.PairTemporaryTarget(tt.first, tt.second), "$startId/$lastDbId") } return true } @@ -204,22 +250,28 @@ class DataSyncSelectorImplementation @Inject constructor( } } + private var lastFoodId = -1L + private var lastFoodTime = -1L override fun processChangedFoodsCompat(): Boolean { - val startId = sp.getLong(R.string.key_ns_food_last_synced_id, 0) - appRepository.getNextSyncElementFood(startId).blockingGet()?.let { tt -> - aapsLogger.info(LTag.DATABASE, "Loading Food data Start: $startId ID: ${tt.first.id} HistoryID: ${tt.second} ") + val lastDbIdWrapped = appRepository.getLastFoodIdWrapped().blockingGet() + val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L + var startId = sp.getLong(R.string.key_ns_food_last_synced_id, 0) + if (startId > lastDbId) { + sp.putLong(R.string.key_ns_food_last_synced_id, 0) + startId = 0 + } + if (startId == lastFoodId && dateUtil.now() - lastFoodTime < 5000) return false + lastFoodId = startId + lastFoodTime = dateUtil.now() + appRepository.getNextSyncElementFood(startId).blockingGet()?.let { food -> + aapsLogger.info(LTag.DATABASE, "Loading Food data Start: $startId ID: ${food.first.id} HistoryID: ${food.second} ") when { - // removed and not uploaded yet = ignore - !tt.first.isValid && tt.first.interfaceIDs.nightscoutId == null -> Any() - // removed and already uploaded = send for removal - !tt.first.isValid && tt.first.interfaceIDs.nightscoutId != null -> - nsClientPlugin.nsClientService?.dbRemove("food", tt.first.interfaceIDs.nightscoutId, DataSyncSelector.PairFood(tt.first, tt.second)) - // existing without nsId = create new - tt.first.isValid && tt.first.interfaceIDs.nightscoutId == null -> - nsClientPlugin.nsClientService?.dbAdd("food", tt.first.toJson(), DataSyncSelector.PairFood(tt.first, tt.second)) - // existing with nsId = update - tt.first.isValid && tt.first.interfaceIDs.nightscoutId != null -> - nsClientPlugin.nsClientService?.dbUpdate("food", tt.first.interfaceIDs.nightscoutId, tt.first.toJson(), DataSyncSelector.PairFood(tt.first, tt.second)) + // without nsId = create new + food.first.interfaceIDs.nightscoutId == null -> + nsClientPlugin.nsClientService?.dbAdd("food", food.first.toJson(true), DataSyncSelector.PairFood(food.first, food.second), "$startId/$lastDbId") + // with nsId = update + food.first.interfaceIDs.nightscoutId != null -> + nsClientPlugin.nsClientService?.dbUpdate("food", food.first.interfaceIDs.nightscoutId, food.first.toJson(false), DataSyncSelector.PairFood(food.first, food.second), "$startId/$lastDbId") } return true } @@ -241,25 +293,35 @@ class DataSyncSelectorImplementation @Inject constructor( } } + private var lastGvId = -1L + private var lastGvTime = -1L override fun processChangedGlucoseValuesCompat(): Boolean { - val startId = sp.getLong(R.string.key_ns_glucose_value_last_synced_id, 0) + val lastDbIdWrapped = appRepository.getLastGlucoseValueIdWrapped().blockingGet() + val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L + var startId = sp.getLong(R.string.key_ns_glucose_value_last_synced_id, 0) + if (startId > lastDbId) { + sp.putLong(R.string.key_ns_glucose_value_last_synced_id, 0) + startId = 0 + } + if (startId == lastGvId && dateUtil.now() - lastGvTime < 5000) return false + lastGvId = startId + lastGvTime = dateUtil.now() appRepository.getNextSyncElementGlucoseValue(startId).blockingGet()?.let { gv -> aapsLogger.info(LTag.DATABASE, "Loading GlucoseValue data Start: $startId ID: ${gv.first.id} HistoryID: ${gv.second} ") if (activePlugin.activeBgSource.shouldUploadToNs(gv.first)) { when { - // removed and not uploaded yet = ignore - !gv.first.isValid && gv.first.interfaceIDs.nightscoutId == null -> Any() - // removed and already uploaded = send for removal - !gv.first.isValid && gv.first.interfaceIDs.nightscoutId != null -> - nsClientPlugin.nsClientService?.dbRemove("entries", gv.first.interfaceIDs.nightscoutId, DataSyncSelector.PairGlucoseValue(gv.first, gv.second)) - // existing without nsId = create new - gv.first.isValid && gv.first.interfaceIDs.nightscoutId == null -> - nsClientPlugin.nsClientService?.dbAdd("entries", gv.first.toJson(dateUtil), DataSyncSelector.PairGlucoseValue(gv.first, gv.second)) - // existing with nsId = update - gv.first.isValid && gv.first.interfaceIDs.nightscoutId != null -> - nsClientPlugin.nsClientService?.dbUpdate("entries", gv.first.interfaceIDs.nightscoutId, gv.first.toJson(dateUtil), DataSyncSelector.PairGlucoseValue(gv.first, gv.second)) + // without nsId = create new + gv.first.interfaceIDs.nightscoutId == null -> + nsClientPlugin.nsClientService?.dbAdd("entries", gv.first.toJson(true, dateUtil), DataSyncSelector.PairGlucoseValue(gv.first, gv.second), "$startId/$lastDbId") + // with nsId = update + gv.first.interfaceIDs.nightscoutId != null -> + nsClientPlugin.nsClientService?.dbUpdate("entries", gv.first.interfaceIDs.nightscoutId, gv.first.toJson(false, dateUtil), DataSyncSelector.PairGlucoseValue(gv.first, gv.second), "$startId/$lastDbId") } return true + } else { + confirmLastGlucoseValueIdIfGreater(gv.second) + lastGvId = -1 + processChangedGlucoseValuesCompat() } } return false @@ -280,22 +342,28 @@ class DataSyncSelectorImplementation @Inject constructor( } } + private var lastTeId = -1L + private var lastTeTime = -1L override fun processChangedTherapyEventsCompat(): Boolean { - val startId = sp.getLong(R.string.key_ns_therapy_event_last_synced_id, 0) - appRepository.getNextSyncElementTherapyEvent(startId).blockingGet()?.let { tt -> - aapsLogger.info(LTag.DATABASE, "Loading TherapyEvents data Start: $startId ID: ${tt.first.id} HistoryID: ${tt.second} ") + val lastDbIdWrapped = appRepository.getLastTherapyEventIdWrapped().blockingGet() + val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L + var startId = sp.getLong(R.string.key_ns_therapy_event_last_synced_id, 0) + if (startId > lastDbId) { + sp.putLong(R.string.key_ns_therapy_event_last_synced_id, 0) + startId = 0 + } + if (startId == lastTeId && dateUtil.now() - lastTeTime < 5000) return false + lastTeId = startId + lastTeTime = dateUtil.now() + appRepository.getNextSyncElementTherapyEvent(startId).blockingGet()?.let { te -> + aapsLogger.info(LTag.DATABASE, "Loading TherapyEvents data Start: $startId ID: ${te.first.id} HistoryID: ${te.second} ") when { - // removed and not uploaded yet = ignore - !tt.first.isValid && tt.first.interfaceIDs.nightscoutId == null -> Any() - // removed and already uploaded = send for removal - !tt.first.isValid && tt.first.interfaceIDs.nightscoutId != null -> - nsClientPlugin.nsClientService?.dbRemove("treatments", tt.first.interfaceIDs.nightscoutId, DataSyncSelector.PairTherapyEvent(tt.first, tt.second)) - // existing without nsId = create new - tt.first.isValid && tt.first.interfaceIDs.nightscoutId == null -> - nsClientPlugin.nsClientService?.dbAdd("treatments", tt.first.toJson(), DataSyncSelector.PairTherapyEvent(tt.first, tt.second)) - // existing with nsId = update - tt.first.isValid && tt.first.interfaceIDs.nightscoutId != null -> - nsClientPlugin.nsClientService?.dbUpdate("treatments", tt.first.interfaceIDs.nightscoutId, tt.first.toJson(), DataSyncSelector.PairTherapyEvent(tt.first, tt.second)) + // without nsId = create new + te.first.interfaceIDs.nightscoutId == null -> + nsClientPlugin.nsClientService?.dbAdd("treatments", te.first.toJson(true), DataSyncSelector.PairTherapyEvent(te.first, te.second), "$startId/$lastDbId") + // nsId = update + te.first.interfaceIDs.nightscoutId != null -> + nsClientPlugin.nsClientService?.dbUpdate("treatments", te.first.interfaceIDs.nightscoutId, te.first.toJson(false), DataSyncSelector.PairTherapyEvent(te.first, te.second), "$startId/$lastDbId") } return true } @@ -316,14 +384,25 @@ class DataSyncSelectorImplementation @Inject constructor( } } + private var lastDsId = -1L + private var lastDsTime = -1L override fun processChangedDeviceStatusesCompat(): Boolean { - val startId = sp.getLong(R.string.key_ns_device_status_last_synced_id, 0) + val lastDbIdWrapped = appRepository.getLastDeviceStatusIdWrapped().blockingGet() + val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L + var startId = sp.getLong(R.string.key_ns_device_status_last_synced_id, 0) + if (startId > lastDbId) { + sp.putLong(R.string.key_ns_device_status_last_synced_id, 0) + startId = 0 + } + if (startId == lastDsId && dateUtil.now() - lastDsTime < 5000) return false + lastDsId = startId + lastDsTime = dateUtil.now() appRepository.getNextSyncElementDeviceStatus(startId).blockingGet()?.let { deviceStatus -> aapsLogger.info(LTag.DATABASE, "Loading DeviceStatus data Start: $startId ID: ${deviceStatus.id}") when { // without nsId = create new deviceStatus.interfaceIDs.nightscoutId == null -> - nsClientPlugin.nsClientService?.dbAdd("devicestatus", deviceStatus.toJson(dateUtil), deviceStatus) + nsClientPlugin.nsClientService?.dbAdd("devicestatus", deviceStatus.toJson(dateUtil), deviceStatus, "$startId/$lastDbId") // with nsId = ignore deviceStatus.interfaceIDs.nightscoutId != null -> Any() } @@ -347,25 +426,37 @@ class DataSyncSelectorImplementation @Inject constructor( } } + private var lastTbrId = -1L + private var lastTbrTime = -1L override fun processChangedTemporaryBasalsCompat(): Boolean { - val startId = sp.getLong(R.string.key_ns_temporary_basal_last_synced_id, 0) + val useAbsolute = sp.getBoolean(R.string.key_ns_sync_use_absolute, false) + val lastDbIdWrapped = appRepository.getLastTemporaryBasalIdWrapped().blockingGet() + val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L + var startId = sp.getLong(R.string.key_ns_temporary_basal_last_synced_id, 0) + if (startId > lastDbId) { + sp.putLong(R.string.key_ns_temporary_basal_last_synced_id, 0) + startId = 0 + } + if (startId == lastTbrId && dateUtil.now() - lastTbrTime < 5000) return false + lastTbrId = startId + lastTbrTime = dateUtil.now() appRepository.getNextSyncElementTemporaryBasal(startId).blockingGet()?.let { tb -> aapsLogger.info(LTag.DATABASE, "Loading TemporaryBasal data Start: $startId ID: ${tb.first.id} HistoryID: ${tb.second} ") - profileFunction.getProfile(tb.first.timestamp)?.let { profile -> + val profile = profileFunction.getProfile(tb.first.timestamp) + if (profile != null) { when { - // removed and not uploaded yet = ignore - !tb.first.isValid && tb.first.interfaceIDs.nightscoutId == null -> Any() - // removed and already uploaded = send for removal - !tb.first.isValid && tb.first.interfaceIDs.nightscoutId != null -> - nsClientPlugin.nsClientService?.dbRemove("treatments", tb.first.interfaceIDs.nightscoutId, DataSyncSelector.PairTemporaryBasal(tb.first, tb.second)) - // existing without nsId = create new - tb.first.isValid && tb.first.interfaceIDs.nightscoutId == null -> - nsClientPlugin.nsClientService?.dbAdd("treatments", tb.first.toJson(profile, dateUtil), DataSyncSelector.PairTemporaryBasal(tb.first, tb.second)) - // existing with nsId = update - tb.first.isValid && tb.first.interfaceIDs.nightscoutId != null -> - nsClientPlugin.nsClientService?.dbUpdate("treatments", tb.first.interfaceIDs.nightscoutId, tb.first.toJson(profile, dateUtil), DataSyncSelector.PairTemporaryBasal(tb.first, tb.second)) + // without nsId = create new + tb.first.interfaceIDs.nightscoutId == null -> + nsClientPlugin.nsClientService?.dbAdd("treatments", tb.first.toJson(true, profile, dateUtil, useAbsolute), DataSyncSelector.PairTemporaryBasal(tb.first, tb.second), "$startId/$lastDbId") + // with nsId = update + tb.first.interfaceIDs.nightscoutId != null -> + nsClientPlugin.nsClientService?.dbUpdate("treatments", tb.first.interfaceIDs.nightscoutId, tb.first.toJson(false, profile, dateUtil, useAbsolute), DataSyncSelector.PairTemporaryBasal(tb.first, tb.second), "$startId/$lastDbId") } return true + } else { + confirmLastTemporaryBasalIdIfGreater(tb.second) + lastTbrId = -1 + processChangedTemporaryBasalsCompat() } } return false @@ -386,25 +477,37 @@ class DataSyncSelectorImplementation @Inject constructor( } } + private var lastEbId = -1L + private var lastEbTime = -1L override fun processChangedExtendedBolusesCompat(): Boolean { - val startId = sp.getLong(R.string.key_ns_extended_bolus_last_synced_id, 0) + val useAbsolute = sp.getBoolean(R.string.key_ns_sync_use_absolute, false) + val lastDbIdWrapped = appRepository.getLastExtendedBolusIdWrapped().blockingGet() + val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L + var startId = sp.getLong(R.string.key_ns_extended_bolus_last_synced_id, 0) + if (startId > lastDbId) { + sp.putLong(R.string.key_ns_extended_bolus_last_synced_id, 0) + startId = 0 + } + if (startId == lastEbId && dateUtil.now() - lastEbTime < 5000) return false + lastEbId = startId + lastEbTime = dateUtil.now() appRepository.getNextSyncElementExtendedBolus(startId).blockingGet()?.let { eb -> aapsLogger.info(LTag.DATABASE, "Loading ExtendedBolus data Start: $startId ID: ${eb.first.id} HistoryID: ${eb.second} ") - profileFunction.getProfile(eb.first.timestamp)?.let { profile -> + val profile = profileFunction.getProfile(eb.first.timestamp) + if (profile != null) { when { - // removed and not uploaded yet = ignore - !eb.first.isValid && eb.first.interfaceIDs.nightscoutId == null -> Any() - // removed and already uploaded = send for removal - !eb.first.isValid && eb.first.interfaceIDs.nightscoutId != null -> - nsClientPlugin.nsClientService?.dbRemove("treatments", eb.first.interfaceIDs.nightscoutId, DataSyncSelector.PairExtendedBolus(eb.first, eb.second)) - // existing without nsId = create new - eb.first.isValid && eb.first.interfaceIDs.nightscoutId == null -> - nsClientPlugin.nsClientService?.dbAdd("treatments", eb.first.toJson(profile, dateUtil), DataSyncSelector.PairExtendedBolus(eb.first, eb.second)) - // existing with nsId = update - eb.first.isValid && eb.first.interfaceIDs.nightscoutId != null -> - nsClientPlugin.nsClientService?.dbUpdate("treatments", eb.first.interfaceIDs.nightscoutId, eb.first.toJson(profile, dateUtil), DataSyncSelector.PairExtendedBolus(eb.first, eb.second)) + // without nsId = create new + eb.first.interfaceIDs.nightscoutId == null -> + nsClientPlugin.nsClientService?.dbAdd("treatments", eb.first.toJson(true, profile, dateUtil, useAbsolute), DataSyncSelector.PairExtendedBolus(eb.first, eb.second), "$startId/$lastDbId") + // with nsId = update + eb.first.interfaceIDs.nightscoutId != null -> + nsClientPlugin.nsClientService?.dbUpdate("treatments", eb.first.interfaceIDs.nightscoutId, eb.first.toJson(false, profile, dateUtil, useAbsolute), DataSyncSelector.PairExtendedBolus(eb.first, eb.second), "$startId/$lastDbId") } return true + } else { + confirmLastExtendedBolusIdIfGreater(eb.second) + lastEbId = -1 + processChangedExtendedBolusesCompat() } } return false @@ -424,25 +527,88 @@ class DataSyncSelectorImplementation @Inject constructor( } } + private var lastPsId = -1L + private var lastPsTime = -1L override fun processChangedProfileSwitchesCompat(): Boolean { - val startId = sp.getLong(R.string.key_ns_profile_switch_last_synced_id, 0) - appRepository.getNextSyncElementProfileSwitch(startId).blockingGet()?.let { eb -> - aapsLogger.info(LTag.DATABASE, "Loading ProfileSwitch data Start: $startId ID: ${eb.first.id} HistoryID: ${eb.second} ") + val lastDbIdWrapped = appRepository.getLastProfileSwitchIdWrapped().blockingGet() + val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L + var startId = sp.getLong(R.string.key_ns_profile_switch_last_synced_id, 0) + if (startId > lastDbId) { + sp.putLong(R.string.key_ns_profile_switch_last_synced_id, 0) + startId = 0 + } + if (startId == lastPsId && dateUtil.now() - lastPsTime < 5000) return false + lastPsId = startId + lastPsTime = dateUtil.now() + appRepository.getNextSyncElementProfileSwitch(startId).blockingGet()?.let { ps -> + aapsLogger.info(LTag.DATABASE, "Loading ProfileSwitch data Start: $startId ID: ${ps.first.id} HistoryID: ${ps.second} ") + when { + // without nsId = create new + ps.first.interfaceIDs.nightscoutId == null -> + nsClientPlugin.nsClientService?.dbAdd("treatments", ps.first.toJson(true, dateUtil), DataSyncSelector.PairProfileSwitch(ps.first, ps.second), "$startId/$lastDbId") + // with nsId = update + ps.first.interfaceIDs.nightscoutId != null -> + nsClientPlugin.nsClientService?.dbUpdate("treatments", ps.first.interfaceIDs.nightscoutId, ps.first.toJson(false, dateUtil), DataSyncSelector.PairProfileSwitch(ps.first, ps.second), "$startId/$lastDbId") + } + return true + } + return false + } + + override fun confirmLastOfflineEventIdIfGreater(lastSynced: Long) { + if (lastSynced > sp.getLong(R.string.key_ns_offline_event_last_synced_id, 0)) { + aapsLogger.debug(LTag.NSCLIENT, "Setting OfflineEvent data sync from $lastSynced") + sp.putLong(R.string.key_ns_offline_event_last_synced_id, lastSynced) + } + } + + // Prepared for v3 (returns all modified after) + override fun changedOfflineEvents(): List { + val startId = sp.getLong(R.string.key_ns_offline_event_last_synced_id, 0) + return appRepository.getModifiedOfflineEventsDataFromId(startId).blockingGet().also { + aapsLogger.debug(LTag.NSCLIENT, "Loading OfflineEvent data for sync from $startId. Records ${it.size}") + } + } + + private var lastOeId = -1L + private var lastOeTime = -1L + override fun processChangedOfflineEventsCompat(): Boolean { + val lastDbIdWrapped = appRepository.getLastOfflineEventIdWrapped().blockingGet() + val lastDbId = if (lastDbIdWrapped is ValueWrapper.Existing) lastDbIdWrapped.value else 0L + var startId = sp.getLong(R.string.key_ns_offline_event_last_synced_id, 0) + if (startId > lastDbId) { + sp.putLong(R.string.key_ns_offline_event_last_synced_id, 0) + startId = 0 + } + if (startId == lastOeId && dateUtil.now() - lastOeTime < 5000) return false + lastOeId = startId + lastOeTime = dateUtil.now() + appRepository.getNextSyncElementOfflineEvent(startId).blockingGet()?.let { oe -> + aapsLogger.info(LTag.DATABASE, "Loading OfflineEvent data Start: $startId ID: ${oe.first.id} HistoryID: ${oe.second} ") when { - // removed and not uploaded yet = ignore - !eb.first.isValid && eb.first.interfaceIDs.nightscoutId == null -> Any() - // removed and already uploaded = send for removal - !eb.first.isValid && eb.first.interfaceIDs.nightscoutId != null -> - nsClientPlugin.nsClientService?.dbRemove("treatments", eb.first.interfaceIDs.nightscoutId, DataSyncSelector.PairProfileSwitch(eb.first, eb.second)) - // existing without nsId = create new - eb.first.isValid && eb.first.interfaceIDs.nightscoutId == null -> - nsClientPlugin.nsClientService?.dbAdd("treatments", eb.first.toJson(dateUtil), DataSyncSelector.PairProfileSwitch(eb.first, eb.second)) + // without nsId = create new + oe.first.interfaceIDs.nightscoutId == null -> + nsClientPlugin.nsClientService?.dbAdd("treatments", oe.first.toJson(true, dateUtil), DataSyncSelector.PairOfflineEvent(oe.first, oe.second), "$startId/$lastDbId") // existing with nsId = update - eb.first.isValid && eb.first.interfaceIDs.nightscoutId != null -> - nsClientPlugin.nsClientService?.dbUpdate("treatments", eb.first.interfaceIDs.nightscoutId, eb.first.toJson(dateUtil), DataSyncSelector.PairProfileSwitch(eb.first, eb.second)) + oe.first.interfaceIDs.nightscoutId != null -> + nsClientPlugin.nsClientService?.dbUpdate("treatments", oe.first.interfaceIDs.nightscoutId, oe.first.toJson(false, dateUtil), DataSyncSelector.PairOfflineEvent(oe.first, oe.second), "$startId/$lastDbId") } return true } return false } + + override fun confirmLastProfileStore(lastSynced: Long) { + sp.putLong(R.string.key_ns_profile_store_last_synced_timestamp, lastSynced) + } + + override fun processChangedProfileStore() { + val lastSync = sp.getLong(R.string.key_ns_profile_store_last_synced_timestamp, 0) + val lastChange = sp.getLong(R.string.key_local_profile_last_change, 0) + if (lastChange == 0L) return + if (lastChange > lastSync) { + val profileJson = localProfilePlugin.profile?.data ?: return + nsClientPlugin.nsClientService?.dbAdd("profile", profileJson, DataSyncSelector.PairProfileStore(profileJson, dateUtil.now()), "") + } + } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientAddAckWorker.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientAddAckWorker.kt index 39c20b00e37..1df63090370 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientAddAckWorker.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientAddAckWorker.kt @@ -147,7 +147,7 @@ class NSClientAddAckWorker( dataSyncSelector.confirmLastCarbsIdIfGreater(pair.updateRecordId) } .blockingGet() - rxBus.send(EventNSClientNewLog("DBADD", "Acked Carbs" + pair.value.interfaceIDs.nightscoutId)) + rxBus.send(EventNSClientNewLog("DBADD", "Acked Carbs " + pair.value.interfaceIDs.nightscoutId)) // Send new if waiting dataSyncSelector.processChangedCarbsCompat() } @@ -166,7 +166,7 @@ class NSClientAddAckWorker( dataSyncSelector.confirmLastBolusCalculatorResultsIdIfGreater(pair.updateRecordId) } .blockingGet() - rxBus.send(EventNSClientNewLog("DBADD", "Acked BolusCalculatorResult" + pair.value.interfaceIDs.nightscoutId)) + rxBus.send(EventNSClientNewLog("DBADD", "Acked BolusCalculatorResult " + pair.value.interfaceIDs.nightscoutId)) // Send new if waiting dataSyncSelector.processChangedBolusCalculatorResultsCompat() } @@ -185,7 +185,7 @@ class NSClientAddAckWorker( dataSyncSelector.confirmLastTemporaryBasalIdIfGreater(pair.updateRecordId) } .blockingGet() - rxBus.send(EventNSClientNewLog("DBADD", "Acked TemporaryBasal" + pair.value.interfaceIDs.nightscoutId)) + rxBus.send(EventNSClientNewLog("DBADD", "Acked TemporaryBasal " + pair.value.interfaceIDs.nightscoutId)) // Send new if waiting dataSyncSelector.processChangedTemporaryBasalsCompat() } @@ -204,9 +204,9 @@ class NSClientAddAckWorker( dataSyncSelector.confirmLastExtendedBolusIdIfGreater(pair.updateRecordId) } .blockingGet() - rxBus.send(EventNSClientNewLog("DBADD", "Acked ExtendedBolus" + pair.value.interfaceIDs.nightscoutId)) + rxBus.send(EventNSClientNewLog("DBADD", "Acked ExtendedBolus " + pair.value.interfaceIDs.nightscoutId)) // Send new if waiting - dataSyncSelector.processChangedTemporaryBasalsCompat() + dataSyncSelector.processChangedExtendedBolusesCompat() } is PairProfileSwitch -> { @@ -223,9 +223,9 @@ class NSClientAddAckWorker( dataSyncSelector.confirmLastProfileSwitchIdIfGreater(pair.updateRecordId) } .blockingGet() - rxBus.send(EventNSClientNewLog("DBADD", "Acked ProfileSwitch" + pair.value.interfaceIDs.nightscoutId)) + rxBus.send(EventNSClientNewLog("DBADD", "Acked ProfileSwitch " + pair.value.interfaceIDs.nightscoutId)) // Send new if waiting - dataSyncSelector.processChangedTemporaryBasalsCompat() + dataSyncSelector.processChangedProfileSwitchesCompat() } is DeviceStatus -> { @@ -242,10 +242,35 @@ class NSClientAddAckWorker( dataSyncSelector.confirmLastDeviceStatusIdIfGreater(deviceStatus.id) } .blockingGet() - rxBus.send(EventNSClientNewLog("DBADD", "Acked DeviceStatus" + deviceStatus.interfaceIDs.nightscoutId)) + rxBus.send(EventNSClientNewLog("DBADD", "Acked DeviceStatus " + deviceStatus.interfaceIDs.nightscoutId)) // Send new if waiting dataSyncSelector.processChangedDeviceStatusesCompat() } + + is PairProfileStore -> { + dataSyncSelector.confirmLastProfileStore(ack.originalObject.timestampSync) + rxBus.send(EventNSClientNewLog("DBADD", "Acked ProfileStore " + ack.id)) + } + + is PairOfflineEvent -> { + val pair = ack.originalObject + pair.value.interfaceIDs.nightscoutId = ack.id + repository.runTransactionForResult(UpdateNsIdOfflineEventTransaction(pair.value)) + .doOnError { error -> + aapsLogger.error(LTag.DATABASE, "Updated ns id of OfflineEvent failed", error) + ret = Result.failure((workDataOf("Error" to error.toString()))) + } + .doOnSuccess { + ret = Result.success(workDataOf("ProcessedData" to pair.toString())) + aapsLogger.debug(LTag.DATABASE, "Updated ns id of OfflineEvent " + pair.value) + dataSyncSelector.confirmLastOfflineEventIdIfGreater(pair.updateRecordId) + } + .blockingGet() + rxBus.send(EventNSClientNewLog("DBADD", "Acked OfflineEvent " + pair.value.interfaceIDs.nightscoutId)) + // Send new if waiting + dataSyncSelector.processChangedOfflineEventsCompat() + } + } return ret } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientAddUpdateWorker.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientAddUpdateWorker.kt index 83b19b2a16f..8bb8413c6d3 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientAddUpdateWorker.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientAddUpdateWorker.kt @@ -14,6 +14,7 @@ import info.nightscout.androidaps.database.entities.UserEntry.Sources import info.nightscout.androidaps.database.entities.ValueWithUnit import info.nightscout.androidaps.database.transactions.* import info.nightscout.androidaps.extensions.* +import info.nightscout.androidaps.interfaces.ActivePlugin import info.nightscout.androidaps.interfaces.Config import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.LTag @@ -43,13 +44,11 @@ class NSClientAddUpdateWorker( @Inject lateinit var dateUtil: DateUtil @Inject lateinit var config: Config @Inject lateinit var repository: AppRepository + @Inject lateinit var activePlugin: ActivePlugin @Inject lateinit var rxBus: RxBusWrapper @Inject lateinit var uel: UserEntryLogger override fun doWork(): Result { - val acceptNSData = !sp.getBoolean(R.string.key_ns_upload_only, true) && buildHelper.isEngineeringMode() || config.NSCLIENT - if (!acceptNSData) return Result.success() - val treatments = dataWorker.pickupJSONArray(inputData.getLong(DataWorker.STORE_KEY, -1)) ?: return Result.failure(workDataOf("Error" to "missing input data")) @@ -73,275 +72,326 @@ class NSClientAddUpdateWorker( if (mills > latestDateInReceivedData) latestDateInReceivedData = mills if (insulin > 0) { - bolusFromJson(json)?.let { bolus -> - repository.runTransactionForResult(SyncNsBolusTransaction(bolus, invalidateByNsOnly = false)) - .doOnError { - aapsLogger.error(LTag.DATABASE, "Error while saving bolus", it) - ret = Result.failure(workDataOf("Error" to it.toString())) - } - .blockingGet() - .also { result -> - result.inserted.forEach { - uel.log(Action.BOLUS, Sources.NSClient, - ValueWithUnit.Timestamp(it.timestamp), - ValueWithUnit.Insulin(it.amount) - ) - aapsLogger.debug(LTag.DATABASE, "Inserted bolus $it") - } - result.invalidated.forEach { - uel.log(Action.BOLUS_REMOVED, Sources.NSClient, - ValueWithUnit.Timestamp(it.timestamp), - ValueWithUnit.Insulin(it.amount) - ) - aapsLogger.debug(LTag.DATABASE, "Invalidated bolus $it") - } - result.updatedNsId.forEach { - aapsLogger.debug(LTag.DATABASE, "Updated nsId bolus $it") - } - } - } ?: aapsLogger.error("Error parsing bolus json $json") - } - if (carbs > 0) { - carbsFromJson(json)?.let { carb -> - repository.runTransactionForResult(SyncNsCarbsTransaction(carb, invalidateByNsOnly = false)) - .doOnError { - aapsLogger.error(LTag.DATABASE, "Error while saving carbs", it) - ret = Result.failure(workDataOf("Error" to it.toString())) - } - .blockingGet() - .also { result -> - result.inserted.forEach { - uel.log(Action.CARBS, Sources.NSClient, - ValueWithUnit.Timestamp(it.timestamp), - ValueWithUnit.Gram(it.amount.toInt()) - ) - aapsLogger.debug(LTag.DATABASE, "Inserted carbs $it") - } - result.invalidated.forEach { - uel.log(Action.CARBS_REMOVED, Sources.NSClient, - ValueWithUnit.Timestamp(it.timestamp), - ValueWithUnit.Gram(it.amount.toInt()) - ) - aapsLogger.debug(LTag.DATABASE, "Invalidated carbs $it") - } - result.updatedNsId.forEach { - aapsLogger.debug(LTag.DATABASE, "Updated nsId carbs $it") - } - } - } ?: aapsLogger.error("Error parsing bolus json $json") - } - // Convert back emulated TBR -> EB - if (eventType == TherapyEvent.Type.TEMPORARY_BASAL.text && json.has("extendedEmulated")) { - val ebJson = json.getJSONObject("extendedEmulated") - ebJson.put("_id", json.getString("_id")) - ebJson.put("isValid", json.getBoolean("isValid")) - json = ebJson - } - when { - insulin > 0 || carbs > 0 -> Any() - eventType == TherapyEvent.Type.TEMPORARY_TARGET.text -> - temporaryTargetFromJson(json)?.let { temporaryTarget -> - repository.runTransactionForResult(SyncNsTemporaryTargetTransaction(temporaryTarget, invalidateByNsOnly = false)) - .doOnError { - aapsLogger.error(LTag.DATABASE, "Error while saving temporary target", it) - ret = Result.failure(workDataOf("Error" to it.toString())) - } - .blockingGet() - .also { result -> - result.inserted.forEach { tt -> - uel.log(Action.TT, Sources.NSClient, - ValueWithUnit.TherapyEventTTReason(tt.reason), - ValueWithUnit.fromGlucoseUnit(tt.lowTarget, Constants.MGDL), - ValueWithUnit.fromGlucoseUnit(tt.highTarget, Constants.MGDL).takeIf { tt.lowTarget != tt.highTarget }, - ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(tt.duration).toInt()) - ) - aapsLogger.debug(LTag.DATABASE, "Inserted TemporaryTarget $tt") - } - result.invalidated.forEach { tt -> - uel.log(Action.TT_REMOVED, Sources.NSClient, - ValueWithUnit.TherapyEventTTReason(tt.reason), - ValueWithUnit.Mgdl(tt.lowTarget), - ValueWithUnit.Mgdl(tt.highTarget).takeIf { tt.lowTarget != tt.highTarget }, - ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(tt.duration).toInt()) - ) - aapsLogger.debug(LTag.DATABASE, "Invalidated TemporaryTarget $tt") - } - result.ended.forEach { tt -> - uel.log(Action.CANCEL_TT, Sources.NSClient, - ValueWithUnit.TherapyEventTTReason(tt.reason), - ValueWithUnit.Mgdl(tt.lowTarget), - ValueWithUnit.Mgdl(tt.highTarget).takeIf { tt.lowTarget != tt.highTarget }, - ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(tt.duration).toInt()) - ) - aapsLogger.debug(LTag.DATABASE, "Updated TemporaryTarget $tt") - } - result.updatedNsId.forEach { - aapsLogger.debug(LTag.DATABASE, "Updated nsId TemporaryTarget $it") - } - } - } ?: aapsLogger.error("Error parsing TT json $json") - eventType == TherapyEvent.Type.CANNULA_CHANGE.text || - eventType == TherapyEvent.Type.INSULIN_CHANGE.text || - eventType == TherapyEvent.Type.SENSOR_CHANGE.text || - eventType == TherapyEvent.Type.FINGER_STICK_BG_VALUE.text || - eventType == TherapyEvent.Type.NONE.text || - eventType == TherapyEvent.Type.ANNOUNCEMENT.text || - eventType == TherapyEvent.Type.QUESTION.text || - eventType == TherapyEvent.Type.EXERCISE.text || - eventType == TherapyEvent.Type.APS_OFFLINE.text || - eventType == TherapyEvent.Type.PUMP_BATTERY_CHANGE.text -> - therapyEventFromJson(json)?.let { therapyEvent -> - repository.runTransactionForResult(SyncNsTherapyEventTransaction(therapyEvent, invalidateByNsOnly = false)) + if (sp.getBoolean(R.string.key_ns_receive_insulin, false) && buildHelper.isEngineeringMode() || config.NSCLIENT) { + bolusFromJson(json)?.let { bolus -> + repository.runTransactionForResult(SyncNsBolusTransaction(bolus, invalidateByNsOnly = false)) .doOnError { - aapsLogger.error(LTag.DATABASE, "Error while saving therapy event", it) + aapsLogger.error(LTag.DATABASE, "Error while saving bolus", it) ret = Result.failure(workDataOf("Error" to it.toString())) } .blockingGet() .also { result -> - val action = when (eventType) { - TherapyEvent.Type.CANNULA_CHANGE.text -> Action.SITE_CHANGE - TherapyEvent.Type.INSULIN_CHANGE.text -> Action.RESERVOIR_CHANGE - else -> Action.CAREPORTAL - } result.inserted.forEach { - uel.log(action, Sources.NSClient, - it.note ?: "", + uel.log(Action.BOLUS, Sources.NSClient, ValueWithUnit.Timestamp(it.timestamp), - ValueWithUnit.TherapyEventType(it.type) + ValueWithUnit.Insulin(it.amount) ) - aapsLogger.debug(LTag.DATABASE, "Inserted TherapyEvent $it") + aapsLogger.debug(LTag.DATABASE, "Inserted bolus $it") } result.invalidated.forEach { - uel.log(Action.CAREPORTAL_REMOVED, Sources.NSClient, - it.note ?: "", + uel.log(Action.BOLUS_REMOVED, Sources.NSClient, ValueWithUnit.Timestamp(it.timestamp), - ValueWithUnit.TherapyEventType(it.type) + ValueWithUnit.Insulin(it.amount) ) - aapsLogger.debug(LTag.DATABASE, "Invalidated TherapyEvent $it") + aapsLogger.debug(LTag.DATABASE, "Invalidated bolus $it") } result.updatedNsId.forEach { - aapsLogger.debug(LTag.DATABASE, "Updated nsId TherapyEvent $it") + aapsLogger.debug(LTag.DATABASE, "Updated nsId bolus $it") } } - } ?: aapsLogger.error("Error parsing TherapyEvent json $json") - eventType == TherapyEvent.Type.COMBO_BOLUS.text -> - extendedBolusFromJson(json)?.let { extendedBolus -> - repository.runTransactionForResult(SyncNsExtendedBolusTransaction(extendedBolus, invalidateByNsOnly = false)) + } ?: aapsLogger.error("Error parsing bolus json $json") + } + } + if (carbs > 0) { + if (sp.getBoolean(R.string.key_ns_receive_carbs, false) && buildHelper.isEngineeringMode() || config.NSCLIENT) { + carbsFromJson(json)?.let { carb -> + repository.runTransactionForResult(SyncNsCarbsTransaction(carb, invalidateByNsOnly = false)) .doOnError { - aapsLogger.error(LTag.DATABASE, "Error while saving extended bolus", it) + aapsLogger.error(LTag.DATABASE, "Error while saving carbs", it) ret = Result.failure(workDataOf("Error" to it.toString())) } .blockingGet() .also { result -> result.inserted.forEach { - uel.log(Action.EXTENDED_BOLUS, Sources.NSClient, + uel.log(Action.CARBS, Sources.NSClient, ValueWithUnit.Timestamp(it.timestamp), - ValueWithUnit.Insulin(it.amount), - ValueWithUnit.UnitPerHour(it.rate), - ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(it.duration).toInt()) + ValueWithUnit.Gram(it.amount.toInt()) ) - aapsLogger.debug(LTag.DATABASE, "Inserted ExtendedBolus $it") + aapsLogger.debug(LTag.DATABASE, "Inserted carbs $it") } result.invalidated.forEach { - uel.log(Action.EXTENDED_BOLUS_REMOVED, Sources.NSClient, - ValueWithUnit.Timestamp(it.timestamp), - ValueWithUnit.Insulin(it.amount), - ValueWithUnit.UnitPerHour(it.rate), - ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(it.duration).toInt()) - ) - aapsLogger.debug(LTag.DATABASE, "Invalidated ExtendedBolus $it") - } - result.ended.forEach { - uel.log(Action.CANCEL_EXTENDED_BOLUS, Sources.NSClient, + uel.log(Action.CARBS_REMOVED, Sources.NSClient, ValueWithUnit.Timestamp(it.timestamp), - ValueWithUnit.Insulin(it.amount), - ValueWithUnit.UnitPerHour(it.rate), - ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(it.duration).toInt()) + ValueWithUnit.Gram(it.amount.toInt()) ) - aapsLogger.debug(LTag.DATABASE, "Updated ExtendedBolus $it") + aapsLogger.debug(LTag.DATABASE, "Invalidated carbs $it") } result.updatedNsId.forEach { - aapsLogger.debug(LTag.DATABASE, "Updated nsId ExtendedBolus $it") + aapsLogger.debug(LTag.DATABASE, "Updated nsId carbs $it") } } - } ?: aapsLogger.error("Error parsing ExtendedBolus json $json") - eventType == TherapyEvent.Type.TEMPORARY_BASAL.text -> - temporaryBasalFromJson(json)?.let { temporaryBasal -> - repository.runTransactionForResult(SyncNsTemporaryBasalTransaction(temporaryBasal, invalidateByNsOnly = false)) - .doOnError { - aapsLogger.error(LTag.DATABASE, "Error while saving temporary basal", it) - ret = Result.failure(workDataOf("Error" to it.toString())) - } - .blockingGet() - .also { result -> - result.inserted.forEach { - uel.log(Action.TEMP_BASAL, Sources.NSClient, - ValueWithUnit.Timestamp(it.timestamp), - ValueWithUnit.UnitPerHour(it.rate), - ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(it.duration).toInt()) - ) - aapsLogger.debug(LTag.DATABASE, "Inserted TemporaryBasal $it") + } ?: aapsLogger.error("Error parsing bolus json $json") + } + } + // Convert back emulated TBR -> EB + if (eventType == TherapyEvent.Type.TEMPORARY_BASAL.text && json.has("extendedEmulated")) { + val ebJson = json.getJSONObject("extendedEmulated") + ebJson.put("_id", json.getString("_id")) + ebJson.put("isValid", json.getBoolean("isValid")) + json = ebJson + } + when { + insulin > 0 || carbs > 0 -> Any() + eventType == TherapyEvent.Type.TEMPORARY_TARGET.text -> + if (sp.getBoolean(R.string.key_ns_receive_temp_target, false) && buildHelper.isEngineeringMode() || config.NSCLIENT) { + temporaryTargetFromJson(json)?.let { temporaryTarget -> + repository.runTransactionForResult(SyncNsTemporaryTargetTransaction(temporaryTarget, invalidateByNsOnly = false)) + .doOnError { + aapsLogger.error(LTag.DATABASE, "Error while saving temporary target", it) + ret = Result.failure(workDataOf("Error" to it.toString())) } - result.invalidated.forEach { - uel.log(Action.TEMP_BASAL_REMOVED, Sources.NSClient, - ValueWithUnit.Timestamp(it.timestamp), - ValueWithUnit.UnitPerHour(it.rate), - ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(it.duration).toInt()) - ) - aapsLogger.debug(LTag.DATABASE, "Invalidated TemporaryBasal $it") + .blockingGet() + .also { result -> + result.inserted.forEach { tt -> + uel.log(Action.TT, Sources.NSClient, + ValueWithUnit.TherapyEventTTReason(tt.reason), + ValueWithUnit.fromGlucoseUnit(tt.lowTarget, Constants.MGDL), + ValueWithUnit.fromGlucoseUnit(tt.highTarget, Constants.MGDL).takeIf { tt.lowTarget != tt.highTarget }, + ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(tt.duration).toInt()) + ) + aapsLogger.debug(LTag.DATABASE, "Inserted TemporaryTarget $tt") + } + result.invalidated.forEach { tt -> + uel.log(Action.TT_REMOVED, Sources.NSClient, + ValueWithUnit.TherapyEventTTReason(tt.reason), + ValueWithUnit.Mgdl(tt.lowTarget), + ValueWithUnit.Mgdl(tt.highTarget).takeIf { tt.lowTarget != tt.highTarget }, + ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(tt.duration).toInt()) + ) + aapsLogger.debug(LTag.DATABASE, "Invalidated TemporaryTarget $tt") + } + result.ended.forEach { tt -> + uel.log(Action.CANCEL_TT, Sources.NSClient, + ValueWithUnit.TherapyEventTTReason(tt.reason), + ValueWithUnit.Mgdl(tt.lowTarget), + ValueWithUnit.Mgdl(tt.highTarget).takeIf { tt.lowTarget != tt.highTarget }, + ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(tt.duration).toInt()) + ) + aapsLogger.debug(LTag.DATABASE, "Updated TemporaryTarget $tt") + } + result.updatedNsId.forEach { + aapsLogger.debug(LTag.DATABASE, "Updated nsId TemporaryTarget $it") + } } - result.ended.forEach { - uel.log(Action.CANCEL_TEMP_BASAL, Sources.NSClient, - ValueWithUnit.Timestamp(it.timestamp), - ValueWithUnit.UnitPerHour(it.rate), - ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(it.duration).toInt()) - ) - aapsLogger.debug(LTag.DATABASE, "Ended TemporaryBasal $it") + } ?: aapsLogger.error("Error parsing TT json $json") + } + eventType == TherapyEvent.Type.CANNULA_CHANGE.text || + eventType == TherapyEvent.Type.INSULIN_CHANGE.text || + eventType == TherapyEvent.Type.SENSOR_CHANGE.text || + eventType == TherapyEvent.Type.FINGER_STICK_BG_VALUE.text || + eventType == TherapyEvent.Type.NONE.text || + eventType == TherapyEvent.Type.ANNOUNCEMENT.text || + eventType == TherapyEvent.Type.QUESTION.text || + eventType == TherapyEvent.Type.EXERCISE.text || + eventType == TherapyEvent.Type.PUMP_BATTERY_CHANGE.text -> + if (sp.getBoolean(R.string.key_ns_receive_therapy_events, false) || config.NSCLIENT) { + therapyEventFromJson(json)?.let { therapyEvent -> + repository.runTransactionForResult(SyncNsTherapyEventTransaction(therapyEvent, invalidateByNsOnly = false)) + .doOnError { + aapsLogger.error(LTag.DATABASE, "Error while saving therapy event", it) + ret = Result.failure(workDataOf("Error" to it.toString())) } - result.updatedNsId.forEach { - aapsLogger.debug(LTag.DATABASE, "Updated nsId TemporaryBasal $it") + .blockingGet() + .also { result -> + val action = when (eventType) { + TherapyEvent.Type.CANNULA_CHANGE.text -> Action.SITE_CHANGE + TherapyEvent.Type.INSULIN_CHANGE.text -> Action.RESERVOIR_CHANGE + else -> Action.CAREPORTAL + } + result.inserted.forEach { + uel.log(action, Sources.NSClient, + it.note ?: "", + ValueWithUnit.Timestamp(it.timestamp), + ValueWithUnit.TherapyEventType(it.type) + ) + aapsLogger.debug(LTag.DATABASE, "Inserted TherapyEvent $it") + } + result.invalidated.forEach { + uel.log(Action.CAREPORTAL_REMOVED, Sources.NSClient, + it.note ?: "", + ValueWithUnit.Timestamp(it.timestamp), + ValueWithUnit.TherapyEventType(it.type) + ) + aapsLogger.debug(LTag.DATABASE, "Invalidated TherapyEvent $it") + } + result.updatedNsId.forEach { + aapsLogger.debug(LTag.DATABASE, "Updated nsId TherapyEvent $it") + } } - } - } ?: aapsLogger.error("Error parsing TemporaryBasal json $json") + } ?: aapsLogger.error("Error parsing TherapyEvent json $json") + } + eventType == TherapyEvent.Type.COMBO_BOLUS.text -> + if (config.NSCLIENT) { + extendedBolusFromJson(json)?.let { extendedBolus -> + repository.runTransactionForResult(SyncNsExtendedBolusTransaction(extendedBolus, invalidateByNsOnly = false)) + .doOnError { + aapsLogger.error(LTag.DATABASE, "Error while saving extended bolus", it) + ret = Result.failure(workDataOf("Error" to it.toString())) + } + .blockingGet() + .also { result -> + result.inserted.forEach { + uel.log(Action.EXTENDED_BOLUS, Sources.NSClient, + ValueWithUnit.Timestamp(it.timestamp), + ValueWithUnit.Insulin(it.amount), + ValueWithUnit.UnitPerHour(it.rate), + ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(it.duration).toInt()) + ) + aapsLogger.debug(LTag.DATABASE, "Inserted ExtendedBolus $it") + } + result.invalidated.forEach { + uel.log(Action.EXTENDED_BOLUS_REMOVED, Sources.NSClient, + ValueWithUnit.Timestamp(it.timestamp), + ValueWithUnit.Insulin(it.amount), + ValueWithUnit.UnitPerHour(it.rate), + ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(it.duration).toInt()) + ) + aapsLogger.debug(LTag.DATABASE, "Invalidated ExtendedBolus $it") + } + result.ended.forEach { + uel.log(Action.CANCEL_EXTENDED_BOLUS, Sources.NSClient, + ValueWithUnit.Timestamp(it.timestamp), + ValueWithUnit.Insulin(it.amount), + ValueWithUnit.UnitPerHour(it.rate), + ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(it.duration).toInt()) + ) + aapsLogger.debug(LTag.DATABASE, "Updated ExtendedBolus $it") + } + result.updatedNsId.forEach { + aapsLogger.debug(LTag.DATABASE, "Updated nsId ExtendedBolus $it") + } + } + } ?: aapsLogger.error("Error parsing ExtendedBolus json $json") + } + eventType == TherapyEvent.Type.TEMPORARY_BASAL.text -> + if (config.NSCLIENT) { + temporaryBasalFromJson(json)?.let { temporaryBasal -> + repository.runTransactionForResult(SyncNsTemporaryBasalTransaction(temporaryBasal, invalidateByNsOnly = false)) + .doOnError { + aapsLogger.error(LTag.DATABASE, "Error while saving temporary basal", it) + ret = Result.failure(workDataOf("Error" to it.toString())) + } + .blockingGet() + .also { result -> + result.inserted.forEach { + uel.log(Action.TEMP_BASAL, Sources.NSClient, + ValueWithUnit.Timestamp(it.timestamp), + if (it.isAbsolute) ValueWithUnit.UnitPerHour(it.rate) else ValueWithUnit.Percent(it.rate.toInt()), + ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(it.duration).toInt()) + ) + aapsLogger.debug(LTag.DATABASE, "Inserted TemporaryBasal $it") + } + result.invalidated.forEach { + uel.log(Action.TEMP_BASAL_REMOVED, Sources.NSClient, + ValueWithUnit.Timestamp(it.timestamp), + if (it.isAbsolute) ValueWithUnit.UnitPerHour(it.rate) else ValueWithUnit.Percent(it.rate.toInt()), + ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(it.duration).toInt()) + ) + aapsLogger.debug(LTag.DATABASE, "Invalidated TemporaryBasal $it") + } + result.ended.forEach { + uel.log(Action.CANCEL_TEMP_BASAL, Sources.NSClient, + ValueWithUnit.Timestamp(it.timestamp), + if (it.isAbsolute) ValueWithUnit.UnitPerHour(it.rate) else ValueWithUnit.Percent(it.rate.toInt()), + ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(it.duration).toInt()) + ) + aapsLogger.debug(LTag.DATABASE, "Ended TemporaryBasal $it") + } + result.updatedNsId.forEach { + aapsLogger.debug(LTag.DATABASE, "Updated nsId TemporaryBasal $it") + } + } + } ?: aapsLogger.error("Error parsing TemporaryBasal json $json") + } eventType == TherapyEvent.Type.PROFILE_SWITCH.text -> - profileSwitchFromJson(json, dateUtil)?.let { profileSwitch -> - repository.runTransactionForResult(SyncNsProfileSwitchTransaction(profileSwitch, invalidateByNsOnly = false)) - .doOnError { - aapsLogger.error(LTag.DATABASE, "Error while saving ProfileSwitch", it) - ret = Result.failure(workDataOf("Error" to it.toString())) - } - .blockingGet() - .also { result -> - result.inserted.forEach { - uel.log(Action.PROFILE_SWITCH, Sources.NSClient, - ValueWithUnit.Timestamp(it.timestamp)) - aapsLogger.debug(LTag.DATABASE, "Inserted ProfileSwitch $it") + if (sp.getBoolean(R.string.key_ns_receive_profile_switch, false) && buildHelper.isEngineeringMode() || config.NSCLIENT) { + profileSwitchFromJson(json, dateUtil, activePlugin)?.let { profileSwitch -> + repository.runTransactionForResult(SyncNsProfileSwitchTransaction(profileSwitch, invalidateByNsOnly = false)) + .doOnError { + aapsLogger.error(LTag.DATABASE, "Error while saving ProfileSwitch", it) + ret = Result.failure(workDataOf("Error" to it.toString())) } - result.invalidated.forEach { - uel.log(Action.PROFILE_SWITCH_REMOVED, Sources.NSClient, - ValueWithUnit.Timestamp(it.timestamp)) - aapsLogger.debug(LTag.DATABASE, "Invalidated ProfileSwitch $it") + .blockingGet() + .also { result -> + result.inserted.forEach { + uel.log(Action.PROFILE_SWITCH, Sources.NSClient, + ValueWithUnit.Timestamp(it.timestamp)) + aapsLogger.debug(LTag.DATABASE, "Inserted ProfileSwitch $it") + } + result.invalidated.forEach { + uel.log(Action.PROFILE_SWITCH_REMOVED, Sources.NSClient, + ValueWithUnit.Timestamp(it.timestamp)) + aapsLogger.debug(LTag.DATABASE, "Invalidated ProfileSwitch $it") + } + result.updatedNsId.forEach { + aapsLogger.debug(LTag.DATABASE, "Updated nsId ProfileSwitch $it") + } } - result.updatedNsId.forEach { - aapsLogger.debug(LTag.DATABASE, "Updated nsId ProfileSwitch $it") + } ?: aapsLogger.error("Error parsing ProfileSwitch json $json") + } + eventType == TherapyEvent.Type.APS_OFFLINE.text -> + if (sp.getBoolean(R.string.key_ns_receive_offline_event, false) && buildHelper.isEngineeringMode() || config.NSCLIENT) { + offlineEventFromJson(json)?.let { offlineEvent -> + repository.runTransactionForResult(SyncNsOfflineEventTransaction(offlineEvent, invalidateByNsOnly = false)) + .doOnError { + aapsLogger.error(LTag.DATABASE, "Error while saving OfflineEvent", it) + ret = Result.failure(workDataOf("Error" to it.toString())) } - } - } ?: aapsLogger.error("Error parsing TemporaryBasal json $json") + .blockingGet() + .also { result -> + result.inserted.forEach { oe -> + uel.log(Action.LOOP_CHANGE, Sources.NSClient, + ValueWithUnit.OfflineEventReason(oe.reason), + ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(oe.duration).toInt()) + ) + aapsLogger.debug(LTag.DATABASE, "Inserted OfflineEvent $oe") + } + result.invalidated.forEach { oe -> + uel.log(Action.LOOP_REMOVED, Sources.NSClient, + ValueWithUnit.OfflineEventReason(oe.reason), + ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(oe.duration).toInt()) + ) + aapsLogger.debug(LTag.DATABASE, "Invalidated OfflineEvent $oe") + } + result.ended.forEach { oe -> + uel.log(Action.LOOP_CHANGE, Sources.NSClient, + ValueWithUnit.OfflineEventReason(oe.reason), + ValueWithUnit.Minute(TimeUnit.MILLISECONDS.toMinutes(oe.duration).toInt()) + ) + aapsLogger.debug(LTag.DATABASE, "Updated OfflineEvent $oe") + } + result.updatedNsId.forEach { + aapsLogger.debug(LTag.DATABASE, "Updated nsId OfflineEvent $it") + } + } + } ?: aapsLogger.error("Error parsing OfflineEvent json $json") + } } - if (eventType == TherapyEvent.Type.ANNOUNCEMENT.text) { - val date = safeGetLong(json, "mills") - val now = System.currentTimeMillis() - val enteredBy = JsonHelper.safeGetString(json, "enteredBy", "") - val notes = JsonHelper.safeGetString(json, "notes", "") - if (date > now - 15 * 60 * 1000L && notes.isNotEmpty() - && enteredBy != sp.getString("careportal_enteredby", "AndroidAPS")) { - val defaultVal = config.NSCLIENT - if (sp.getBoolean(R.string.key_ns_announcements, defaultVal)) { - val announcement = Notification(Notification.NS_ANNOUNCEMENT, notes, Notification.ANNOUNCEMENT, 60) - rxBus.send(EventNewNotification(announcement)) + if (sp.getBoolean(R.string.key_ns_receive_therapy_events, false) || config.NSCLIENT) + if (eventType == TherapyEvent.Type.ANNOUNCEMENT.text) { + val date = safeGetLong(json, "mills") + val now = System.currentTimeMillis() + val enteredBy = JsonHelper.safeGetString(json, "enteredBy", "") + val notes = JsonHelper.safeGetString(json, "notes", "") + if (date > now - 15 * 60 * 1000L && notes.isNotEmpty() + && enteredBy != sp.getString("careportal_enteredby", "AndroidAPS")) { + val defaultVal = config.NSCLIENT + if (sp.getBoolean(R.string.key_ns_announcements, defaultVal)) { + val announcement = Notification(Notification.NS_ANNOUNCEMENT, notes, Notification.ANNOUNCEMENT, 60) + rxBus.send(EventNewNotification(announcement)) + } } } - } } nsClientPlugin.updateLatestDateReceivedIfNewer(latestDateInReceivedData) return ret diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientFragment.kt index d2e77a4f95d..80b508766d5 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientFragment.kt @@ -12,14 +12,11 @@ import info.nightscout.androidaps.database.entities.UserEntry.Action import info.nightscout.androidaps.database.entities.UserEntry.Sources import info.nightscout.androidaps.databinding.NsClientFragmentBinding import info.nightscout.androidaps.interfaces.DataSyncSelector -import info.nightscout.androidaps.interfaces.UploadQueueAdminInterface import info.nightscout.androidaps.logging.UserEntryLogger import info.nightscout.androidaps.plugins.bus.RxBusWrapper -import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientNewLog import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientUpdateGUI import info.nightscout.androidaps.utils.FabricPrivacy -import info.nightscout.androidaps.utils.HtmlHelper.fromHtml import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.rx.AapsSchedulers @@ -33,7 +30,6 @@ class NSClientFragment : DaggerFragment() { @Inject lateinit var sp: SP @Inject lateinit var resourceHelper: ResourceHelper @Inject lateinit var rxBus: RxBusWrapper - @Inject lateinit var uploadQueue: UploadQueueAdminInterface @Inject lateinit var fabricPrivacy: FabricPrivacy @Inject lateinit var aapsSchedulers: AapsSchedulers @Inject lateinit var dataSyncSelector: DataSyncSelector @@ -72,18 +68,6 @@ class NSClientFragment : DaggerFragment() { binding.restart.paintFlags = binding.restart.paintFlags or Paint.UNDERLINE_TEXT_FLAG binding.deliverNow.setOnClickListener { nsClientPlugin.resend("GUI") } binding.deliverNow.paintFlags = binding.deliverNow.paintFlags or Paint.UNDERLINE_TEXT_FLAG - binding.clearQueue.setOnClickListener { - context?.let { context -> - OKDialog.showConfirmation(context, resourceHelper.gs(R.string.nsclientinternal), resourceHelper.gs(R.string.clearqueueconfirm), Runnable { - uel.log(Action.NS_QUEUE_CLEARED, Sources.NSClient) - uploadQueue.clearQueue() - updateGui() - }) - } - } - binding.clearQueue.paintFlags = binding.clearQueue.paintFlags or Paint.UNDERLINE_TEXT_FLAG - binding.showQueue.setOnClickListener { rxBus.send(EventNSClientNewLog("QUEUE", uploadQueue.textList())) } - binding.showQueue.paintFlags = binding.showQueue.paintFlags or Paint.UNDERLINE_TEXT_FLAG binding.fullSync.setOnClickListener { context?.let { context -> OKDialog.showConfirmation(context, resourceHelper.gs(R.string.nsclientinternal), resourceHelper.gs(R.string.full_sync), Runnable { @@ -116,7 +100,6 @@ class NSClientFragment : DaggerFragment() { binding.log.text = nsClientPlugin.textLog if (nsClientPlugin.autoscroll) binding.logScrollview.fullScroll(ScrollView.FOCUS_DOWN) binding.url.text = nsClientPlugin.url() - binding.queue.text = fromHtml(resourceHelper.gs(R.string.queue) + " " + uploadQueue.size() + "") binding.status.text = nsClientPlugin.status } } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientMbgWorker.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientMbgWorker.kt index a769f2ec386..b7f1d1540c9 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientMbgWorker.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientMbgWorker.kt @@ -8,13 +8,13 @@ import dagger.android.HasAndroidInjector import info.nightscout.androidaps.R import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.transactions.SyncNsTherapyEventTransaction +import info.nightscout.androidaps.extensions.therapyEventFromNsMbg import info.nightscout.androidaps.interfaces.Config import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.plugins.general.nsclient.data.NSMbg import info.nightscout.androidaps.receivers.DataWorker import info.nightscout.androidaps.utils.buildHelper.BuildHelper -import info.nightscout.androidaps.extensions.therapyEventFromNsMbg import info.nightscout.androidaps.utils.sharedPreferences.SP import javax.inject.Inject @@ -33,8 +33,8 @@ class NSClientMbgWorker( override fun doWork(): Result { var ret = Result.success() - val acceptNSData = !sp.getBoolean(R.string.key_ns_upload_only, true) && buildHelper.isEngineeringMode() || config.NSCLIENT - if (!acceptNSData) return ret + val acceptNSData = sp.getBoolean(R.string.key_ns_receive_therapy_events, false) || config.NSCLIENT + if (!acceptNSData) return Result.success(workDataOf("Result" to "Sync not enabled")) val mbgArray = dataWorker.pickupJSONArray(inputData.getLong(DataWorker.STORE_KEY, -1)) ?: return Result.failure(workDataOf("Error" to "missing input data")) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientPlugin.java deleted file mode 100644 index ae28f50b36c..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientPlugin.java +++ /dev/null @@ -1,320 +0,0 @@ -package info.nightscout.androidaps.plugins.general.nsclient; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.IBinder; -import android.text.Spanned; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.preference.PreferenceFragmentCompat; -import androidx.preference.SwitchPreference; - -import java.util.ArrayList; -import java.util.List; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import dagger.android.HasAndroidInjector; -import info.nightscout.androidaps.interfaces.Config; -import info.nightscout.androidaps.Constants; -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.events.EventAppExit; -import info.nightscout.androidaps.events.EventChargingState; -import info.nightscout.androidaps.events.EventNetworkChange; -import info.nightscout.androidaps.events.EventPreferenceChange; -import info.nightscout.androidaps.interfaces.PluginBase; -import info.nightscout.androidaps.interfaces.PluginDescription; -import info.nightscout.androidaps.interfaces.PluginType; -import info.nightscout.androidaps.logging.AAPSLogger; -import info.nightscout.androidaps.logging.LTag; -import info.nightscout.androidaps.plugins.bus.RxBusWrapper; -import info.nightscout.androidaps.plugins.general.nsclient.data.AlarmAck; -import info.nightscout.androidaps.plugins.general.nsclient.data.NSAlarm; -import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientNewLog; -import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientResend; -import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientStatus; -import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientUpdateGUI; -import info.nightscout.androidaps.plugins.general.nsclient.services.NSClientService; -import info.nightscout.androidaps.utils.FabricPrivacy; -import info.nightscout.androidaps.utils.HtmlHelper; -import info.nightscout.androidaps.utils.ToastUtils; -import info.nightscout.androidaps.utils.buildHelper.BuildHelper; -import info.nightscout.androidaps.utils.resources.ResourceHelper; -import info.nightscout.androidaps.utils.rx.AapsSchedulers; -import info.nightscout.androidaps.utils.sharedPreferences.SP; -import io.reactivex.disposables.CompositeDisposable; - -@Singleton -public class NSClientPlugin extends PluginBase { - private final CompositeDisposable disposable = new CompositeDisposable(); - - private final AAPSLogger aapsLogger; - private final RxBusWrapper rxBus; - private final ResourceHelper resourceHelper; - private final Context context; - private final AapsSchedulers aapsSchedulers; - private final FabricPrivacy fabricPrivacy; - private final SP sp; - private final NsClientReceiverDelegate nsClientReceiverDelegate; - private final Config config; - private final BuildHelper buildHelper; - - public Handler handler; - - private final List listLog = new ArrayList<>(); - Spanned textLog = HtmlHelper.INSTANCE.fromHtml(""); - - public boolean paused; - boolean autoscroll; - - public String status = ""; - - public @Nullable NSClientService nsClientService = null; - - - @Inject - public NSClientPlugin( - HasAndroidInjector injector, - AAPSLogger aapsLogger, - AapsSchedulers aapsSchedulers, - RxBusWrapper rxBus, - ResourceHelper resourceHelper, - Context context, - FabricPrivacy fabricPrivacy, - SP sp, - NsClientReceiverDelegate nsClientReceiverDelegate, - Config config, - BuildHelper buildHelper - ) { - super(new PluginDescription() - .mainType(PluginType.GENERAL) - .fragmentClass(NSClientFragment.class.getName()) - .pluginIcon(R.drawable.ic_nightscout_syncs) - .pluginName(R.string.nsclientinternal) - .shortName(R.string.nsclientinternal_shortname) - .preferencesId(R.xml.pref_nsclientinternal) - .description(R.string.description_ns_client), - aapsLogger, resourceHelper, injector - ); - - this.aapsLogger = aapsLogger; - this.aapsSchedulers = aapsSchedulers; - this.rxBus = rxBus; - this.resourceHelper = resourceHelper; - this.context = context; - this.fabricPrivacy = fabricPrivacy; - this.sp = sp; - this.nsClientReceiverDelegate = nsClientReceiverDelegate; - this.config = config; - this.buildHelper = buildHelper; - - if (config.getNSCLIENT()) { - getPluginDescription().alwaysEnabled(true).visibleByDefault(true); - } - if (handler == null) { - HandlerThread handlerThread = new HandlerThread(NSClientPlugin.class.getSimpleName() + "Handler"); - handlerThread.start(); - handler = new Handler(handlerThread.getLooper()); - } - - } - - public boolean isAllowed() { - return nsClientReceiverDelegate.allowed; - } - - - @Override - protected void onStart() { - paused = sp.getBoolean(R.string.key_nsclientinternal_paused, false); - autoscroll = sp.getBoolean(R.string.key_nsclientinternal_autoscroll, true); - - Intent intent = new Intent(context, NSClientService.class); - context.bindService(intent, mConnection, Context.BIND_AUTO_CREATE); - super.onStart(); - - nsClientReceiverDelegate.grabReceiversState(); - disposable.add(rxBus - .toObservable(EventNSClientStatus.class) - .observeOn(aapsSchedulers.getIo()) - .subscribe(event -> { - status = event.getStatus(resourceHelper); - rxBus.send(new EventNSClientUpdateGUI()); - }, fabricPrivacy::logException) - ); - disposable.add(rxBus - .toObservable(EventNetworkChange.class) - .observeOn(aapsSchedulers.getIo()) - .subscribe(nsClientReceiverDelegate::onStatusEvent, fabricPrivacy::logException) - ); - disposable.add(rxBus - .toObservable(EventPreferenceChange.class) - .observeOn(aapsSchedulers.getIo()) - .subscribe(nsClientReceiverDelegate::onStatusEvent, fabricPrivacy::logException) - ); - disposable.add(rxBus - .toObservable(EventAppExit.class) - .observeOn(aapsSchedulers.getIo()) - .subscribe(event -> { - if (nsClientService != null) { - context.unbindService(mConnection); - } - }, fabricPrivacy::logException) - ); - disposable.add(rxBus - .toObservable(EventNSClientNewLog.class) - .observeOn(aapsSchedulers.getIo()) - .subscribe(event -> { - addToLog(event); - aapsLogger.debug(LTag.NSCLIENT, event.getAction() + " " + event.getLogText()); - }, fabricPrivacy::logException) - ); - disposable.add(rxBus - .toObservable(EventChargingState.class) - .observeOn(aapsSchedulers.getIo()) - .subscribe(nsClientReceiverDelegate::onStatusEvent, fabricPrivacy::logException) - ); - disposable.add(rxBus - .toObservable(EventNSClientResend.class) - .observeOn(aapsSchedulers.getIo()) - .subscribe(event -> resend(event.getReason()), fabricPrivacy::logException) - ); - } - - @Override - protected void onStop() { - context.getApplicationContext().unbindService(mConnection); - disposable.clear(); - super.onStop(); - } - - @Override - public void preprocessPreferences(@NonNull PreferenceFragmentCompat preferenceFragment) { - super.preprocessPreferences(preferenceFragment); - - if (config.getNSCLIENT()) { - SwitchPreference key_ns_uploadlocalprofile = preferenceFragment.findPreference(resourceHelper.gs(R.string.key_ns_uploadlocalprofile)); - if (key_ns_uploadlocalprofile != null) key_ns_uploadlocalprofile.setVisible(false); - SwitchPreference key_ns_autobackfill = preferenceFragment.findPreference(resourceHelper.gs(R.string.key_ns_autobackfill)); - if (key_ns_autobackfill != null) key_ns_autobackfill.setVisible(false); - SwitchPreference key_ns_create_announcements_from_errors = preferenceFragment.findPreference(resourceHelper.gs(R.string.key_ns_create_announcements_from_errors)); - if (key_ns_create_announcements_from_errors != null) - key_ns_create_announcements_from_errors.setVisible(false); - SwitchPreference key_ns_create_announcements_from_carbs_req = preferenceFragment.findPreference(resourceHelper.gs(R.string.key_ns_create_announcements_from_carbs_req)); - if (key_ns_create_announcements_from_carbs_req != null) - key_ns_create_announcements_from_carbs_req.setVisible(false); - SwitchPreference key_ns_upload_only = preferenceFragment.findPreference(resourceHelper.gs(R.string.key_ns_upload_only)); - if (key_ns_upload_only != null) { - key_ns_upload_only.setVisible(false); - key_ns_upload_only.setEnabled(false); - } - SwitchPreference key_ns_sync_use_absolute = preferenceFragment.findPreference(resourceHelper.gs(R.string.key_ns_sync_use_absolute)); - if (key_ns_sync_use_absolute != null) key_ns_sync_use_absolute.setVisible(false); - } else { - // APS or pumpControl mode - SwitchPreference key_ns_upload_only = preferenceFragment.findPreference(resourceHelper.gs(R.string.key_ns_upload_only)); - if (key_ns_upload_only != null) - key_ns_upload_only.setVisible(buildHelper.isEngineeringMode()); - } - } - - private final ServiceConnection mConnection = new ServiceConnection() { - - public void onServiceDisconnected(ComponentName name) { - aapsLogger.debug(LTag.NSCLIENT, "Service is disconnected"); - nsClientService = null; - } - - public void onServiceConnected(ComponentName name, IBinder service) { - aapsLogger.debug(LTag.NSCLIENT, "Service is connected"); - NSClientService.LocalBinder mLocalBinder = (NSClientService.LocalBinder) service; - if (mLocalBinder != null) // is null when running in roboelectric - nsClientService = mLocalBinder.getServiceInstance(); - } - }; - - synchronized void clearLog() { - handler.post(() -> { - synchronized (listLog) { - listLog.clear(); - } - rxBus.send(new EventNSClientUpdateGUI()); - }); - } - - private synchronized void addToLog(final EventNSClientNewLog ev) { - handler.post(() -> { - synchronized (listLog) { - listLog.add(ev); - // remove the first line if log is too large - if (listLog.size() >= Constants.MAX_LOG_LINES) { - listLog.remove(0); - } - } - rxBus.send(new EventNSClientUpdateGUI()); - }); - } - - synchronized void updateLog() { - try { - StringBuilder newTextLog = new StringBuilder(); - synchronized (listLog) { - for (EventNSClientNewLog log : listLog) { - newTextLog.append(log.toPreparedHtml()); - } - } - textLog = HtmlHelper.INSTANCE.fromHtml(newTextLog.toString()); - } catch (OutOfMemoryError e) { - ToastUtils.showToastInUiThread(context, rxBus, "Out of memory!\nStop using this phone !!!", R.raw.error); - } - } - - void resend(String reason) { - if (nsClientService != null) - nsClientService.resend(reason); - } - - public void pause(boolean newState) { - sp.putBoolean(R.string.key_nsclientinternal_paused, newState); - paused = newState; - rxBus.send(new EventPreferenceChange(resourceHelper, R.string.key_nsclientinternal_paused)); - } - - public String url() { - return NSClientService.nsURL; - } - - public boolean hasWritePermission() { - return NSClientService.hasWriteAuth; - } - - public void handleClearAlarm(NSAlarm originalAlarm, long silenceTimeInMsec) { - - if (!isEnabled(PluginType.GENERAL)) { - return; - } - if (sp.getBoolean(R.string.key_ns_noupload, false)) { - aapsLogger.debug(LTag.NSCLIENT, "Upload disabled. Message dropped"); - return; - } - - AlarmAck ack = new AlarmAck(); - ack.level = originalAlarm.level(); - ack.group = originalAlarm.group(); - ack.silenceTime = silenceTimeInMsec; - - if (nsClientService != null) - nsClientService.sendAlarmAck(ack); - } - - public void updateLatestDateReceivedIfNewer(long latestReceived) { - if (nsClientService != null && latestReceived > nsClientService.latestDateInReceivedData) - nsClientService.latestDateInReceivedData = latestReceived; - } -} 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 new file mode 100644 index 00000000000..54bc13f0bdf --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientPlugin.kt @@ -0,0 +1,243 @@ +package info.nightscout.androidaps.plugins.general.nsclient + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.ServiceConnection +import android.os.Handler +import android.os.HandlerThread +import android.os.IBinder +import androidx.preference.PreferenceFragmentCompat +import androidx.preference.PreferenceScreen +import androidx.preference.SwitchPreference +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.Constants +import info.nightscout.androidaps.R +import info.nightscout.androidaps.events.EventAppExit +import info.nightscout.androidaps.events.EventChargingState +import info.nightscout.androidaps.events.EventNetworkChange +import info.nightscout.androidaps.events.EventPreferenceChange +import info.nightscout.androidaps.interfaces.Config +import info.nightscout.androidaps.interfaces.PluginBase +import info.nightscout.androidaps.interfaces.PluginDescription +import info.nightscout.androidaps.interfaces.PluginType +import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.plugins.bus.RxBusWrapper +import info.nightscout.androidaps.plugins.general.nsclient.data.AlarmAck +import info.nightscout.androidaps.plugins.general.nsclient.data.NSAlarm +import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientNewLog +import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientResend +import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientStatus +import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientUpdateGUI +import info.nightscout.androidaps.plugins.general.nsclient.services.NSClientService +import info.nightscout.androidaps.utils.FabricPrivacy +import info.nightscout.androidaps.utils.HtmlHelper.fromHtml +import info.nightscout.androidaps.utils.ToastUtils +import info.nightscout.androidaps.utils.buildHelper.BuildHelper +import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.androidaps.utils.rx.AapsSchedulers +import info.nightscout.androidaps.utils.sharedPreferences.SP +import io.reactivex.disposables.CompositeDisposable +import java.util.* +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class NSClientPlugin @Inject constructor( + injector: HasAndroidInjector, + aapsLogger: AAPSLogger, + private val aapsSchedulers: AapsSchedulers, + private val rxBus: RxBusWrapper, + resourceHelper: ResourceHelper, + private val context: Context, + private val fabricPrivacy: FabricPrivacy, + private val sp: SP, + private val nsClientReceiverDelegate: NsClientReceiverDelegate, + private val config: Config, + private val buildHelper: BuildHelper +) : PluginBase(PluginDescription() + .mainType(PluginType.GENERAL) + .fragmentClass(NSClientFragment::class.java.name) + .pluginIcon(R.drawable.ic_nightscout_syncs) + .pluginName(R.string.nsclientinternal) + .shortName(R.string.nsclientinternal_shortname) + .preferencesId(R.xml.pref_nsclientinternal) + .description(R.string.description_ns_client), + aapsLogger, resourceHelper, injector +) { + + private val disposable = CompositeDisposable() + var handler: Handler? = null + private val listLog: MutableList = ArrayList() + var textLog = fromHtml("") + var paused = false + var autoscroll = false + var status = "" + var nsClientService: NSClientService? = null + val isAllowed: Boolean + get() = nsClientReceiverDelegate.allowed + + init { + if (config.NSCLIENT) { + pluginDescription.alwaysEnabled(true).visibleByDefault(true) + } + if (handler == null) { + val handlerThread = HandlerThread(NSClientPlugin::class.java.simpleName + "Handler") + handlerThread.start() + handler = Handler(handlerThread.looper) + } + } + + override fun onStart() { + paused = sp.getBoolean(R.string.key_nsclientinternal_paused, false) + autoscroll = sp.getBoolean(R.string.key_nsclientinternal_autoscroll, true) + val intent = Intent(context, NSClientService::class.java) + context.bindService(intent, mConnection, Context.BIND_AUTO_CREATE) + super.onStart() + nsClientReceiverDelegate.grabReceiversState() + disposable.add(rxBus + .toObservable(EventNSClientStatus::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ event: EventNSClientStatus -> + status = event.getStatus(resourceHelper) + rxBus.send(EventNSClientUpdateGUI()) + }, fabricPrivacy::logException) + ) + disposable.add(rxBus + .toObservable(EventNetworkChange::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ ev -> nsClientReceiverDelegate.onStatusEvent(ev) }, fabricPrivacy::logException) + ) + disposable.add(rxBus + .toObservable(EventPreferenceChange::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ ev -> nsClientReceiverDelegate.onStatusEvent(ev) }, fabricPrivacy::logException) + ) + disposable.add(rxBus + .toObservable(EventAppExit::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ if (nsClientService != null) context.unbindService(mConnection) }, fabricPrivacy::logException) + ) + disposable.add(rxBus + .toObservable(EventNSClientNewLog::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ event: EventNSClientNewLog -> + addToLog(event) + aapsLogger.debug(LTag.NSCLIENT, event.action + " " + event.logText) + }, fabricPrivacy::logException) + ) + disposable.add(rxBus + .toObservable(EventChargingState::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ ev -> nsClientReceiverDelegate.onStatusEvent(ev) }, fabricPrivacy::logException) + ) + disposable.add(rxBus + .toObservable(EventNSClientResend::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ event -> resend(event.reason) }, fabricPrivacy::logException) + ) + } + + override fun onStop() { + context.applicationContext.unbindService(mConnection) + disposable.clear() + super.onStop() + } + + override fun preprocessPreferences(preferenceFragment: PreferenceFragmentCompat) { + super.preprocessPreferences(preferenceFragment) + if (config.NSCLIENT) { + preferenceFragment.findPreference(resourceHelper.gs(R.string.ns_sync_options))?.isVisible = false + + preferenceFragment.findPreference(resourceHelper.gs(R.string.key_ns_create_announcements_from_errors))?.isVisible = false + preferenceFragment.findPreference(resourceHelper.gs(R.string.key_ns_create_announcements_from_carbs_req))?.isVisible = false + preferenceFragment.findPreference(resourceHelper.gs(R.string.key_ns_sync_use_absolute))?.isVisible = false + } else { + // APS or pumpControl mode + preferenceFragment.findPreference(resourceHelper.gs(R.string.key_ns_receive_profile_switch))?.isVisible = buildHelper.isEngineeringMode() + preferenceFragment.findPreference(resourceHelper.gs(R.string.key_ns_receive_insulin))?.isVisible = buildHelper.isEngineeringMode() + preferenceFragment.findPreference(resourceHelper.gs(R.string.key_ns_receive_carbs))?.isVisible = buildHelper.isEngineeringMode() + preferenceFragment.findPreference(resourceHelper.gs(R.string.key_ns_receive_temp_target))?.isVisible = buildHelper.isEngineeringMode() + } + } + + private val mConnection: ServiceConnection = object : ServiceConnection { + override fun onServiceDisconnected(name: ComponentName) { + aapsLogger.debug(LTag.NSCLIENT, "Service is disconnected") + nsClientService = null + } + + override fun onServiceConnected(name: ComponentName, service: IBinder) { + aapsLogger.debug(LTag.NSCLIENT, "Service is connected") + val mLocalBinder = service as NSClientService.LocalBinder + @Suppress("UNNECESSARY_SAFE_CALL") + nsClientService = mLocalBinder?.serviceInstance // is null when running in roboelectric + } + } + + @Synchronized fun clearLog() { + handler?.post { + synchronized(listLog) { listLog.clear() } + rxBus.send(EventNSClientUpdateGUI()) + } + } + + @Synchronized private fun addToLog(ev: EventNSClientNewLog) { + handler?.post { + synchronized(listLog) { + listLog.add(ev) + // remove the first line if log is too large + if (listLog.size >= Constants.MAX_LOG_LINES) { + listLog.removeAt(0) + } + } + rxBus.send(EventNSClientUpdateGUI()) + } + } + + @Synchronized fun updateLog() { + try { + val newTextLog = StringBuilder() + synchronized(listLog) { + for (log in listLog) { + newTextLog.append(log.toPreparedHtml()) + } + } + textLog = fromHtml(newTextLog.toString()) + } catch (e: OutOfMemoryError) { + ToastUtils.showToastInUiThread(context, rxBus, "Out of memory!\nStop using this phone !!!", R.raw.error) + } + } + + fun resend(reason: String) { + nsClientService?.resend(reason) + } + + fun pause(newState: Boolean) { + sp.putBoolean(R.string.key_nsclientinternal_paused, newState) + paused = newState + rxBus.send(EventPreferenceChange(resourceHelper, R.string.key_nsclientinternal_paused)) + } + + fun url(): String = nsClientService?.nsURL ?: "" + fun hasWritePermission(): Boolean = nsClientService?.hasWriteAuth ?: false + + fun handleClearAlarm(originalAlarm: NSAlarm, silenceTimeInMilliseconds: Long) { + if (!isEnabled(PluginType.GENERAL)) return + if (!sp.getBoolean(R.string.key_ns_upload, false)) { + aapsLogger.debug(LTag.NSCLIENT, "Upload disabled. Message dropped") + return + } + nsClientService?.sendAlarmAck( + AlarmAck().also { ack -> + ack.level = originalAlarm.level() + ack.group = originalAlarm.group() + ack.silenceTime = silenceTimeInMilliseconds + }) + } + + fun updateLatestDateReceivedIfNewer(latestReceived: Long) { + nsClientService?.let { if (latestReceived > it.latestDateInReceivedData) it.latestDateInReceivedData = latestReceived } + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientRemoveWorker.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientRemoveWorker.kt index a2423de9ce0..ee8dac6c47a 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientRemoveWorker.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientRemoveWorker.kt @@ -42,8 +42,9 @@ class NSClientRemoveWorker( @Inject lateinit var uel: UserEntryLogger override fun doWork(): Result { - val acceptNSData = !sp.getBoolean(R.string.key_ns_upload_only, true) && buildHelper.isEngineeringMode() || config.NSCLIENT - if (!acceptNSData) return Result.success() + // Do not accept removed data over WS. Only invalidated trough NSClient + @Suppress("ConstantConditionIf") + if (true) return Result.success() var ret = Result.success() diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientUpdateRemoveAckWorker.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientUpdateRemoveAckWorker.kt index cd818d44615..5a38645c4d4 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientUpdateRemoveAckWorker.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientUpdateRemoveAckWorker.kt @@ -39,7 +39,7 @@ class NSClientUpdateRemoveAckWorker( is PairTemporaryTarget -> { val pair = ack.originalObject dataSyncSelector.confirmLastTempTargetsIdIfGreater(pair.updateRecordId) - rxBus.send(EventNSClientNewLog("DBUPDATE/DBREMOVE", "Acked TemporaryTarget" + ack._id)) + rxBus.send(EventNSClientNewLog("DBUPDATE", "Acked TemporaryTarget" + ack._id)) // Send new if waiting dataSyncSelector.processChangedTempTargetsCompat() ret = Result.success(workDataOf("ProcessedData" to pair.toString())) @@ -48,7 +48,7 @@ class NSClientUpdateRemoveAckWorker( is PairGlucoseValue -> { val pair = ack.originalObject dataSyncSelector.confirmLastGlucoseValueIdIfGreater(pair.updateRecordId) - rxBus.send(EventNSClientNewLog("DBUPDATE/DBREMOVE", "Acked GlucoseValue " + ack._id)) + rxBus.send(EventNSClientNewLog("DBUPDATE", "Acked GlucoseValue " + ack._id)) // Send new if waiting dataSyncSelector.processChangedGlucoseValuesCompat() ret = Result.success(workDataOf("ProcessedData" to pair.toString())) @@ -57,7 +57,7 @@ class NSClientUpdateRemoveAckWorker( is PairFood -> { val pair = ack.originalObject dataSyncSelector.confirmLastFoodIdIfGreater(pair.updateRecordId) - rxBus.send(EventNSClientNewLog("DBUPDATE/DBREMOVE", "Acked Food " + ack._id)) + rxBus.send(EventNSClientNewLog("DBUPDATE", "Acked Food " + ack._id)) // Send new if waiting dataSyncSelector.processChangedFoodsCompat() ret = Result.success(workDataOf("ProcessedData" to pair.toString())) @@ -66,7 +66,7 @@ class NSClientUpdateRemoveAckWorker( is PairTherapyEvent -> { val pair = ack.originalObject dataSyncSelector.confirmLastTherapyEventIdIfGreater(pair.updateRecordId) - rxBus.send(EventNSClientNewLog("DBUPDATE/DBREMOVE", "Acked TherapyEvent " + ack._id)) + rxBus.send(EventNSClientNewLog("DBUPDATE", "Acked TherapyEvent " + ack._id)) // Send new if waiting dataSyncSelector.processChangedTherapyEventsCompat() ret = Result.success(workDataOf("ProcessedData" to pair.toString())) @@ -75,7 +75,7 @@ class NSClientUpdateRemoveAckWorker( is PairBolus -> { val pair = ack.originalObject dataSyncSelector.confirmLastBolusIdIfGreater(pair.updateRecordId) - rxBus.send(EventNSClientNewLog("DBUPDATE/DBREMOVE", "Acked Bolus " + ack._id)) + rxBus.send(EventNSClientNewLog("DBUPDATE", "Acked Bolus " + ack._id)) // Send new if waiting dataSyncSelector.processChangedBolusesCompat() ret = Result.success(workDataOf("ProcessedData" to pair.toString())) @@ -84,7 +84,7 @@ class NSClientUpdateRemoveAckWorker( is PairCarbs -> { val pair = ack.originalObject dataSyncSelector.confirmLastCarbsIdIfGreater(pair.updateRecordId) - rxBus.send(EventNSClientNewLog("DBUPDATE/DBREMOVE", "Acked Carbs " + ack._id)) + rxBus.send(EventNSClientNewLog("DBUPDATE", "Acked Carbs " + ack._id)) // Send new if waiting dataSyncSelector.processChangedCarbsCompat() ret = Result.success(workDataOf("ProcessedData" to pair.toString())) @@ -93,7 +93,7 @@ class NSClientUpdateRemoveAckWorker( is PairBolusCalculatorResult -> { val pair = ack.originalObject dataSyncSelector.confirmLastBolusCalculatorResultsIdIfGreater(pair.updateRecordId) - rxBus.send(EventNSClientNewLog("DBUPDATE/DBREMOVE", "Acked BolusCalculatorResult " + ack._id)) + rxBus.send(EventNSClientNewLog("DBUPDATE", "Acked BolusCalculatorResult " + ack._id)) // Send new if waiting dataSyncSelector.processChangedBolusCalculatorResultsCompat() ret = Result.success(workDataOf("ProcessedData" to pair.toString())) @@ -102,7 +102,7 @@ class NSClientUpdateRemoveAckWorker( is PairTemporaryBasal -> { val pair = ack.originalObject dataSyncSelector.confirmLastTemporaryBasalIdIfGreater(pair.updateRecordId) - rxBus.send(EventNSClientNewLog("DBUPDATE/DBREMOVE", "Acked TemporaryBasal " + ack._id)) + rxBus.send(EventNSClientNewLog("DBUPDATE", "Acked TemporaryBasal " + ack._id)) // Send new if waiting dataSyncSelector.processChangedTemporaryBasalsCompat() ret = Result.success(workDataOf("ProcessedData" to pair.toString())) @@ -111,7 +111,7 @@ class NSClientUpdateRemoveAckWorker( is PairExtendedBolus -> { val pair = ack.originalObject dataSyncSelector.confirmLastExtendedBolusIdIfGreater(pair.updateRecordId) - rxBus.send(EventNSClientNewLog("DBUPDATE/DBREMOVE", "Acked ExtendedBolus " + ack._id)) + rxBus.send(EventNSClientNewLog("DBUPDATE", "Acked ExtendedBolus " + ack._id)) // Send new if waiting dataSyncSelector.processChangedExtendedBolusesCompat() ret = Result.success(workDataOf("ProcessedData" to pair.toString())) @@ -120,11 +120,20 @@ class NSClientUpdateRemoveAckWorker( is PairProfileSwitch -> { val pair = ack.originalObject dataSyncSelector.confirmLastProfileSwitchIdIfGreater(pair.updateRecordId) - rxBus.send(EventNSClientNewLog("DBUPDATE/DBREMOVE", "Acked ProfileSwitch " + ack._id)) + rxBus.send(EventNSClientNewLog("DBUPDATE", "Acked ProfileSwitch " + ack._id)) // Send new if waiting dataSyncSelector.processChangedProfileSwitchesCompat() ret = Result.success(workDataOf("ProcessedData" to pair.toString())) } + + is PairOfflineEvent -> { + val pair = ack.originalObject + dataSyncSelector.confirmLastOfflineEventIdIfGreater(pair.updateRecordId) + rxBus.send(EventNSClientNewLog("DBUPDATE", "Acked OfflineEvent" + ack._id)) + // Send new if waiting + dataSyncSelector.processChangedOfflineEventsCompat() + ret = Result.success(workDataOf("ProcessedData" to pair.toString())) + } } return ret } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NsClientReceiverDelegate.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NsClientReceiverDelegate.java deleted file mode 100644 index d54f48d5110..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NsClientReceiverDelegate.java +++ /dev/null @@ -1,117 +0,0 @@ -package info.nightscout.androidaps.plugins.general.nsclient; - -import java.util.Arrays; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.events.EventChargingState; -import info.nightscout.androidaps.events.EventNetworkChange; -import info.nightscout.androidaps.events.EventPreferenceChange; -import info.nightscout.androidaps.plugins.bus.RxBusWrapper; -import info.nightscout.androidaps.receivers.ReceiverStatusStore; -import info.nightscout.androidaps.utils.resources.ResourceHelper; -import info.nightscout.androidaps.utils.sharedPreferences.SP; - -@Singleton -class NsClientReceiverDelegate { - - private boolean allowedChargingState = true; - private boolean allowedNetworkState = true; - boolean allowed = true; - - private RxBusWrapper rxBus; - private ResourceHelper resourceHelper; - private SP sp; - private ReceiverStatusStore receiverStatusStore; - - @Inject - public NsClientReceiverDelegate( - RxBusWrapper rxBus, - ResourceHelper resourceHelper, - SP sp, - ReceiverStatusStore receiverStatusStore - ) { - this.rxBus = rxBus; - this.resourceHelper = resourceHelper; - this.sp = sp; - this.receiverStatusStore = receiverStatusStore; - } - - void grabReceiversState() { - - receiverStatusStore.updateNetworkStatus(); - } - - void onStatusEvent(EventPreferenceChange ev) { - if (ev.isChanged(resourceHelper, R.string.key_ns_wifionly) || - ev.isChanged(resourceHelper, R.string.key_ns_wifi_ssids) || - ev.isChanged(resourceHelper, R.string.key_ns_allowroaming) - ) { - receiverStatusStore.updateNetworkStatus(); - onStatusEvent(receiverStatusStore.getLastNetworkEvent()); - } else if (ev.isChanged(resourceHelper, R.string.key_ns_chargingonly)) { - receiverStatusStore.broadcastChargingState(); - } - } - - void onStatusEvent(final EventChargingState ev) { - boolean newChargingState = calculateStatus(ev); - - if (newChargingState != allowedChargingState) { - allowedChargingState = newChargingState; - processStateChange(); - } - } - - void onStatusEvent(final EventNetworkChange ev) { - boolean newNetworkState = calculateStatus(ev); - - if (newNetworkState != allowedNetworkState) { - allowedNetworkState = newNetworkState; - processStateChange(); - } - } - - private void processStateChange() { - boolean newAllowedState = allowedChargingState && allowedNetworkState; - if (newAllowedState != allowed) { - allowed = newAllowedState; - rxBus.send(new EventPreferenceChange(resourceHelper.gs(R.string.key_nsclientinternal_paused))); - } - } - - boolean calculateStatus(final EventChargingState ev) { - boolean chargingOnly = sp.getBoolean(R.string.key_ns_chargingonly, false); - boolean newAllowedState = true; - - if (!ev.isCharging() && chargingOnly) { - newAllowedState = false; - } - - return newAllowedState; - } - - boolean calculateStatus(final EventNetworkChange ev) { - boolean wifiOnly = sp.getBoolean(R.string.key_ns_wifionly, false); - String allowedSSIDstring = sp.getString(R.string.key_ns_wifi_ssids, ""); - String[] allowedSSIDs = allowedSSIDstring.split(";"); - if (allowedSSIDstring.isEmpty()) allowedSSIDs = new String[0]; - boolean allowRoaming = sp.getBoolean(R.string.key_ns_allowroaming, true); - - boolean newAllowedState = true; - - if (ev.getWifiConnected()) { - if (allowedSSIDs.length != 0 && !Arrays.asList(allowedSSIDs).contains(ev.getSsid())) { - newAllowedState = false; - } - } else { - if ((!allowRoaming && ev.getRoaming()) || wifiOnly) { - newAllowedState = false; - } - } - - return newAllowedState; - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NsClientReceiverDelegate.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NsClientReceiverDelegate.kt new file mode 100644 index 00000000000..fa67b25cda6 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NsClientReceiverDelegate.kt @@ -0,0 +1,91 @@ +package info.nightscout.androidaps.plugins.general.nsclient + +import info.nightscout.androidaps.R +import info.nightscout.androidaps.events.EventChargingState +import info.nightscout.androidaps.events.EventNetworkChange +import info.nightscout.androidaps.events.EventPreferenceChange +import info.nightscout.androidaps.plugins.bus.RxBusWrapper +import info.nightscout.androidaps.receivers.ReceiverStatusStore +import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.androidaps.utils.sharedPreferences.SP +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class NsClientReceiverDelegate @Inject constructor( + private val rxBus: RxBusWrapper, + private val resourceHelper: ResourceHelper, + private val sp: SP, + private val receiverStatusStore: ReceiverStatusStore +) { + + private var allowedChargingState = true + private var allowedNetworkState = true + var allowed = true + + fun grabReceiversState() { + receiverStatusStore.updateNetworkStatus() + } + + fun onStatusEvent(ev: EventPreferenceChange) { + if (ev.isChanged(resourceHelper, R.string.key_ns_wifionly) || + ev.isChanged(resourceHelper, R.string.key_ns_wifi_ssids) || + ev.isChanged(resourceHelper, R.string.key_ns_allowroaming)) { + receiverStatusStore.updateNetworkStatus() + onStatusEvent(receiverStatusStore.lastNetworkEvent) + } else if (ev.isChanged(resourceHelper, R.string.key_ns_chargingonly)) { + receiverStatusStore.broadcastChargingState() + } + } + + fun onStatusEvent(ev: EventChargingState) { + val newChargingState = calculateStatus(ev) + if (newChargingState != allowedChargingState) { + allowedChargingState = newChargingState + processStateChange() + } + } + + fun onStatusEvent(ev: EventNetworkChange?) { + val newNetworkState = calculateStatus(ev) + if (newNetworkState != allowedNetworkState) { + allowedNetworkState = newNetworkState + processStateChange() + } + } + + private fun processStateChange() { + val newAllowedState = allowedChargingState && allowedNetworkState + if (newAllowedState != allowed) { + allowed = newAllowedState + rxBus.send(EventPreferenceChange(resourceHelper.gs(R.string.key_nsclientinternal_paused))) + } + } + + fun calculateStatus(ev: EventChargingState): Boolean { + val chargingOnly = sp.getBoolean(R.string.key_ns_chargingonly, false) + var newAllowedState = true + if (!ev.isCharging && chargingOnly) { + newAllowedState = false + } + return newAllowedState + } + + fun calculateStatus(ev: EventNetworkChange?): Boolean { + val wifiOnly = sp.getBoolean(R.string.key_ns_wifionly, false) + val allowedSsidString = sp.getString(R.string.key_ns_wifi_ssids, "") + val allowedSSIDs: List = if (allowedSsidString.isEmpty()) List(0) { "" } else allowedSsidString.split(";") + val allowRoaming = sp.getBoolean(R.string.key_ns_allowroaming, true) + var newAllowedState = true + if (ev?.wifiConnected == true) { + if (allowedSSIDs.isNotEmpty() && !allowedSSIDs.contains(ev.ssid)) { + newAllowedState = false + } + } else { + if (!allowRoaming && ev?.roaming == true || wifiOnly) { + newAllowedState = false + } + } + return newAllowedState + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/UploadQueue.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/UploadQueue.java deleted file mode 100644 index f7669b182d3..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/UploadQueue.java +++ /dev/null @@ -1,124 +0,0 @@ -package info.nightscout.androidaps.plugins.general.nsclient; - -import android.content.Context; -import android.content.Intent; -import android.os.SystemClock; - -import com.j256.ormlite.dao.CloseableIterator; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.sql.SQLException; - -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.db.DatabaseHelper; -import info.nightscout.androidaps.db.DbRequest; -import info.nightscout.androidaps.interfaces.DatabaseHelperInterface; -import info.nightscout.androidaps.interfaces.UploadQueueAdminInterface; -import info.nightscout.androidaps.interfaces.UploadQueueInterface; -import info.nightscout.androidaps.logging.AAPSLogger; -import info.nightscout.androidaps.logging.LTag; -import info.nightscout.androidaps.plugins.bus.RxBusWrapper; -import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientResend; -import info.nightscout.androidaps.plugins.general.nsclient.services.NSClientService; -import info.nightscout.androidaps.utils.sharedPreferences.SP; - -/** - * Created by mike on 21.02.2016. - */ -public class UploadQueue implements UploadQueueAdminInterface { - private final AAPSLogger aapsLogger; - private final DatabaseHelperInterface databaseHelper; - private final Context context; - private final SP sp; - private final RxBusWrapper rxBus; - - public UploadQueue( - AAPSLogger aapsLogger, - DatabaseHelperInterface databaseHelper, - Context context, - SP sp, - RxBusWrapper rxBus - ) { - this.aapsLogger = aapsLogger; - this.databaseHelper = databaseHelper; - this.context = context; - this.sp = sp; - this.rxBus = rxBus; - } - - public String status() { - return "QUEUE: " + databaseHelper.size(DatabaseHelper.DATABASE_DBREQUESTS); - } - - @Override - public long size() { - return databaseHelper.size(DatabaseHelper.DATABASE_DBREQUESTS); - } - - private void startService() { - if (NSClientService.handler == null) { - context.startService(new Intent(context, NSClientService.class)); - SystemClock.sleep(2000); - } - } - - public void add(final DbRequest dbr) { - if (sp.getBoolean(R.string.key_ns_noupload, false)) return; - aapsLogger.debug(LTag.NSCLIENT, "Adding to queue: " + dbr.log()); - try { - databaseHelper.create(dbr); - } catch (Exception e) { - aapsLogger.error("Unhandled exception", e); - } - rxBus.send(new EventNSClientResend("newdata")); - } - - @Override public void clearQueue() { - startService(); - if (NSClientService.handler != null) { - NSClientService.handler.post(() -> { - aapsLogger.debug(LTag.NSCLIENT, "ClearQueue"); - databaseHelper.deleteAllDbRequests(); - aapsLogger.debug(LTag.NSCLIENT, status()); - }); - } - } - - @Override - public void removeByMongoId(final String action, final String _id) { - if (_id == null || _id.equals("")) - return; - startService(); - if (NSClientService.handler != null) { - NSClientService.handler.post(() -> { - databaseHelper.deleteDbRequestbyMongoId(action, _id); - aapsLogger.debug(LTag.NSCLIENT, "Removing " + _id + " from UploadQueue. " + status()); - }); - } - } - - @Override public String textList() { - String result = ""; - CloseableIterator iterator; - try { - iterator = databaseHelper.getDbRequestIterator(); - try { - while (iterator.hasNext()) { - DbRequest dbr = iterator.next(); - result += "
"; - result += dbr.action.toUpperCase() + " "; - result += dbr.collection + ": "; - result += dbr.data; - } - } finally { - iterator.close(); - } - } catch (SQLException e) { - aapsLogger.error("Unhandled exception", e); - } - return result; - } - -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/services/NSClientService.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/services/NSClientService.java deleted file mode 100644 index 078d56943eb..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/services/NSClientService.java +++ /dev/null @@ -1,913 +0,0 @@ -package info.nightscout.androidaps.plugins.general.nsclient.services; - -import android.content.Context; -import android.content.Intent; -import android.os.Binder; -import android.os.Bundle; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.IBinder; -import android.os.PowerManager; -import android.os.SystemClock; - -import androidx.work.OneTimeWorkRequest; - -import com.google.common.base.Charsets; -import com.google.common.hash.Hashing; -import com.j256.ormlite.dao.CloseableIterator; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.net.URISyntaxException; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; - -import javax.inject.Inject; - -import dagger.android.DaggerService; -import dagger.android.HasAndroidInjector; -import info.nightscout.androidaps.interfaces.Config; -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.database.AppRepository; -import info.nightscout.androidaps.db.DbRequest; -import info.nightscout.androidaps.events.EventAppExit; -import info.nightscout.androidaps.events.EventConfigBuilderChange; -import info.nightscout.androidaps.events.EventPreferenceChange; -import info.nightscout.androidaps.interfaces.DataSyncSelector; -import info.nightscout.androidaps.interfaces.DatabaseHelperInterface; -import info.nightscout.androidaps.interfaces.PluginType; -import info.nightscout.androidaps.interfaces.UploadQueueInterface; -import info.nightscout.androidaps.logging.AAPSLogger; -import info.nightscout.androidaps.logging.LTag; -import info.nightscout.androidaps.plugins.bus.RxBusWrapper; -import info.nightscout.androidaps.plugins.general.food.FoodPlugin; -import info.nightscout.androidaps.plugins.general.nsclient.NSClientAddAckWorker; -import info.nightscout.androidaps.plugins.general.nsclient.NSClientAddUpdateWorker; -import info.nightscout.androidaps.plugins.general.nsclient.NSClientMbgWorker; -import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin; -import info.nightscout.androidaps.plugins.general.nsclient.NSClientRemoveWorker; -import info.nightscout.androidaps.plugins.general.nsclient.NSClientUpdateRemoveAckWorker; -import info.nightscout.androidaps.plugins.general.nsclient.acks.NSAddAck; -import info.nightscout.androidaps.plugins.general.nsclient.acks.NSAuthAck; -import info.nightscout.androidaps.plugins.general.nsclient.acks.NSUpdateAck; -import info.nightscout.androidaps.plugins.general.nsclient.data.AlarmAck; -import info.nightscout.androidaps.plugins.general.nsclient.data.NSAlarm; -import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus; -import info.nightscout.androidaps.plugins.general.nsclient.data.NSSettingsStatus; -import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientNewLog; -import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart; -import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientStatus; -import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientUpdateGUI; -import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification; -import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; -import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; -import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationWithAction; -import info.nightscout.androidaps.plugins.profile.ns.NSProfilePlugin; -import info.nightscout.androidaps.plugins.source.NSClientSourcePlugin; -import info.nightscout.androidaps.receivers.DataWorker; -import info.nightscout.androidaps.services.Intents; -import info.nightscout.androidaps.utils.DateUtil; -import info.nightscout.androidaps.utils.FabricPrivacy; -import info.nightscout.androidaps.utils.JsonHelper; -import info.nightscout.androidaps.utils.T; -import info.nightscout.androidaps.utils.buildHelper.BuildHelper; -import info.nightscout.androidaps.utils.resources.ResourceHelper; -import info.nightscout.androidaps.utils.rx.AapsSchedulers; -import info.nightscout.androidaps.utils.sharedPreferences.SP; -import io.reactivex.disposables.CompositeDisposable; -import io.socket.client.IO; -import io.socket.client.Socket; -import io.socket.emitter.Emitter; - -public class NSClientService extends DaggerService { - @Inject HasAndroidInjector injector; - @Inject AAPSLogger aapsLogger; - @Inject AapsSchedulers aapsSchedulers; - @Inject NSSettingsStatus nsSettingsStatus; - @Inject NSDeviceStatus nsDeviceStatus; - @Inject DatabaseHelperInterface databaseHelper; - @Inject RxBusWrapper rxBus; - @Inject ResourceHelper resourceHelper; - @Inject SP sp; - @Inject FabricPrivacy fabricPrivacy; - @Inject NSClientPlugin nsClientPlugin; - @Inject BuildHelper buildHelper; - @Inject Config config; - @Inject DateUtil dateUtil; - @Inject UploadQueueInterface uploadQueue; - @Inject DataWorker dataWorker; - @Inject DataSyncSelector dataSyncSelector; - @Inject AppRepository repository; - - private final CompositeDisposable disposable = new CompositeDisposable(); - - static public PowerManager.WakeLock mWakeLock; - private final IBinder mBinder = new NSClientService.LocalBinder(); - - static public Handler handler; - - public static Socket mSocket; - public static boolean isConnected = false; - public static boolean hasWriteAuth = false; - private static Integer dataCounter = 0; - private static Integer connectCounter = 0; - - - private boolean nsEnabled = false; - static public String nsURL = ""; - private String nsAPISecret = ""; - private String nsDevice = ""; - private final Integer nsHours = 48; - - public long lastResendTime = 0; - public long lastAckTime = 0; - - public long latestDateInReceivedData = 0; - - private String nsAPIhashCode = ""; - - private final ArrayList reconnections = new ArrayList<>(); - private final int WATCHDOG_INTERVAL_MINUTES = 2; - private final int WATCHDOG_RECONNECT_IN = 15; - private final int WATCHDOG_MAX_CONNECTIONS = 5; - - public NSClientService() { - super(); - if (handler == null) { - HandlerThread handlerThread = new HandlerThread(NSClientService.class.getSimpleName() + "Handler"); - handlerThread.start(); - handler = new Handler(handlerThread.getLooper()); - } - } - - @Override - public void onCreate() { - super.onCreate(); - PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); - mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "AndroidAPS:NSClientService"); - mWakeLock.acquire(); - - initialize(); - - disposable.add(rxBus - .toObservable(EventConfigBuilderChange.class) - .observeOn(aapsSchedulers.getIo()) - .subscribe(event -> { - if (nsEnabled != nsClientPlugin.isEnabled(PluginType.GENERAL)) { - latestDateInReceivedData = 0; - destroy(); - initialize(); - } - }, fabricPrivacy::logException) - ); - disposable.add(rxBus - .toObservable(EventPreferenceChange.class) - .observeOn(aapsSchedulers.getIo()) - .subscribe(event -> { - if (event.isChanged(resourceHelper, R.string.key_nsclientinternal_url) || - event.isChanged(resourceHelper, R.string.key_nsclientinternal_api_secret) || - event.isChanged(resourceHelper, R.string.key_nsclientinternal_paused) - ) { - latestDateInReceivedData = 0; - destroy(); - initialize(); - } - }, fabricPrivacy::logException) - ); - disposable.add(rxBus - .toObservable(EventAppExit.class) - .observeOn(aapsSchedulers.getIo()) - .subscribe(event -> { - aapsLogger.debug(LTag.NSCLIENT, "EventAppExit received"); - destroy(); - stopSelf(); - }, fabricPrivacy::logException) - ); - disposable.add(rxBus - .toObservable(EventNSClientRestart.class) - .observeOn(aapsSchedulers.getIo()) - .subscribe(event -> { - latestDateInReceivedData = 0; - restart(); - }, fabricPrivacy::logException) - ); - disposable.add(rxBus - .toObservable(NSAuthAck.class) - .observeOn(aapsSchedulers.getIo()) - .subscribe(this::processAuthAck, fabricPrivacy::logException) - ); - disposable.add(rxBus - .toObservable(NSUpdateAck.class) - .observeOn(aapsSchedulers.getIo()) - .subscribe(this::processUpdateAck, fabricPrivacy::logException) - ); - disposable.add(rxBus - .toObservable(NSAddAck.class) - .observeOn(aapsSchedulers.getIo()) - .subscribe(this::processAddAck, fabricPrivacy::logException) - ); - } - - @Override - public void onDestroy() { - super.onDestroy(); - disposable.clear(); - if (mWakeLock.isHeld()) mWakeLock.release(); - } - - public void processAddAck(NSAddAck ack) { - lastAckTime = dateUtil.now(); - dataWorker.enqueue( - new OneTimeWorkRequest.Builder(NSClientAddAckWorker.class) - .setInputData(dataWorker.storeInputData(ack, null)) - .build()); - } - - public void processUpdateAck(NSUpdateAck ack) { - lastAckTime = dateUtil.now(); - dataWorker.enqueue( - new OneTimeWorkRequest.Builder(NSClientUpdateRemoveAckWorker.class) - .setInputData(dataWorker.storeInputData(ack, null)) - .build()); - } - - public void processAuthAck(NSAuthAck ack) { - String connectionStatus = "Authenticated ("; - if (ack.read) connectionStatus += "R"; - if (ack.write) connectionStatus += "W"; - if (ack.write_treatment) connectionStatus += "T"; - connectionStatus += ')'; - isConnected = true; - hasWriteAuth = ack.write && ack.write_treatment; - rxBus.send(new EventNSClientStatus(connectionStatus)); - rxBus.send(new EventNSClientNewLog("AUTH", connectionStatus)); - if (!ack.write) { - rxBus.send(new EventNSClientNewLog("ERROR", "Write permission not granted !!!!")); - } - if (!ack.write_treatment) { - rxBus.send(new EventNSClientNewLog("ERROR", "Write treatment permission not granted !!!!")); - } - if (!hasWriteAuth) { - Notification noperm = new Notification(Notification.NSCLIENT_NO_WRITE_PERMISSION, resourceHelper.gs(R.string.nowritepermission), Notification.URGENT); - rxBus.send(new EventNewNotification(noperm)); - } else { - rxBus.send(new EventDismissNotification(Notification.NSCLIENT_NO_WRITE_PERMISSION)); - } - } - - public class LocalBinder extends Binder { - public NSClientService getServiceInstance() { - return NSClientService.this; - } - - } - - @Override - public IBinder onBind(Intent intent) { - return mBinder; - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - - return START_STICKY; - } - - @SuppressWarnings("deprecation") - public void initialize() { - dataCounter = 0; - - readPreferences(); - - if (!nsAPISecret.equals("")) - nsAPIhashCode = Hashing.sha1().hashString(nsAPISecret, Charsets.UTF_8).toString(); - - rxBus.send(new EventNSClientStatus("Initializing")); - if (!nsClientPlugin.isAllowed()) { - rxBus.send(new EventNSClientNewLog("NSCLIENT", "not allowed")); - rxBus.send(new EventNSClientStatus("Not allowed")); - } else if (nsClientPlugin.paused) { - rxBus.send(new EventNSClientNewLog("NSCLIENT", "paused")); - rxBus.send(new EventNSClientStatus("Paused")); - } else if (!nsEnabled) { - rxBus.send(new EventNSClientNewLog("NSCLIENT", "disabled")); - rxBus.send(new EventNSClientStatus("Disabled")); - } else if (!nsURL.equals("") && (buildHelper.isEngineeringMode() || nsURL.toLowerCase().startsWith("https://"))) { - try { - rxBus.send(new EventNSClientStatus("Connecting ...")); - IO.Options opt = new IO.Options(); - opt.forceNew = true; - opt.reconnection = true; - mSocket = IO.socket(nsURL, opt); - mSocket.on(Socket.EVENT_CONNECT, onConnect); - mSocket.on(Socket.EVENT_DISCONNECT, onDisconnect); - mSocket.on(Socket.EVENT_ERROR, onError); - mSocket.on(Socket.EVENT_CONNECT_ERROR, onError); - mSocket.on(Socket.EVENT_CONNECT_TIMEOUT, onError); - mSocket.on(Socket.EVENT_PING, onPing); - rxBus.send(new EventNSClientNewLog("NSCLIENT", "do connect")); - mSocket.connect(); - mSocket.on("dataUpdate", onDataUpdate); - mSocket.on("announcement", onAnnouncement); - mSocket.on("alarm", onAlarm); - mSocket.on("urgent_alarm", onUrgentAlarm); - mSocket.on("clear_alarm", onClearAlarm); - } catch (URISyntaxException | RuntimeException e) { - rxBus.send(new EventNSClientNewLog("NSCLIENT", "Wrong URL syntax")); - rxBus.send(new EventNSClientStatus("Wrong URL syntax")); - } - } else if (nsURL.toLowerCase().startsWith("http://")) { - rxBus.send(new EventNSClientNewLog("NSCLIENT", "NS URL not encrypted")); - rxBus.send(new EventNSClientStatus("Not encrypted")); - } else { - rxBus.send(new EventNSClientNewLog("NSCLIENT", "No NS URL specified")); - rxBus.send(new EventNSClientStatus("Not configured")); - } - } - - private final Emitter.Listener onConnect = new Emitter.Listener() { - @Override - public void call(Object... args) { - connectCounter++; - String socketId = mSocket != null ? mSocket.id() : "NULL"; - rxBus.send(new EventNSClientNewLog("NSCLIENT", "connect #" + connectCounter + " event. ID: " + socketId)); - if (mSocket != null) - sendAuthMessage(new NSAuthAck(rxBus)); - watchdog(); - } - }; - - void watchdog() { - synchronized (reconnections) { - long now = dateUtil.now(); - reconnections.add(now); - for (int i = 0; i < reconnections.size(); i++) { - Long r = reconnections.get(i); - if (r < now - T.mins(WATCHDOG_INTERVAL_MINUTES).msecs()) { - reconnections.remove(r); - } - } - rxBus.send(new EventNSClientNewLog("WATCHDOG", "connections in last " + WATCHDOG_INTERVAL_MINUTES + " mins: " + reconnections.size() + "/" + WATCHDOG_MAX_CONNECTIONS)); - if (reconnections.size() >= WATCHDOG_MAX_CONNECTIONS) { - Notification n = new Notification(Notification.NS_MALFUNCTION, resourceHelper.gs(R.string.nsmalfunction), Notification.URGENT); - rxBus.send(new EventNewNotification(n)); - rxBus.send(new EventNSClientNewLog("WATCHDOG", "pausing for " + WATCHDOG_RECONNECT_IN + " mins")); - nsClientPlugin.pause(true); - rxBus.send(new EventNSClientUpdateGUI()); - new Thread(() -> { - SystemClock.sleep(T.mins(WATCHDOG_RECONNECT_IN).msecs()); - rxBus.send(new EventNSClientNewLog("WATCHDOG", "reenabling NSClient")); - nsClientPlugin.pause(false); - }).start(); - } - } - } - - private final Emitter.Listener onDisconnect = new Emitter.Listener() { - @Override - public void call(Object... args) { - aapsLogger.debug(LTag.NSCLIENT, "disconnect reason: {}", args); - rxBus.send(new EventNSClientNewLog("NSCLIENT", "disconnect event")); - } - }; - - public synchronized void destroy() { - if (mSocket != null) { - mSocket.off(Socket.EVENT_CONNECT); - mSocket.off(Socket.EVENT_DISCONNECT); - mSocket.off(Socket.EVENT_PING); - mSocket.off("dataUpdate"); - mSocket.off("announcement"); - mSocket.off("alarm"); - mSocket.off("urgent_alarm"); - mSocket.off("clear_alarm"); - - rxBus.send(new EventNSClientNewLog("NSCLIENT", "destroy")); - isConnected = false; - hasWriteAuth = false; - mSocket.disconnect(); - mSocket = null; - } - } - - - public void sendAuthMessage(NSAuthAck ack) { - JSONObject authMessage = new JSONObject(); - try { - authMessage.put("client", "Android_" + nsDevice); - authMessage.put("history", nsHours); - authMessage.put("status", true); // receive status - authMessage.put("from", latestDateInReceivedData); // send data newer than - authMessage.put("secret", nsAPIhashCode); - } catch (JSONException e) { - aapsLogger.error("Unhandled exception", e); - return; - } - rxBus.send(new EventNSClientNewLog("AUTH", "requesting auth")); - if (mSocket != null) - mSocket.emit("authorize", authMessage, ack); - } - - public void readPreferences() { - nsEnabled = nsClientPlugin.isEnabled(PluginType.GENERAL); - nsURL = sp.getString(R.string.key_nsclientinternal_url, ""); - nsAPISecret = sp.getString(R.string.key_nsclientinternal_api_secret, ""); - nsDevice = sp.getString("careportal_enteredby", ""); - } - - private final Emitter.Listener onError = new Emitter.Listener() { - @Override - public void call(final Object... args) { - String msg = "Unknown Error"; - if (args.length > 0 && args[0] != null) { - msg = args[0].toString(); - } - rxBus.send(new EventNSClientNewLog("ERROR", msg)); - } - }; - - private final Emitter.Listener onPing = new Emitter.Listener() { - @Override - public void call(final Object... args) { - rxBus.send(new EventNSClientNewLog("PING", "received")); - // send data if there is something waiting - resend("Ping received"); - } - }; - - private final Emitter.Listener onAnnouncement = new Emitter.Listener() { - /* - { - "level":0, - "title":"Announcement", - "message":"test", - "plugin":{"name":"treatmentnotify","label":"Treatment Notifications","pluginType":"notification","enabled":true}, - "group":"Announcement", - "isAnnouncement":true, - "key":"9ac46ad9a1dcda79dd87dae418fce0e7955c68da" - } - */ - @Override - public void call(final Object... args) { - JSONObject data; - try { - data = (JSONObject) args[0]; - handleAnnouncement(data); - } catch (Exception e) { - aapsLogger.error("Unhandled exception", e); - } - } - }; - - private final Emitter.Listener onAlarm = new Emitter.Listener() { - /* - { - "level":1, - "title":"Warning HIGH", - "message":"BG Now: 5 -0.2 → mmol\/L\nRaw BG: 4.8 mmol\/L Čistý\nBG 15m: 4.8 mmol\/L\nIOB: -0.02U\nCOB: 0g", - "eventName":"high", - "plugin":{"name":"simplealarms","label":"Simple Alarms","pluginType":"notification","enabled":true}, - "pushoverSound":"climb", - "debug":{"lastSGV":5,"thresholds":{"bgHigh":180,"bgTargetTop":75,"bgTargetBottom":72,"bgLow":70}}, - "group":"default", - "key":"simplealarms_1" - } - */ - @Override - public void call(final Object... args) { - JSONObject data; - try { - data = (JSONObject) args[0]; - handleAlarm(data); - } catch (Exception e) { - aapsLogger.error("Unhandled exception", e); - } - } - }; - - private final Emitter.Listener onUrgentAlarm = args -> { - JSONObject data; - try { - data = (JSONObject) args[0]; - handleUrgentAlarm(data); - } catch (Exception e) { - aapsLogger.error("Unhandled exception", e); - } - }; - - private final Emitter.Listener onClearAlarm = new Emitter.Listener() { - /* - { - "clear":true, - "title":"All Clear", - "message":"default - Urgent was ack'd", - "group":"default" - } - */ - @Override - public void call(final Object... args) { - JSONObject data; - try { - data = (JSONObject) args[0]; - rxBus.send(new EventNSClientNewLog("CLEARALARM", "received")); - rxBus.send(new EventDismissNotification(Notification.NS_ALARM)); - rxBus.send(new EventDismissNotification(Notification.NS_URGENT_ALARM)); - aapsLogger.debug(LTag.NSCLIENT, data.toString()); - } catch (Exception e) { - aapsLogger.error("Unhandled exception", e); - } - } - }; - - private final Emitter.Listener onDataUpdate = new Emitter.Listener() { - @Override - public void call(final Object... args) { - NSClientService.handler.post(() -> { - PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); - PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, - "AndroidAPS:NSClientService_onDataUpdate"); - wakeLock.acquire(); - try { - - JSONObject data = (JSONObject) args[0]; - boolean broadcastProfile = false; - try { - // delta means only increment/changes are comming - boolean isDelta = data.has("delta"); - boolean isFull = !isDelta; - rxBus.send(new EventNSClientNewLog("DATA", "Data packet #" + dataCounter++ + (isDelta ? " delta" : " full"))); - - if (data.has("status")) { - JSONObject status = data.getJSONObject("status"); - nsSettingsStatus.handleNewData(status); - } else if (!isDelta) { - rxBus.send(new EventNSClientNewLog("ERROR", "Unsupported Nightscout version !!!!")); - } - - if (data.has("profiles")) { - JSONArray profiles = data.getJSONArray("profiles"); - if (profiles.length() > 0) { - // take the newest - JSONObject profileStoreJson = (JSONObject) profiles.get(profiles.length() - 1); - rxBus.send(new EventNSClientNewLog("PROFILE", "profile received")); - dataWorker.enqueue( - new OneTimeWorkRequest.Builder(NSProfilePlugin.NSProfileWorker.class) - .setInputData(dataWorker.storeInputData(profileStoreJson, null)) - .build()); - - if (sp.getBoolean(R.string.key_nsclient_localbroadcasts, false)) { - Bundle bundle = new Bundle(); - bundle.putString("profile", profileStoreJson.toString()); - bundle.putBoolean("delta", isDelta); - Intent intent = new Intent(Intents.ACTION_NEW_PROFILE); - intent.putExtras(bundle); - intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); - sendBroadcast(intent); - } - } - } - - if (data.has("treatments")) { - JSONArray treatments = data.getJSONArray("treatments"); - JSONArray removedTreatments = new JSONArray(); - JSONArray addedOrUpdatedTreatments = new JSONArray(); - if (treatments.length() > 0) - rxBus.send(new EventNSClientNewLog("DATA", "received " + treatments.length() + " treatments")); - for (Integer index = 0; index < treatments.length(); index++) { - JSONObject jsonTreatment = treatments.getJSONObject(index); - String action = JsonHelper.safeGetStringAllowNull(jsonTreatment, "action", null); - long mills = JsonHelper.safeGetLong(jsonTreatment, "mills"); - - if (action == null) addedOrUpdatedTreatments.put(jsonTreatment); - else if (action.equals("update")) - addedOrUpdatedTreatments.put(jsonTreatment); - else if (action.equals("remove") && mills > dateUtil.now() - T.days(1).msecs()) // handle 1 day old deletions only - removedTreatments.put(jsonTreatment); - } - if (removedTreatments.length() > 0) { - dataWorker.enqueue( - new OneTimeWorkRequest.Builder(NSClientRemoveWorker.class) - .setInputData(dataWorker.storeInputData(removedTreatments, null)) - .build()); - - if (sp.getBoolean(R.string.key_nsclient_localbroadcasts, false)) { - Bundle bundle = new Bundle(); - bundle.putString("treatments", removedTreatments.toString()); - bundle.putBoolean("delta", isDelta); - Intent intent = new Intent(Intents.ACTION_REMOVED_TREATMENT); - intent.putExtras(bundle); - intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); - sendBroadcast(intent); - } - } - if (addedOrUpdatedTreatments.length() > 0) { - dataWorker.enqueue( - new OneTimeWorkRequest.Builder(NSClientAddUpdateWorker.class) - .setInputData(dataWorker.storeInputData(addedOrUpdatedTreatments, null)) - .build()); - - if (sp.getBoolean(R.string.key_nsclient_localbroadcasts, false)) { - List splitted = splitArray(addedOrUpdatedTreatments); - for (JSONArray part : splitted) { - Bundle bundle = new Bundle(); - bundle.putString("treatments", part.toString()); - bundle.putBoolean("delta", isDelta); - Intent intent = new Intent(Intents.ACTION_CHANGED_TREATMENT); - intent.putExtras(bundle); - intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); - sendBroadcast(intent); - } - } - } - } - if (data.has("devicestatus")) { - JSONArray devicestatuses = data.getJSONArray("devicestatus"); - if (devicestatuses.length() > 0) { - rxBus.send(new EventNSClientNewLog("DATA", "received " + devicestatuses.length() + " device statuses")); - nsDeviceStatus.handleNewData(devicestatuses); - } - } - if (data.has("food")) { - JSONArray foods = data.getJSONArray("food"); - if (foods.length() > 0) - rxBus.send(new EventNSClientNewLog("DATA", "received " + foods.length() + " foods")); - dataWorker.enqueue( - new OneTimeWorkRequest.Builder(FoodPlugin.FoodWorker.class) - .setInputData(dataWorker.storeInputData(foods, null)) - .build()); - } - //noinspection SpellCheckingInspection - if (data.has("mbgs")) { - JSONArray mbgArray = data.getJSONArray("mbgs"); - if (mbgArray.length() > 0) - rxBus.send(new EventNSClientNewLog("DATA", "received " + mbgArray.length() + " mbgs")); - dataWorker.enqueue( - new OneTimeWorkRequest.Builder(NSClientMbgWorker.class) - .setInputData(dataWorker.storeInputData(mbgArray, null)) - .build()); - } - if (data.has("cals")) { - JSONArray cals = data.getJSONArray("cals"); - if (cals.length() > 0) - rxBus.send(new EventNSClientNewLog("DATA", "received " + cals.length() + " cals")); - // Calibrations ignored - } - if (data.has("sgvs")) { - JSONArray sgvs = data.getJSONArray("sgvs"); - if (sgvs.length() > 0) - rxBus.send(new EventNSClientNewLog("DATA", "received " + sgvs.length() + " sgvs")); - - dataWorker.enqueue(new OneTimeWorkRequest.Builder(NSClientSourcePlugin.NSClientSourceWorker.class) - .setInputData(dataWorker.storeInputData(sgvs, null)) - .build()); - - List splitted = splitArray(sgvs); - if (sp.getBoolean(R.string.key_nsclient_localbroadcasts, false)) { - for (JSONArray part : splitted) { - Bundle bundle = new Bundle(); - bundle.putString("sgvs", part.toString()); - bundle.putBoolean("delta", isDelta); - Intent intent = new Intent(Intents.ACTION_NEW_SGV); - intent.putExtras(bundle); - intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); - sendBroadcast(intent); - } - } - } - rxBus.send(new EventNSClientNewLog("LAST", dateUtil.dateAndTimeString(latestDateInReceivedData))); - } catch (JSONException e) { - aapsLogger.error("Unhandled exception", e); - } - //rxBus.send(new EventNSClientNewLog("NSCLIENT", "onDataUpdate end"); - } finally { - if (wakeLock.isHeld()) wakeLock.release(); - } - }); - } - }; - - public void dbUpdate(DbRequest dbr, NSUpdateAck ack) { - try { - if (!isConnected || !hasWriteAuth) return; - JSONObject message = new JSONObject(); - message.put("collection", dbr.collection); - message.put("_id", dbr._id); - message.put("data", new JSONObject(dbr.data)); - mSocket.emit("dbUpdate", message, ack); - rxBus.send(new EventNSClientNewLog("DBUPDATE " + dbr.collection, "Sent " + dbr._id)); - } catch (JSONException e) { - aapsLogger.error("Unhandled exception", e); - } - } - - public void dbUpdate(String collection, String _id, JSONObject data, Object originalObject) { - try { - if (!isConnected || !hasWriteAuth) return; - JSONObject message = new JSONObject(); - message.put("collection", collection); - message.put("_id", _id); - message.put("data", data); - mSocket.emit("dbUpdate", message, new NSUpdateAck("dbUpdate", _id, aapsLogger, rxBus, originalObject)); - rxBus.send(new EventNSClientNewLog("DBUPDATE " + collection, "Sent " + originalObject.getClass().getSimpleName() + " " + _id)); - } catch (JSONException e) { - aapsLogger.error("Unhandled exception", e); - } - } - - public void dbRemove(DbRequest dbr, NSUpdateAck ack) { - try { - if (!isConnected || !hasWriteAuth) return; - JSONObject message = new JSONObject(); - message.put("collection", dbr.collection); - message.put("_id", dbr._id); - mSocket.emit("dbRemove", message, ack); - rxBus.send(new EventNSClientNewLog("DBREMOVE " + dbr.collection, "Sent " + dbr._id)); - } catch (JSONException e) { - aapsLogger.error("Unhandled exception", e); - } - } - - public void dbRemove(String collection, String _id, Object originalObject) { - try { - if (!isConnected || !hasWriteAuth) return; - JSONObject message = new JSONObject(); - message.put("collection", collection); - message.put("_id", _id); - mSocket.emit("dbRemove", message, new NSUpdateAck("dbRemove", _id, aapsLogger, rxBus, originalObject)); - rxBus.send(new EventNSClientNewLog("DBREMOVE " + collection, "Sent " + originalObject.getClass().getSimpleName() + " " + _id)); - } catch (JSONException e) { - aapsLogger.error("Unhandled exception", e); - } - } - - public void dbAdd(DbRequest dbr, NSAddAck ack) { - try { - if (!isConnected || !hasWriteAuth) return; - JSONObject message = new JSONObject(); - message.put("collection", dbr.collection); - message.put("data", new JSONObject(dbr.data)); - mSocket.emit("dbAdd", message, ack); - rxBus.send(new EventNSClientNewLog("DBADD " + dbr.collection, "Sent " + dbr.nsClientID)); - } catch (JSONException e) { - aapsLogger.error("Unhandled exception", e); - } - } - - public void dbAdd(String collection, JSONObject data, Object originalObject) { - try { - if (!isConnected || !hasWriteAuth) return; - JSONObject message = new JSONObject(); - message.put("collection", collection); - message.put("data", data); - mSocket.emit("dbAdd", message, new NSAddAck(aapsLogger, rxBus, originalObject)); - rxBus.send(new EventNSClientNewLog("DBADD " + collection, "Sent " + originalObject.getClass().getSimpleName() + " " + data)); - } catch (JSONException e) { - aapsLogger.error("Unhandled exception", e); - } - } - - public void sendAlarmAck(AlarmAck alarmAck) { - if (!isConnected || !hasWriteAuth) return; - mSocket.emit("ack", alarmAck.level, alarmAck.group, alarmAck.silenceTime); - rxBus.send(new EventNSClientNewLog("ALARMACK ", alarmAck.level + " " + alarmAck.group + " " + alarmAck.silenceTime)); - } - - public void resend(final String reason) { - if (!isConnected || !hasWriteAuth) return; - - handler.post(() -> { - if (mSocket == null || !mSocket.connected()) return; - - rxBus.send(new EventNSClientNewLog("QUEUE", "Resend started: " + reason)); - - if (lastAckTime > System.currentTimeMillis() - 10 * 1000L) { - aapsLogger.debug(LTag.NSCLIENT, "Skipping resend by lastAckTime: " + ((System.currentTimeMillis() - lastAckTime) / 1000L) + " sec"); - return; - } - - dataSyncSelector.processChangedBolusesCompat(); - dataSyncSelector.processChangedCarbsCompat(); - dataSyncSelector.processChangedBolusCalculatorResultsCompat(); - dataSyncSelector.processChangedTemporaryBasalsCompat(); - dataSyncSelector.processChangedExtendedBolusesCompat(); - dataSyncSelector.processChangedProfileSwitchesCompat(); - dataSyncSelector.processChangedGlucoseValuesCompat(); - dataSyncSelector.processChangedTempTargetsCompat(); - dataSyncSelector.processChangedFoodsCompat(); - dataSyncSelector.processChangedTherapyEventsCompat(); - dataSyncSelector.processChangedDeviceStatusesCompat(); - - if (uploadQueue.size() == 0) - return; - - if (lastResendTime > System.currentTimeMillis() - 10 * 1000L) { - aapsLogger.debug(LTag.NSCLIENT, "Skipping resend by lastResendTime: " + ((System.currentTimeMillis() - lastResendTime) / 1000L) + " sec"); - return; - } - lastResendTime = System.currentTimeMillis(); - - CloseableIterator iterator; - int maxcount = 30; - try { - iterator = databaseHelper.getDbRequestIterator(); - try { - while (iterator.hasNext() && maxcount > 0) { - DbRequest dbr = iterator.next(); - if (dbr.action.equals("dbAdd")) { - NSAddAck addAck = new NSAddAck(aapsLogger, rxBus, null); - dbAdd(dbr, addAck); - } else if (dbr.action.equals("dbRemove")) { - NSUpdateAck removeAck = new NSUpdateAck("dbRemove", dbr._id, aapsLogger, rxBus, null); - dbRemove(dbr, removeAck); - } else if (dbr.action.equals("dbUpdate")) { - NSUpdateAck updateAck = new NSUpdateAck("dbUpdate", dbr._id, aapsLogger, rxBus, null); - dbUpdate(dbr, updateAck); - } - maxcount--; - } - } finally { - iterator.close(); - } - } catch (SQLException e) { - aapsLogger.error("Unhandled exception", e); - } - - rxBus.send(new EventNSClientNewLog("QUEUE", "Resend ended: " + reason)); - }); - } - - public void restart() { - destroy(); - initialize(); - } - - private void handleAnnouncement(JSONObject announcement) { - boolean defaultVal = config.getNSCLIENT(); - if (sp.getBoolean(R.string.key_ns_announcements, defaultVal)) { - NSAlarm nsAlarm = new NSAlarm(announcement); - Notification notification = new NotificationWithAction(injector, nsAlarm); - rxBus.send(new EventNewNotification(notification)); - rxBus.send(new EventNSClientNewLog("ANNOUNCEMENT", JsonHelper.safeGetString(announcement, "message", "received"))); - aapsLogger.debug(LTag.NSCLIENT, announcement.toString()); - } - } - - private void handleAlarm(JSONObject alarm) { - boolean defaultVal = config.getNSCLIENT(); - if (sp.getBoolean(R.string.key_ns_alarms, defaultVal)) { - long snoozedTo = sp.getLong(R.string.key_snoozedTo, 0L); - if (snoozedTo == 0L || System.currentTimeMillis() > snoozedTo) { - NSAlarm nsAlarm = new NSAlarm(alarm); - Notification notification = new NotificationWithAction(injector, nsAlarm); - rxBus.send(new EventNewNotification(notification)); - } - rxBus.send(new EventNSClientNewLog("ALARM", JsonHelper.safeGetString(alarm, "message", "received"))); - aapsLogger.debug(LTag.NSCLIENT, alarm.toString()); - } - } - - private void handleUrgentAlarm(JSONObject alarm) { - boolean defaultVal = config.getNSCLIENT(); - if (sp.getBoolean(R.string.key_ns_alarms, defaultVal)) { - long snoozedTo = sp.getLong(R.string.key_snoozedTo, 0L); - if (snoozedTo == 0L || System.currentTimeMillis() > snoozedTo) { - NSAlarm nsAlarm = new NSAlarm(alarm); - Notification notification = new NotificationWithAction(injector, nsAlarm); - rxBus.send(new EventNewNotification(notification)); - } - rxBus.send(new EventNSClientNewLog("URGENTALARM", JsonHelper.safeGetString(alarm, "message", "received"))); - aapsLogger.debug(LTag.NSCLIENT, alarm.toString()); - } - } - - public List splitArray(JSONArray array) { - List ret = new ArrayList<>(); - try { - int size = array.length(); - int count = 0; - JSONArray newarr = null; - for (int i = 0; i < size; i++) { - if (count == 0) { - if (newarr != null) { - ret.add(newarr); - } - newarr = new JSONArray(); - count = 20; - } - newarr.put(array.get(i)); - --count; - } - if (newarr != null && newarr.length() > 0) { - ret.add(newarr); - } - } catch (JSONException e) { - aapsLogger.error("Unhandled exception", e); - ret = new ArrayList<>(); - ret.add(array); - } - return ret; - } -} \ No newline at end of file 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 new file mode 100644 index 00000000000..6510abb76ad --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/services/NSClientService.kt @@ -0,0 +1,731 @@ +package info.nightscout.androidaps.plugins.general.nsclient.services + +import android.annotation.SuppressLint +import android.content.Context +import android.content.Intent +import android.os.* +import androidx.work.OneTimeWorkRequest +import com.google.common.base.Charsets +import com.google.common.hash.Hashing +import dagger.android.DaggerService +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.R +import info.nightscout.androidaps.database.AppRepository +import info.nightscout.androidaps.events.EventAppExit +import info.nightscout.androidaps.events.EventConfigBuilderChange +import info.nightscout.androidaps.events.EventPreferenceChange +import info.nightscout.androidaps.interfaces.Config +import info.nightscout.androidaps.interfaces.DataSyncSelector +import info.nightscout.androidaps.interfaces.DatabaseHelperInterface +import info.nightscout.androidaps.interfaces.PluginType +import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.plugins.bus.RxBusWrapper +import info.nightscout.androidaps.plugins.general.food.FoodPlugin.FoodWorker +import info.nightscout.androidaps.plugins.general.nsclient.* +import info.nightscout.androidaps.plugins.general.nsclient.acks.NSAddAck +import info.nightscout.androidaps.plugins.general.nsclient.acks.NSAuthAck +import info.nightscout.androidaps.plugins.general.nsclient.acks.NSUpdateAck +import info.nightscout.androidaps.plugins.general.nsclient.data.AlarmAck +import info.nightscout.androidaps.plugins.general.nsclient.data.NSAlarm +import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus +import info.nightscout.androidaps.plugins.general.nsclient.data.NSSettingsStatus +import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientNewLog +import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart +import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientStatus +import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientUpdateGUI +import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification +import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification +import info.nightscout.androidaps.plugins.general.overview.notifications.Notification +import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationWithAction +import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin +import info.nightscout.androidaps.plugins.source.NSClientSourcePlugin.NSClientSourceWorker +import info.nightscout.androidaps.receivers.DataWorker +import info.nightscout.androidaps.services.Intents +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.FabricPrivacy +import info.nightscout.androidaps.utils.JsonHelper.safeGetLong +import info.nightscout.androidaps.utils.JsonHelper.safeGetString +import info.nightscout.androidaps.utils.JsonHelper.safeGetStringAllowNull +import info.nightscout.androidaps.utils.T.Companion.days +import info.nightscout.androidaps.utils.T.Companion.mins +import info.nightscout.androidaps.utils.buildHelper.BuildHelper +import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.androidaps.utils.rx.AapsSchedulers +import info.nightscout.androidaps.utils.sharedPreferences.SP +import io.reactivex.disposables.CompositeDisposable +import io.socket.client.IO +import io.socket.client.Socket +import io.socket.emitter.Emitter +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject +import java.net.URISyntaxException +import java.util.* +import javax.inject.Inject + +class NSClientService : DaggerService() { + + @Inject lateinit var injector: HasAndroidInjector + @Inject lateinit var aapsLogger: AAPSLogger + @Inject lateinit var aapsSchedulers: AapsSchedulers + @Inject lateinit var nsSettingsStatus: NSSettingsStatus + @Inject lateinit var nsDeviceStatus: NSDeviceStatus + @Inject lateinit var databaseHelper: DatabaseHelperInterface + @Inject lateinit var rxBus: RxBusWrapper + @Inject lateinit var resourceHelper: ResourceHelper + @Inject lateinit var sp: SP + @Inject lateinit var fabricPrivacy: FabricPrivacy + @Inject lateinit var nsClientPlugin: NSClientPlugin + @Inject lateinit var buildHelper: BuildHelper + @Inject lateinit var config: Config + @Inject lateinit var dateUtil: DateUtil + @Inject lateinit var dataWorker: DataWorker + @Inject lateinit var dataSyncSelector: DataSyncSelector + @Inject lateinit var repository: AppRepository + + companion object { + + private const val WATCHDOG_INTERVAL_MINUTES = 2 + private const val WATCHDOG_RECONNECT_IN = 15 + private const val WATCHDOG_MAX_CONNECTIONS = 5 + } + + private val disposable = CompositeDisposable() + + private var wakeLock: PowerManager.WakeLock? = null + private val binder: IBinder = LocalBinder() + private var handler: Handler? = null + private var socket: Socket? = null + private var dataCounter = 0 + private var connectCounter = 0 + private var nsEnabled = false + private var nsAPISecret = "" + private var nsDevice = "" + private val nsHours = 48 + private var lastAckTime: Long = 0 + private var nsApiHashCode = "" + private val reconnections = ArrayList() + + var isConnected = false + var hasWriteAuth = false + var nsURL = "" + var latestDateInReceivedData: Long = 0 + + @SuppressLint("WakelockTimeout") + @kotlin.ExperimentalStdlibApi + override fun onCreate() { + super.onCreate() + wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "AndroidAPS:NSClientService") + wakeLock?.acquire() + initialize() + disposable.add(rxBus + .toObservable(EventConfigBuilderChange::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ + if (nsEnabled != nsClientPlugin.isEnabled(PluginType.GENERAL)) { + latestDateInReceivedData = 0 + destroy() + initialize() + } + }, fabricPrivacy::logException) + ) + disposable.add(rxBus + .toObservable(EventPreferenceChange::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ event: EventPreferenceChange -> + if (event.isChanged(resourceHelper, R.string.key_nsclientinternal_url) || + event.isChanged(resourceHelper, R.string.key_nsclientinternal_api_secret) || + event.isChanged(resourceHelper, R.string.key_nsclientinternal_paused)) { + latestDateInReceivedData = 0 + destroy() + initialize() + } + }, fabricPrivacy::logException) + ) + disposable.add(rxBus + .toObservable(EventAppExit::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ + aapsLogger.debug(LTag.NSCLIENT, "EventAppExit received") + destroy() + stopSelf() + }, fabricPrivacy::logException) + ) + disposable.add(rxBus + .toObservable(EventNSClientRestart::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ + latestDateInReceivedData = 0 + restart() + }, fabricPrivacy::logException) + ) + disposable.add(rxBus + .toObservable(NSAuthAck::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ ack -> processAuthAck(ack) }, fabricPrivacy::logException) + ) + disposable.add(rxBus + .toObservable(NSUpdateAck::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ ack -> processUpdateAck(ack) }, fabricPrivacy::logException) + ) + disposable.add(rxBus + .toObservable(NSAddAck::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ ack -> processAddAck(ack) }, fabricPrivacy::logException) + ) + } + + override fun onDestroy() { + super.onDestroy() + disposable.clear() + if (wakeLock?.isHeld == true) wakeLock?.release() + } + + private fun processAddAck(ack: NSAddAck) { + lastAckTime = dateUtil.now() + dataWorker.enqueue( + OneTimeWorkRequest.Builder(NSClientAddAckWorker::class.java) + .setInputData(dataWorker.storeInputData(ack, null)) + .build()) + } + + private fun processUpdateAck(ack: NSUpdateAck) { + lastAckTime = dateUtil.now() + dataWorker.enqueue( + OneTimeWorkRequest.Builder(NSClientUpdateRemoveAckWorker::class.java) + .setInputData(dataWorker.storeInputData(ack, null)) + .build()) + } + + fun processAuthAck(ack: NSAuthAck) { + var connectionStatus = "Authenticated (" + if (ack.read) connectionStatus += "R" + if (ack.write) connectionStatus += "W" + if (ack.write_treatment) connectionStatus += "T" + connectionStatus += ')' + isConnected = true + hasWriteAuth = ack.write && ack.write_treatment + rxBus.send(EventNSClientStatus(connectionStatus)) + rxBus.send(EventNSClientNewLog("AUTH", connectionStatus)) + if (!ack.write) { + rxBus.send(EventNSClientNewLog("ERROR", "Write permission not granted ")) + } + if (!ack.write_treatment) { + rxBus.send(EventNSClientNewLog("ERROR", "Write treatment permission not granted ")) + } + if (!hasWriteAuth) { + val noWritePerm = Notification(Notification.NSCLIENT_NO_WRITE_PERMISSION, resourceHelper.gs(R.string.nowritepermission), Notification.URGENT) + rxBus.send(EventNewNotification(noWritePerm)) + } else { + rxBus.send(EventDismissNotification(Notification.NSCLIENT_NO_WRITE_PERMISSION)) + } + } + + inner class LocalBinder : Binder() { + + val serviceInstance: NSClientService + get() = this@NSClientService + } + + override fun onBind(intent: Intent): IBinder { + return binder + } + + override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { + return START_STICKY + } + + @kotlin.ExperimentalStdlibApi + fun initialize() { + dataCounter = 0 + readPreferences() + @Suppress("UnstableApiUsage", "DEPRECATION") + if (nsAPISecret != "") nsApiHashCode = Hashing.sha1().hashString(nsAPISecret, Charsets.UTF_8).toString() + rxBus.send(EventNSClientStatus("Initializing")) + if (!nsClientPlugin.isAllowed) { + rxBus.send(EventNSClientNewLog("NSCLIENT", "not allowed")) + rxBus.send(EventNSClientStatus("Not allowed")) + } else if (nsClientPlugin.paused) { + rxBus.send(EventNSClientNewLog("NSCLIENT", "paused")) + rxBus.send(EventNSClientStatus("Paused")) + } else if (!nsEnabled) { + rxBus.send(EventNSClientNewLog("NSCLIENT", "disabled")) + rxBus.send(EventNSClientStatus("Disabled")) + } else if (nsURL != "" && (buildHelper.isEngineeringMode() || nsURL.lowercase(Locale.getDefault()).startsWith("https://"))) { + try { + rxBus.send(EventNSClientStatus("Connecting ...")) + val opt = IO.Options() + opt.forceNew = true + opt.reconnection = true + socket = IO.socket(nsURL, opt).also { socket -> + socket.on(Socket.EVENT_CONNECT, onConnect) + socket.on(Socket.EVENT_DISCONNECT, onDisconnect) + socket.on(Socket.EVENT_ERROR, onError) + socket.on(Socket.EVENT_CONNECT_ERROR, onError) + socket.on(Socket.EVENT_CONNECT_TIMEOUT, onError) + socket.on(Socket.EVENT_PING, onPing) + rxBus.send(EventNSClientNewLog("NSCLIENT", "do connect")) + socket.connect() + socket.on("dataUpdate", onDataUpdate) + socket.on("announcement", onAnnouncement) + socket.on("alarm", onAlarm) + socket.on("urgent_alarm", onUrgentAlarm) + socket.on("clear_alarm", onClearAlarm) + } + } catch (e: URISyntaxException) { + rxBus.send(EventNSClientNewLog("NSCLIENT", "Wrong URL syntax")) + rxBus.send(EventNSClientStatus("Wrong URL syntax")) + } catch (e: RuntimeException) { + rxBus.send(EventNSClientNewLog("NSCLIENT", "Wrong URL syntax")) + rxBus.send(EventNSClientStatus("Wrong URL syntax")) + } + } else if (nsURL.lowercase(Locale.getDefault()).startsWith("http://")) { + rxBus.send(EventNSClientNewLog("NSCLIENT", "NS URL not encrypted")) + rxBus.send(EventNSClientStatus("Not encrypted")) + } else { + rxBus.send(EventNSClientNewLog("NSCLIENT", "No NS URL specified")) + rxBus.send(EventNSClientStatus("Not configured")) + } + } + + private val onConnect = Emitter.Listener { + connectCounter++ + val socketId = socket?.id() ?: "NULL" + rxBus.send(EventNSClientNewLog("NSCLIENT", "connect #$connectCounter event. ID: $socketId")) + if (socket != null) sendAuthMessage(NSAuthAck(rxBus)) + watchdog() + } + + private fun watchdog() { + synchronized(reconnections) { + val now = dateUtil.now() + reconnections.add(now) + for (r in reconnections.reversed()) { + if (r < now - mins(WATCHDOG_INTERVAL_MINUTES.toLong()).msecs()) { + reconnections.remove(r) + } + } + rxBus.send(EventNSClientNewLog("WATCHDOG", "connections in last " + WATCHDOG_INTERVAL_MINUTES + " minutes: " + reconnections.size + "/" + WATCHDOG_MAX_CONNECTIONS)) + if (reconnections.size >= WATCHDOG_MAX_CONNECTIONS) { + val n = Notification(Notification.NS_MALFUNCTION, resourceHelper.gs(R.string.nsmalfunction), Notification.URGENT) + rxBus.send(EventNewNotification(n)) + rxBus.send(EventNSClientNewLog("WATCHDOG", "pausing for $WATCHDOG_RECONNECT_IN minutes")) + nsClientPlugin.pause(true) + rxBus.send(EventNSClientUpdateGUI()) + Thread { + SystemClock.sleep(mins(WATCHDOG_RECONNECT_IN.toLong()).msecs()) + rxBus.send(EventNSClientNewLog("WATCHDOG", "re-enabling NSClient")) + nsClientPlugin.pause(false) + }.start() + } + } + } + + private val onDisconnect = Emitter.Listener { args -> + aapsLogger.debug(LTag.NSCLIENT, "disconnect reason: {}", *args) + rxBus.send(EventNSClientNewLog("NSCLIENT", "disconnect event")) + } + + @Synchronized fun destroy() { + socket?.off(Socket.EVENT_CONNECT) + socket?.off(Socket.EVENT_DISCONNECT) + socket?.off(Socket.EVENT_PING) + socket?.off("dataUpdate") + socket?.off("announcement") + socket?.off("alarm") + socket?.off("urgent_alarm") + socket?.off("clear_alarm") + rxBus.send(EventNSClientNewLog("NSCLIENT", "destroy")) + isConnected = false + hasWriteAuth = false + socket?.disconnect() + socket = null + } + + private fun sendAuthMessage(ack: NSAuthAck?) { + val authMessage = JSONObject() + try { + authMessage.put("client", "Android_$nsDevice") + authMessage.put("history", nsHours) + authMessage.put("status", true) // receive status + authMessage.put("from", latestDateInReceivedData) // send data newer than + authMessage.put("secret", nsApiHashCode) + } catch (e: JSONException) { + aapsLogger.error("Unhandled exception", e) + return + } + rxBus.send(EventNSClientNewLog("AUTH", "requesting auth")) + socket?.emit("authorize", authMessage, ack) + } + + fun readPreferences() { + nsEnabled = nsClientPlugin.isEnabled(PluginType.GENERAL) + nsURL = sp.getString(R.string.key_nsclientinternal_url, "") + nsAPISecret = sp.getString(R.string.key_nsclientinternal_api_secret, "") + nsDevice = sp.getString("careportal_enteredby", "") + } + + private val onError = Emitter.Listener { args -> + var msg = "Unknown Error" + if (args.isNotEmpty() && args[0] != null) { + msg = args[0].toString() + } + rxBus.send(EventNSClientNewLog("ERROR", msg)) + } + private val onPing = Emitter.Listener { + rxBus.send(EventNSClientNewLog("PING", "received")) + // send data if there is something waiting + resend("Ping received") + } + private val onAnnouncement = Emitter.Listener { args -> + + /* + { + "level":0, + "title":"Announcement", + "message":"test", + "plugin":{"name":"treatmentnotify","label":"Treatment Notifications","pluginType":"notification","enabled":true}, + "group":"Announcement", + "isAnnouncement":true, + "key":"9ac46ad9a1dcda79dd87dae418fce0e7955c68da" + } + */ + val data: JSONObject + try { + data = args[0] as JSONObject + handleAnnouncement(data) + } catch (e: Exception) { + aapsLogger.error("Unhandled exception", e) + } + } + private val onAlarm = Emitter.Listener { args -> + + /* + { + "level":1, + "title":"Warning HIGH", + "message":"BG Now: 5 -0.2 → mmol\/L\nRaw BG: 4.8 mmol\/L Čistý\nBG 15m: 4.8 mmol\/L\nIOB: -0.02U\nCOB: 0g", + "eventName":"high", + "plugin":{"name":"simplealarms","label":"Simple Alarms","pluginType":"notification","enabled":true}, + "pushoverSound":"climb", + "debug":{"lastSGV":5,"thresholds":{"bgHigh":180,"bgTargetTop":75,"bgTargetBottom":72,"bgLow":70}}, + "group":"default", + "key":"simplealarms_1" + } + */ + val data: JSONObject + try { + data = args[0] as JSONObject + handleAlarm(data) + } catch (e: Exception) { + aapsLogger.error("Unhandled exception", e) + } + } + private val onUrgentAlarm = Emitter.Listener { args: Array -> + val data: JSONObject + try { + data = args[0] as JSONObject + handleUrgentAlarm(data) + } catch (e: Exception) { + aapsLogger.error("Unhandled exception", e) + } + } + private val onClearAlarm = Emitter.Listener { args -> + + /* + { + "clear":true, + "title":"All Clear", + "message":"default - Urgent was ack'd", + "group":"default" + } + */ + val data: JSONObject + try { + data = args[0] as JSONObject + rxBus.send(EventNSClientNewLog("CLEARALARM", "received")) + rxBus.send(EventDismissNotification(Notification.NS_ALARM)) + rxBus.send(EventDismissNotification(Notification.NS_URGENT_ALARM)) + aapsLogger.debug(LTag.NSCLIENT, data.toString()) + } catch (e: Exception) { + aapsLogger.error("Unhandled exception", e) + } + } + private val onDataUpdate = Emitter.Listener { args -> + handler?.post { + // val powerManager = getSystemService(POWER_SERVICE) as PowerManager + // val wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + // "AndroidAPS:NSClientService_onDataUpdate") + // wakeLock.acquire(3000) + try { + val data = args[0] as JSONObject + try { + // delta means only increment/changes are coming + val isDelta = data.has("delta") + rxBus.send(EventNSClientNewLog("DATA", "Data packet #" + dataCounter++ + if (isDelta) " delta" else " full")) + if (data.has("status")) { + val status = data.getJSONObject("status") + nsSettingsStatus.handleNewData(status) + } else if (!isDelta) { + rxBus.send(EventNSClientNewLog("ERROR", "Unsupported Nightscout version ")) + } + if (data.has("profiles")) { + val profiles = data.getJSONArray("profiles") + if (profiles.length() > 0) { + // take the newest + val profileStoreJson = profiles[profiles.length() - 1] as JSONObject + rxBus.send(EventNSClientNewLog("PROFILE", "profile received")) + dataWorker.enqueue( + OneTimeWorkRequest.Builder(LocalProfilePlugin.NSProfileWorker::class.java) + .setInputData(dataWorker.storeInputData(profileStoreJson, null)) + .build()) + if (sp.getBoolean(R.string.key_nsclient_localbroadcasts, false)) { + val bundle = Bundle() + bundle.putString("profile", profileStoreJson.toString()) + bundle.putBoolean("delta", isDelta) + val intent = Intent(Intents.ACTION_NEW_PROFILE) + intent.putExtras(bundle) + intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES) + sendBroadcast(intent) + } + } + } + if (data.has("treatments")) { + val treatments = data.getJSONArray("treatments") + val removedTreatments = JSONArray() + val addedOrUpdatedTreatments = JSONArray() + if (treatments.length() > 0) rxBus.send(EventNSClientNewLog("DATA", "received " + treatments.length() + " treatments")) + for (index in 0 until treatments.length()) { + val jsonTreatment = treatments.getJSONObject(index) + val action = safeGetStringAllowNull(jsonTreatment, "action", null) + val mills = safeGetLong(jsonTreatment, "mills") + if (action == null) addedOrUpdatedTreatments.put(jsonTreatment) else if (action == "update") addedOrUpdatedTreatments.put(jsonTreatment) else if (action == "remove" && mills > dateUtil.now() - days(1).msecs()) // handle 1 day old deletions only + removedTreatments.put(jsonTreatment) + } + if (removedTreatments.length() > 0) { + dataWorker.enqueue( + OneTimeWorkRequest.Builder(NSClientRemoveWorker::class.java) + .setInputData(dataWorker.storeInputData(removedTreatments, null)) + .build()) + if (sp.getBoolean(R.string.key_nsclient_localbroadcasts, false)) { + val bundle = Bundle() + bundle.putString("treatments", removedTreatments.toString()) + bundle.putBoolean("delta", isDelta) + val intent = Intent(Intents.ACTION_REMOVED_TREATMENT) + intent.putExtras(bundle) + intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES) + sendBroadcast(intent) + } + } + if (addedOrUpdatedTreatments.length() > 0) { + dataWorker.enqueue( + OneTimeWorkRequest.Builder(NSClientAddUpdateWorker::class.java) + .setInputData(dataWorker.storeInputData(addedOrUpdatedTreatments, null)) + .build()) + if (sp.getBoolean(R.string.key_nsclient_localbroadcasts, false)) { + val splitted = splitArray(addedOrUpdatedTreatments) + for (part in splitted) { + val bundle = Bundle() + bundle.putString("treatments", part.toString()) + bundle.putBoolean("delta", isDelta) + val intent = Intent(Intents.ACTION_CHANGED_TREATMENT) + intent.putExtras(bundle) + intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES) + sendBroadcast(intent) + } + } + } + } + if (data.has("devicestatus")) { + val devicestatuses = data.getJSONArray("devicestatus") + if (devicestatuses.length() > 0) { + rxBus.send(EventNSClientNewLog("DATA", "received " + devicestatuses.length() + " device statuses")) + nsDeviceStatus.handleNewData(devicestatuses) + } + } + if (data.has("food")) { + val foods = data.getJSONArray("food") + if (foods.length() > 0) rxBus.send(EventNSClientNewLog("DATA", "received " + foods.length() + " foods")) + dataWorker.enqueue( + OneTimeWorkRequest.Builder(FoodWorker::class.java) + .setInputData(dataWorker.storeInputData(foods, null)) + .build()) + } + if (data.has("mbgs")) { + val mbgArray = data.getJSONArray("mbgs") + if (mbgArray.length() > 0) rxBus.send(EventNSClientNewLog("DATA", "received " + mbgArray.length() + " mbgs")) + dataWorker.enqueue( + OneTimeWorkRequest.Builder(NSClientMbgWorker::class.java) + .setInputData(dataWorker.storeInputData(mbgArray, null)) + .build()) + } + if (data.has("cals")) { + val cals = data.getJSONArray("cals") + if (cals.length() > 0) rxBus.send(EventNSClientNewLog("DATA", "received " + cals.length() + " cals")) + // Calibrations ignored + } + if (data.has("sgvs")) { + val sgvs = data.getJSONArray("sgvs") + if (sgvs.length() > 0) rxBus.send(EventNSClientNewLog("DATA", "received " + sgvs.length() + " sgvs")) + dataWorker.enqueue(OneTimeWorkRequest.Builder(NSClientSourceWorker::class.java) + .setInputData(dataWorker.storeInputData(sgvs, null)) + .build()) + val splitted = splitArray(sgvs) + if (sp.getBoolean(R.string.key_nsclient_localbroadcasts, false)) { + for (part in splitted) { + val bundle = Bundle() + bundle.putString("sgvs", part.toString()) + bundle.putBoolean("delta", isDelta) + val intent = Intent(Intents.ACTION_NEW_SGV) + intent.putExtras(bundle) + intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES) + sendBroadcast(intent) + } + } + } + rxBus.send(EventNSClientNewLog("LAST", dateUtil.dateAndTimeString(latestDateInReceivedData))) + } catch (e: JSONException) { + aapsLogger.error("Unhandled exception", e) + } + //rxBus.send(new EventNSClientNewLog("NSCLIENT", "onDataUpdate end"); + } finally { + // if (wakeLock.isHeld) wakeLock.release() + } + } + } + + fun dbUpdate(collection: String, _id: String?, data: JSONObject?, originalObject: Any, progress: String) { + try { + if (_id == null) return + if (!isConnected || !hasWriteAuth) return + val message = JSONObject() + message.put("collection", collection) + message.put("_id", _id) + message.put("data", data) + socket?.emit("dbUpdate", message, NSUpdateAck("dbUpdate", _id, aapsLogger, rxBus, originalObject)) + rxBus.send(EventNSClientNewLog("DBUPDATE $collection", "Sent " + originalObject.javaClass.simpleName + " " + _id + " " + progress)) + } catch (e: JSONException) { + aapsLogger.error("Unhandled exception", e) + } + } + + fun dbAdd(collection: String, data: JSONObject, originalObject: Any, progress: String) { + try { + if (!isConnected || !hasWriteAuth) return + val message = JSONObject() + message.put("collection", collection) + message.put("data", data) + socket?.emit("dbAdd", message, NSAddAck(aapsLogger, rxBus, originalObject)) + rxBus.send(EventNSClientNewLog("DBADD $collection", "Sent " + originalObject.javaClass.simpleName + " " + data + " " + progress)) + } catch (e: JSONException) { + aapsLogger.error("Unhandled exception", e) + } + } + + fun sendAlarmAck(alarmAck: AlarmAck) { + if (!isConnected || !hasWriteAuth) return + socket?.emit("ack", alarmAck.level, alarmAck.group, alarmAck.silenceTime) + rxBus.send(EventNSClientNewLog("ALARMACK ", alarmAck.level.toString() + " " + alarmAck.group + " " + alarmAck.silenceTime)) + } + + fun resend(reason: String) { + if (!isConnected || !hasWriteAuth) return + handler?.post { + if (socket?.connected() != true) return@post + if (lastAckTime > System.currentTimeMillis() - 10 * 1000L) { + aapsLogger.debug(LTag.NSCLIENT, "Skipping resend by lastAckTime: " + (System.currentTimeMillis() - lastAckTime) / 1000L + " sec") + return@post + } + // val powerManager = getSystemService(POWER_SERVICE) as PowerManager + // val wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + // "AndroidAPS:NSClientService_onDataUpdate") + // wakeLock.acquire(mins(10).msecs()) + try { + rxBus.send(EventNSClientNewLog("QUEUE", "Resend started: $reason")) + dataSyncSelector.doUpload() + rxBus.send(EventNSClientNewLog("QUEUE", "Resend ended: $reason")) + } finally { + // if (wakeLock.isHeld) wakeLock.release() + } + } + } + + @kotlin.ExperimentalStdlibApi + fun restart() { + destroy() + initialize() + } + + private fun handleAnnouncement(announcement: JSONObject) { + val defaultVal = config.NSCLIENT + if (sp.getBoolean(R.string.key_ns_announcements, defaultVal)) { + val nsAlarm = NSAlarm(announcement) + val notification: Notification = NotificationWithAction(injector, nsAlarm) + rxBus.send(EventNewNotification(notification)) + rxBus.send(EventNSClientNewLog("ANNOUNCEMENT", safeGetString(announcement, "message", "received"))) + aapsLogger.debug(LTag.NSCLIENT, announcement.toString()) + } + } + + private fun handleAlarm(alarm: JSONObject) { + val defaultVal = config.NSCLIENT + if (sp.getBoolean(R.string.key_ns_alarms, defaultVal)) { + val snoozedTo = sp.getLong(R.string.key_snoozedTo, 0L) + if (snoozedTo == 0L || System.currentTimeMillis() > snoozedTo) { + val nsAlarm = NSAlarm(alarm) + val notification: Notification = NotificationWithAction(injector, nsAlarm) + rxBus.send(EventNewNotification(notification)) + } + rxBus.send(EventNSClientNewLog("ALARM", safeGetString(alarm, "message", "received"))) + aapsLogger.debug(LTag.NSCLIENT, alarm.toString()) + } + } + + private fun handleUrgentAlarm(alarm: JSONObject) { + val defaultVal = config.NSCLIENT + if (sp.getBoolean(R.string.key_ns_alarms, defaultVal)) { + val snoozedTo = sp.getLong(R.string.key_snoozedTo, 0L) + if (snoozedTo == 0L || System.currentTimeMillis() > snoozedTo) { + val nsAlarm = NSAlarm(alarm) + val notification: Notification = NotificationWithAction(injector, nsAlarm) + rxBus.send(EventNewNotification(notification)) + } + rxBus.send(EventNSClientNewLog("URGENTALARM", safeGetString(alarm, "message", "received"))) + aapsLogger.debug(LTag.NSCLIENT, alarm.toString()) + } + } + + private fun splitArray(array: JSONArray): List { + var ret: MutableList = ArrayList() + try { + val size = array.length() + var count = 0 + var newarr: JSONArray? = null + for (i in 0 until size) { + if (count == 0) { + if (newarr != null) ret.add(newarr) + newarr = JSONArray() + count = 20 + } + newarr?.put(array[i]) + --count + } + if (newarr != null && newarr.length() > 0) ret.add(newarr) + } catch (e: JSONException) { + aapsLogger.error("Unhandled exception", e) + ret = ArrayList() + ret.add(array) + } + return ret + } + + init { + if (handler == null) { + val handlerThread = HandlerThread(NSClientService::class.java.simpleName + "Handler") + handlerThread.start() + handler = Handler(handlerThread.looper) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/openhumans/AllowedPreferenceKeys.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/openhumans/AllowedPreferenceKeys.kt index c8f13a842c0..0398af2892a 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/openhumans/AllowedPreferenceKeys.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/openhumans/AllowedPreferenceKeys.kt @@ -2,8 +2,10 @@ package info.nightscout.androidaps.plugins.general.openhumans import java.util.* -fun String.isAllowedKey() = if (startsWith("ConfigBuilder_")) true else allowedKeys.contains(this.toUpperCase(Locale.ROOT)) +@kotlin.ExperimentalStdlibApi +fun String.isAllowedKey() = if (startsWith("ConfigBuilder_")) true else allowedKeys.contains(this.uppercase(Locale.ROOT)) +@kotlin.ExperimentalStdlibApi private val allowedKeys = """ absorption absorption_maxtime @@ -206,4 +208,4 @@ private val allowedKeys = """ xdripstatus xdripstatus_detailediob xdripstatus_showbgi -""".trimIndent().split("\n").filterNot { it.isBlank() }.map { it.toUpperCase() } \ No newline at end of file +""".trimIndent().split("\n").filterNot { it.isBlank() }.map { it.uppercase(Locale.getDefault()) } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/openhumans/OHUploadWorker.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/openhumans/OHUploadWorker.kt index 7ce4d784e23..f6d5dca118f 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/openhumans/OHUploadWorker.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/openhumans/OHUploadWorker.kt @@ -28,6 +28,7 @@ class OHUploadWorker(context: Context, workerParameters: WorkerParameters) @Inject lateinit var resourceHelper: ResourceHelper + @kotlin.ExperimentalStdlibApi override fun createWork(): Single = Single.defer { // Here we inject every time we create work diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/openhumans/OpenHumansUploader.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/openhumans/OpenHumansUploader.kt index cf4f0c62fa8..1af58b5089d 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/openhumans/OpenHumansUploader.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/openhumans/OpenHumansUploader.kt @@ -178,24 +178,24 @@ class OpenHumansUploader @Inject constructor( } } - @JvmOverloads - fun enqueueTreatment(treatment: Treatment?, deleted: Boolean = false) = treatment?.let { - insertQueueItem("Treatments") { - put("date", treatment.date) - put("isValid", treatment.isValid) - put("source", treatment.source) - put("nsId", treatment._id) - put("boluscalc", treatment.boluscalc) - put("carbs", treatment.carbs) - put("dia", treatment.dia) - put("insulin", treatment.insulin) - put("insulinInterfaceID", treatment.insulinInterfaceID) - put("isSMB", treatment.isSMB) - put("mealBolus", treatment.mealBolus) - put("bolusCalcJson", treatment.getBoluscalc()) - put("isDeletion", deleted) - } - } + // @JvmOverloads + // fun enqueueTreatment(treatment: Treatment?, deleted: Boolean = false) = treatment?.let { + // insertQueueItem("Treatments") { + // put("date", treatment.date) + // put("isValid", treatment.isValid) + // put("source", treatment.source) + // put("nsId", treatment._id) + // put("boluscalc", treatment.boluscalc) + // put("carbs", treatment.carbs) + // put("dia", treatment.dia) + // put("insulin", treatment.insulin) + // put("insulinInterfaceID", treatment.insulinInterfaceID) + // put("isSMB", treatment.isSMB) + // put("mealBolus", treatment.mealBolus) + // put("bolusCalcJson", treatment.getBoluscalc()) + // put("isDeletion", deleted) + // } + // } @JvmOverloads fun enqueueTherapyEvent(therapyEvent: TherapyEvent, deleted: Boolean = false) = insertQueueItem("TherapyEvents") { @@ -210,17 +210,17 @@ class OpenHumansUploader @Inject constructor( put("isDeletion", deleted) } - @JvmOverloads - fun enqueueExtendedBolus(extendedBolus: ExtendedBolus, deleted: Boolean = false) = insertQueueItem("ExtendedBoluses") { - put("date", extendedBolus.date) - put("isValid", extendedBolus.isValid) - put("source", extendedBolus.source) - put("nsId", extendedBolus._id) - put("pumpId", extendedBolus.pumpId) - put("insulin", extendedBolus.insulin) - put("durationInMinutes", extendedBolus.durationInMinutes) - put("isDeletion", deleted) - } + // @JvmOverloads + // fun enqueueExtendedBolus(extendedBolus: ExtendedBolus, deleted: Boolean = false) = insertQueueItem("ExtendedBoluses") { + // put("date", extendedBolus.date) + // put("isValid", extendedBolus.isValid) + // put("source", extendedBolus.source) + // put("nsId", extendedBolus._id) + // put("pumpId", extendedBolus.pumpId) + // put("insulin", extendedBolus.insulin) + // put("durationInMinutes", extendedBolus.durationInMinutes) + // put("isDeletion", deleted) + // } // @JvmOverloads // fun enqueueProfileSwitch(profileSwitch: ProfileSwitch, deleted: Boolean = false) = insertQueueItem("ProfileSwitches") { @@ -244,22 +244,22 @@ class OpenHumansUploader @Inject constructor( // put("double", tdd.total) // } - @JvmOverloads - fun enqueueTemporaryBasal(temporaryBasal: TemporaryBasal?, deleted: Boolean = false) = temporaryBasal?.let { - insertQueueItem("TemporaryBasals") { - put("date", temporaryBasal.date) - put("isValid", temporaryBasal.isValid) - put("source", temporaryBasal.source) - put("nsId", temporaryBasal._id) - put("pumpId", temporaryBasal.pumpId) - put("durationInMinutes", temporaryBasal.durationInMinutes) - put("durationInMinutes", temporaryBasal.durationInMinutes) - put("isAbsolute", temporaryBasal.isAbsolute) - put("percentRate", temporaryBasal.percentRate) - put("absoluteRate", temporaryBasal.absoluteRate) - put("isDeletion", deleted) - } - } + // @JvmOverloads + // fun enqueueTemporaryBasal(temporaryBasal: TemporaryBasal?, deleted: Boolean = false) = temporaryBasal?.let { + // insertQueueItem("TemporaryBasals") { + // put("date", temporaryBasal.date) + // put("isValid", temporaryBasal.isValid) + // put("source", temporaryBasal.source) + // put("nsId", temporaryBasal._id) + // put("pumpId", temporaryBasal.pumpId) + // put("durationInMinutes", temporaryBasal.durationInMinutes) + // put("durationInMinutes", temporaryBasal.durationInMinutes) + // put("isAbsolute", temporaryBasal.isAbsolute) + // put("percentRate", temporaryBasal.percentRate) + // put("absoluteRate", temporaryBasal.absoluteRate) + // put("isDeletion", deleted) + // } + // } @JvmOverloads fun enqueueTempTarget(tempTarget: TemporaryTarget?, deleted: Boolean = false) = tempTarget?.let { @@ -439,6 +439,7 @@ class OpenHumansUploader @Inject constructor( notificationManager.notify(FAILURE_NOTIFICATION_ID, notification) } + @kotlin.ExperimentalStdlibApi fun uploadDataSegmentally(): Completable = uploadData(UPLOAD_SEGMENT_SIZE) .repeatUntil { databaseHelper.getOHQueueSize() == 0L } @@ -452,6 +453,7 @@ class OpenHumansUploader @Inject constructor( aapsLogger.error(LTag.OHUPLOADER, "Segmental upload exceptional", it) } + @kotlin.ExperimentalStdlibApi @Suppress("SameParameterValue") private fun uploadData(maxEntries: Long): Completable = gatherData(maxEntries) .flatMap { data -> refreshAccessTokensIfNeeded().map { accessToken -> accessToken to data } } @@ -494,6 +496,7 @@ class OpenHumansUploader @Inject constructor( } } + @kotlin.ExperimentalStdlibApi private fun gatherData(maxEntries: Long) = Single.defer { val items = databaseHelper.getAllOHQueueItems(maxEntries) val baos = ByteArrayOutputStream() 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 new file mode 100644 index 00000000000..415ae58299d --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewData.kt @@ -0,0 +1,811 @@ +package info.nightscout.androidaps.plugins.general.overview + +import android.graphics.DashPathEffect +import android.graphics.Paint +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.* +import info.nightscout.androidaps.extensions.* +import info.nightscout.androidaps.interfaces.* +import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin +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.plugins.iob.iobCobCalculator.CobInfo +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.data.AutosensData +import info.nightscout.androidaps.utils.* +import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.androidaps.utils.sharedPreferences.SP +import java.util.* +import javax.inject.Inject +import javax.inject.Singleton +import kotlin.collections.ArrayList +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 resourceHelper: ResourceHelper, + private val dateUtil: DateUtil, + private val sp: SP, + private val activePlugin: ActivePlugin, + private val defaultValueHelper: DefaultValueHelper, + private val profileFunction: ProfileFunction, + private val config: Config, + private val loopPlugin: LoopPlugin, + private val nsDeviceStatus: NSDeviceStatus, + private val repository: AppRepository, + private val overviewMenus: OverviewMenus, + private val iobCobCalculator: IobCobCalculator, + private val translator: Translator +) { + + enum class Property { + TIME, + CALC_PROGRESS, + PROFILE, + TEMPORARY_BASAL, + EXTENDED_BOLUS, + TEMPORARY_TARGET, + BG, + IOB_COB, + SENSITIVITY, + GRAPH + } + + var rangeToDisplay = 6 // for graph + var toTime: Long = 0 + var fromTime: Long = 0 + var endTime: Long = 0 + + fun initRange() { + rangeToDisplay = sp.getInt(R.string.key_rangetodisplay, 6) + + val calendar = Calendar.getInstance().also { + it.timeInMillis = System.currentTimeMillis() + it[Calendar.MILLISECOND] = 0 + it[Calendar.SECOND] = 0 + it[Calendar.MINUTE] = 0 + it.add(Calendar.HOUR, 1) + } + + toTime = calendar.timeInMillis + 100000 // little bit more to avoid wrong rounding - GraphView specific + fromTime = toTime - T.hours(rangeToDisplay.toLong()).msecs() + endTime = toTime + } + + /* + * PROFILE + */ + var profile: Profile? = null + var profileName: String? = null + var profileNameWithRemainingTime: String? = null + + val profileBackgroundColor: Int + get() = + profile?.let { profile -> + if (profile.percentage != 100 || profile.timeshift != 0) resourceHelper.gc(R.color.ribbonWarning) + else resourceHelper.gc(R.color.ribbonDefault) + } ?: resourceHelper.gc(R.color.ribbonTextDefault) + + val profileTextColor: Int + get() = + profile?.let { profile -> + if (profile.percentage != 100 || profile.timeshift != 0) resourceHelper.gc(R.color.ribbonTextWarning) + else resourceHelper.gc(R.color.ribbonTextDefault) + } ?: resourceHelper.gc(R.color.ribbonTextDefault) + + /* + * CALC PROGRESS + */ + + var calcProgress: String = "" + + /* + * BG + */ + + var lastBg: GlucoseValue? = null + + val lastBgColor: Int + get() = lastBg?.let { lastBg -> + when { + lastBg.valueToUnits(profileFunction.getUnits()) < defaultValueHelper.determineLowLine() -> resourceHelper.gc(R.color.low) + lastBg.valueToUnits(profileFunction.getUnits()) > defaultValueHelper.determineHighLine() -> resourceHelper.gc(R.color.high) + else -> resourceHelper.gc(R.color.inrange) + } + } ?: resourceHelper.gc(R.color.inrange) + + val isActualBg: Boolean + get() = + lastBg?.let { lastBg -> + lastBg.timestamp > dateUtil.now() - T.mins(9).msecs() + } ?: false + + /* + * TEMPORARY BASAL + */ + + var temporaryBasal: TemporaryBasal? = null + + val temporaryBasalText: String + get() = + profile?.let { profile -> + if (temporaryBasal?.isInProgress == false) temporaryBasal = null + temporaryBasal?.let { "T:" + it.toStringShort() } + ?: resourceHelper.gs(R.string.pump_basebasalrate, profile.getBasal()) + } ?: resourceHelper.gs(R.string.notavailable) + + val temporaryBasalDialogText: String + get() = profile?.let { profile -> + temporaryBasal?.let { temporaryBasal -> + "${resourceHelper.gs(R.string.basebasalrate_label)}: ${resourceHelper.gs(R.string.pump_basebasalrate, profile.getBasal())}" + + "\n" + resourceHelper.gs(R.string.tempbasal_label) + ": " + temporaryBasal.toStringFull(profile, dateUtil) + } + ?: "${resourceHelper.gs(R.string.basebasalrate_label)}: ${resourceHelper.gs(R.string.pump_basebasalrate, profile.getBasal())}" + } ?: resourceHelper.gs(R.string.notavailable) + + val temporaryBasalIcon: Int + get() = + profile?.let { profile -> + temporaryBasal?.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 + + val temporaryBasalColor: Int + get() = temporaryBasal?.let { resourceHelper.gc(R.color.basal) } + ?: resourceHelper.gc(R.color.defaulttextcolor) + + /* + * EXTENDED BOLUS + */ + + var extendedBolus: ExtendedBolus? = null + + val extendedBolusText: String + get() = + extendedBolus?.let { extendedBolus -> + if (!extendedBolus.isInProgress(dateUtil)) { + this@OverviewData.extendedBolus = null + "" + } else if (activePlugin.activePump.isFakingTempsByExtendedBoluses) resourceHelper.gs(R.string.pump_basebasalrate, extendedBolus.rate) + else "" + } ?: "" + + val extendedBolusDialogText: String + get() = extendedBolus?.toStringFull(dateUtil) ?: "" + + /* + * IOB, COB + */ + + var bolusIob: IobTotal? = null + var basalIob: IobTotal? = null + var cobInfo: CobInfo? = null + var lastCarbsTime: Long = 0L + + val iobText: String + get() = + bolusIob?.let { bolusIob -> + basalIob?.let { basalIob -> + resourceHelper.gs(R.string.formatinsulinunits, bolusIob.iob + basalIob.basaliob) + } ?: resourceHelper.gs(R.string.value_unavailable_short) + } ?: resourceHelper.gs(R.string.value_unavailable_short) + + val iobDialogText: String + get() = + bolusIob?.let { bolusIob -> + basalIob?.let { basalIob -> + resourceHelper.gs(R.string.formatinsulinunits, bolusIob.iob + basalIob.basaliob) + "\n" + + resourceHelper.gs(R.string.bolus) + ": " + resourceHelper.gs(R.string.formatinsulinunits, bolusIob.iob) + "\n" + + resourceHelper.gs(R.string.basal) + ": " + resourceHelper.gs(R.string.formatinsulinunits, basalIob.basaliob) + } ?: resourceHelper.gs(R.string.value_unavailable_short) + } ?: resourceHelper.gs(R.string.value_unavailable_short) + + /* + * TEMP TARGET + */ + + var temporaryTarget: TemporaryTarget? = null + + /* + * SENSITIVITY + */ + + var lastAutosensData: AutosensData? = null + /* + * Graphs + */ + + var bgReadingsArray: List = ArrayList() + var maxBgValue = Double.MIN_VALUE + var bucketedGraphSeries: PointsWithLabelGraphSeries = PointsWithLabelGraphSeries() + var bgReadingGraphSeries: PointsWithLabelGraphSeries = PointsWithLabelGraphSeries() + var predictionsGraphSeries: PointsWithLabelGraphSeries = PointsWithLabelGraphSeries() + + var maxBasalValueFound = 0.0 + val basalScale = Scale() + var baseBasalGraphSeries: LineGraphSeries = LineGraphSeries() + var tempBasalGraphSeries: LineGraphSeries = LineGraphSeries() + var basalLineGraphSeries: LineGraphSeries = LineGraphSeries() + var absoluteBasalGraphSeries: LineGraphSeries = LineGraphSeries() + + var temporaryTargetSeries: LineGraphSeries = LineGraphSeries() + + var maxIAValue = 0.0 + val actScale = Scale() + var activitySeries: FixedLineGraphSeries = FixedLineGraphSeries() + var activityPredictionSeries: FixedLineGraphSeries = FixedLineGraphSeries() + + var maxTreatmentsValue = 0.0 + var treatmentsSeries: PointsWithLabelGraphSeries = PointsWithLabelGraphSeries() + + var maxIobValueFound = Double.MIN_VALUE + val iobScale = Scale() + var iobSeries: FixedLineGraphSeries = FixedLineGraphSeries() + var absIobSeries: FixedLineGraphSeries = FixedLineGraphSeries() + var iobPredictions1Series: PointsWithLabelGraphSeries = PointsWithLabelGraphSeries() + var iobPredictions2Series: PointsWithLabelGraphSeries = PointsWithLabelGraphSeries() + + var maxBGIValue = Double.MIN_VALUE + val bgiScale = Scale() + var minusBgiSeries: FixedLineGraphSeries = FixedLineGraphSeries() + var minusBgiHistSeries: FixedLineGraphSeries = FixedLineGraphSeries() + + var maxCobValueFound = Double.MIN_VALUE + val cobScale = Scale() + var cobSeries: FixedLineGraphSeries = FixedLineGraphSeries() + var cobMinFailOverSeries: PointsWithLabelGraphSeries = PointsWithLabelGraphSeries() + + var maxDevValueFound = Double.MIN_VALUE + val devScale = Scale() + var deviationsSeries: BarGraphSeries = BarGraphSeries() + + var maxRatioValueFound = 5.0 //even if sens data equals 0 for all the period, minimum scale is between 95% and 105% + var minRatioValueFound = -maxRatioValueFound + val ratioScale = Scale() + var ratioSeries: LineGraphSeries = LineGraphSeries() + + var maxFromMaxValueFound = Double.MIN_VALUE + var maxFromMinValueFound = Double.MIN_VALUE + val dsMaxScale = Scale() + val dsMinScale = Scale() + var dsMaxSeries: LineGraphSeries = LineGraphSeries() + var dsMinSeries: LineGraphSeries = LineGraphSeries() + + @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, resourceHelper)) + } + 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) loopPlugin.lastRun?.constraintsProcessed else nsDeviceStatus.getAPSResult(injector) + val predictionsAvailable = if (config.APS) loopPlugin.lastRun?.request?.hasPredictions == true else config.NSCLIENT + val menuChartSettings = overviewMenus.setting + // align to hours + val calendar = Calendar.getInstance().also { + it.timeInMillis = System.currentTimeMillis() + it[Calendar.MILLISECOND] = 0 + it[Calendar.SECOND] = 0 + it[Calendar.MINUTE] = 0 + it.add(Calendar.HOUR, 1) + } + if (predictionsAvailable && apsResult != null && menuChartSettings[0][OverviewMenus.CharType.PRE.ordinal]) { + var predictionHours = (ceil(apsResult.latestPredictionsTime - System.currentTimeMillis().toDouble()) / (60 * 60 * 1000)).toInt() + predictionHours = min(2, predictionHours) + predictionHours = max(0, predictionHours) + val hoursToFetch = 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, resourceHelper) } + ?.toMutableList() + if (predictions != null) { + predictions.sortWith { o1: GlucoseValueDataPoint, o2: GlucoseValueDataPoint -> o1.x.compareTo(o2.x) } + for (prediction in predictions) if (prediction.data.value >= 40) bgListArray.add(prediction) + } + 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, resourceHelper)) + } + 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 = resourceHelper.gc(R.color.basebasal) + it.thickness = 0 + } + tempBasalGraphSeries = LineGraphSeries(Array(tempBasalArray.size) { i -> tempBasalArray[i] }).also { + it.isDrawBackground = true + it.backgroundColor = resourceHelper.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 = resourceHelper.getDisplayMetrics().scaledDensity * 2 + paint.pathEffect = DashPathEffect(floatArrayOf(2f, 4f), 0f) + paint.color = resourceHelper.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 = resourceHelper.getDisplayMetrics().scaledDensity * 2 + absolutePaint.color = resourceHelper.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 = profile ?: return + val units = profileFunction.getUnits() + var toTime = toTime + val targetsSeriesArray: MutableList = java.util.ArrayList() + var lastTarget = -1.0 + loopPlugin.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 = resourceHelper.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.getBolusesIncludingInvalidFromTimeToTime(fromTime, endTime, true).blockingGet() + .map { BolusDataPoint(it, resourceHelper, activePlugin, defaultValueHelper) } + .filter { it.data.type != Bolus.Type.SMB || it.data.isValid } + .forEach { + it.y = getNearestBg(it.x.toLong()) + filteredTreatments.add(it) + } + repository.getCarbsIncludingInvalidFromTimeToTimeExpanded(fromTime, endTime, true).blockingGet() + .map { CarbsDataPoint(it, resourceHelper) } + .forEach { + it.y = getNearestBg(it.x.toLong()) + filteredTreatments.add(it) + } + + // ProfileSwitch + repository.getEffectiveProfileSwitchDataFromTimeToTime(fromTime, endTime, true).blockingGet() + .map { EffectiveProfileSwitchDataPoint(it) } + .forEach(filteredTreatments::add) + + // 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), resourceHelper, 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, resourceHelper, profileFunction, translator) } + .filterTimeframe(fromTime, endTime) + .forEach { + if (it.y == 0.0) it.y = getNearestBg(it.x.toLong()) + filteredTreatments.add(it) + } + + // increase maxY if a treatment forces it's own height that's higher than a BG value + filteredTreatments.map { it.y } + .maxOrNull() + ?.let(::addUpperChartMargin) + ?.let { 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.failoverToMinAbsorbtionRate) { + autosensData.setScale(cobScale) + autosensData.setChartTime(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 = resourceHelper.gc(R.color.deviationblack) // "=" + if (autosensData.type == "" || autosensData.type == "non-meal") { + if (autosensData.pastSensitivity == "C") color = resourceHelper.gc(R.color.deviationgrey) + if (autosensData.pastSensitivity == "+") color = resourceHelper.gc(R.color.deviationgreen) + if (autosensData.pastSensitivity == "-") color = resourceHelper.gc(R.color.deviationred) + } else if (autosensData.type == "uam") { + color = resourceHelper.gc(R.color.uam) + } else if (autosensData.type == "csf") { + color = resourceHelper.gc(R.color.deviationgrey) + } + devArray.add(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 resourceHelper.gc(R.color.iob) //50% + it.color = resourceHelper.gc(R.color.iob) + it.thickness = 3 + } + absIobSeries = FixedLineGraphSeries(Array(absIobArray.size) { i -> absIobArray[i] }).also { + it.isDrawBackground = true + it.backgroundColor = -0x7f000001 and resourceHelper.gc(R.color.iob) //50% + it.color = resourceHelper.gc(R.color.iob) + it.thickness = 3 + } + + if (overviewMenus.setting[0][OverviewMenus.CharType.PRE.ordinal]) { + val autosensData = adsData.getLastAutosensData("GraphData", aapsLogger, dateUtil) + val lastAutosensResult = autosensData?.autosensResult ?: AutosensResult() + val isTempTarget = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet() is ValueWrapper.Existing + val iobPrediction: MutableList = 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(resourceHelper.gc(R.color.iobPredAS))) + maxIobValueFound = max(maxIobValueFound, abs(i.iob)) + } + iobPredictions1Series = PointsWithLabelGraphSeries(Array(iobPrediction.size) { i -> iobPrediction[i] }) + 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(resourceHelper.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(lastAutosensResult.ratio) + ": " + iobCobCalculator.iobArrayToString(iobPredictionArray)) + 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 resourceHelper.gc(R.color.cob) //50% + it.color = resourceHelper.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 = resourceHelper.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 = resourceHelper.gc(R.color.activity) + }) + } + + // BGI + minusBgiSeries = FixedLineGraphSeries(Array(bgiArrayHist.size) { i -> bgiArrayHist[i] }).also { + it.isDrawBackground = false + it.color = resourceHelper.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 = resourceHelper.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 = resourceHelper.gc(R.color.ratio) + it.thickness = 3 + } + + // DEV SLOPE + dsMaxSeries = LineGraphSeries(Array(dsMaxArray.size) { i -> dsMaxArray[i] }).also { + it.color = resourceHelper.gc(R.color.devslopepos) + it.thickness = 3 + } + dsMinSeries = LineGraphSeries(Array(dsMinArray.size) { i -> dsMinArray[i] }).also { + it.color = resourceHelper.gc(R.color.devslopeneg) + it.thickness = 3 + } + +// profiler.log(LTag.UI, "prepareIobAutosensData() $from", start) + } + + private fun addUpperChartMargin(maxBgValue: Double) = + if (profileFunction.getUnits() == GlucoseUnit.MGDL) Round.roundTo(maxBgValue, 40.0) + 80 else Round.roundTo(maxBgValue, 2.0) + 4 + + private fun getNearestBg(date: Long): Double { + 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 8810a7613f0..4ab51bfe5cf 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 @@ -10,6 +10,7 @@ import android.graphics.Paint import android.graphics.drawable.AnimationDrawable import android.os.Bundle import android.os.Handler +import android.os.HandlerThread import android.util.DisplayMetrics import android.view.LayoutInflater import android.view.View @@ -19,25 +20,27 @@ import android.widget.LinearLayout import android.widget.RelativeLayout import android.widget.TextView import androidx.core.text.toSpanned -import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import com.jjoe64.graphview.GraphView import dagger.android.HasAndroidInjector import dagger.android.support.DaggerFragment -import info.nightscout.androidaps.interfaces.Config import info.nightscout.androidaps.Constants import info.nightscout.androidaps.R -import info.nightscout.androidaps.interfaces.Profile import info.nightscout.androidaps.database.AppRepository -import info.nightscout.androidaps.database.ValueWrapper -import info.nightscout.androidaps.database.entities.TemporaryTarget import info.nightscout.androidaps.database.entities.UserEntry.Action import info.nightscout.androidaps.database.entities.UserEntry.Sources import info.nightscout.androidaps.database.interfaces.end import info.nightscout.androidaps.databinding.OverviewFragmentBinding import info.nightscout.androidaps.dialogs.* -import info.nightscout.androidaps.events.* -import info.nightscout.androidaps.extensions.* +import info.nightscout.androidaps.events.EventAcceptOpenLoopChange +import info.nightscout.androidaps.events.EventInitializationChanged +import info.nightscout.androidaps.events.EventPreferenceChange +import info.nightscout.androidaps.events.EventPumpStatusChanged +import info.nightscout.androidaps.events.EventRefreshOverview +import info.nightscout.androidaps.extensions.directionToIcon +import info.nightscout.androidaps.extensions.isInProgress +import info.nightscout.androidaps.extensions.toVisibility +import info.nightscout.androidaps.extensions.valueToUnitsString import info.nightscout.androidaps.interfaces.* import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.UserEntryLogger @@ -47,22 +50,19 @@ import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus import info.nightscout.androidaps.plugins.general.overview.activities.QuickWizardListActivity +import info.nightscout.androidaps.plugins.general.overview.events.EventUpdateOverview import info.nightscout.androidaps.plugins.general.overview.graphData.GraphData -import info.nightscout.androidaps.plugins.general.overview.graphExtensions.GlucoseValueDataPoint import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationStore import info.nightscout.androidaps.plugins.general.wear.events.EventWearInitiateAction import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProvider -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress import info.nightscout.androidaps.plugins.pump.common.defs.PumpType import info.nightscout.androidaps.plugins.source.DexcomPlugin import info.nightscout.androidaps.plugins.source.XdripPlugin -import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin import info.nightscout.androidaps.queue.CommandQueue import info.nightscout.androidaps.skins.SkinProvider import info.nightscout.androidaps.utils.* import info.nightscout.androidaps.utils.alertDialogs.OKDialog import info.nightscout.androidaps.utils.buildHelper.BuildHelper -import info.nightscout.androidaps.utils.extensions.* import info.nightscout.androidaps.utils.protection.ProtectionCheck import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.rx.AapsSchedulers @@ -70,15 +70,11 @@ import info.nightscout.androidaps.utils.sharedPreferences.SP import info.nightscout.androidaps.utils.ui.UIRunnable import info.nightscout.androidaps.utils.wizard.QuickWizard import io.reactivex.disposables.CompositeDisposable -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext +import io.reactivex.rxkotlin.plusAssign import java.util.* import javax.inject.Inject import kotlin.collections.ArrayList import kotlin.math.abs -import kotlin.math.ceil -import kotlin.math.max import kotlin.math.min class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickListener { @@ -96,7 +92,6 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList @Inject lateinit var nsDeviceStatus: NSDeviceStatus @Inject lateinit var loopPlugin: LoopPlugin @Inject lateinit var activePlugin: ActivePlugin - @Inject lateinit var treatmentsPlugin: TreatmentsPlugin @Inject lateinit var iobCobCalculator: IobCobCalculator @Inject lateinit var dexcomPlugin: DexcomPlugin @Inject lateinit var dexcomMediator: DexcomPlugin.DexcomMediator @@ -112,10 +107,11 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList @Inject lateinit var trendCalculator: TrendCalculator @Inject lateinit var config: Config @Inject lateinit var dateUtil: DateUtil - @Inject lateinit var databaseHelper: DatabaseHelperInterface @Inject lateinit var uel: UserEntryLogger @Inject lateinit var repository: AppRepository @Inject lateinit var glucoseStatusProvider: GlucoseStatusProvider + @Inject lateinit var overviewData: OverviewData + @Inject lateinit var overviewPlugin: OverviewPlugin private val disposable = CompositeDisposable() @@ -123,17 +119,14 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList private var smallHeight = false private lateinit var dm: DisplayMetrics private var axisWidth: Int = 0 - private var rangeToDisplay = 6 // for graph - private var loopHandler = Handler() private var refreshLoop: Runnable? = null + private lateinit var handler: Handler private val secondaryGraphs = ArrayList() private val secondaryGraphsLabel = ArrayList() private var carbAnimation: AnimationDrawable? = null - private val graphLock = Object() - private var _binding: OverviewFragmentBinding? = null // This property is only valid between onCreateView and @@ -150,6 +143,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + handler = Handler(HandlerThread(this::class.simpleName + "Handler").also { it.start() }.looper) // pre-process landscape mode val screenWidth = dm.widthPixels @@ -175,13 +169,14 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList carbAnimation?.setEnterFadeDuration(1200) carbAnimation?.setExitFadeDuration(1200) - rangeToDisplay = sp.getInt(R.string.key_rangetodisplay, 6) binding.graphsLayout.bgGraph.setOnLongClickListener { - rangeToDisplay += 6 - rangeToDisplay = if (rangeToDisplay > 24) 6 else rangeToDisplay - sp.putInt(R.string.key_rangetodisplay, rangeToDisplay) - updateGUI("rangeChange") + overviewData.rangeToDisplay += 6 + overviewData.rangeToDisplay = if (overviewData.rangeToDisplay > 24) 6 else overviewData.rangeToDisplay + sp.putInt(R.string.key_rangetodisplay, overviewData.rangeToDisplay) + overviewData.initRange() + updateGUI("rangeChange", OverviewData.Property.GRAPH) + rxBus.send(EventPreferenceChange(resourceHelper, R.string.key_rangetodisplay)) sp.putBoolean(R.string.key_objectiveusescale, true) false } @@ -210,55 +205,32 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList override fun onPause() { super.onPause() disposable.clear() - loopHandler.removeCallbacksAndMessages(null) + handler.removeCallbacksAndMessages(null) } @Synchronized override fun onResume() { super.onResume() + disposable += activePlugin.activeOverview.overviewBus + .toObservable(EventUpdateOverview::class.java) + .observeOn(aapsSchedulers.main) + .subscribe({ updateGUI(it.from, it.what) }, fabricPrivacy::logException) + disposable.add(rxBus .toObservable(EventRefreshOverview::class.java) - .observeOn(aapsSchedulers.main) + .observeOn(aapsSchedulers.io) .subscribe({ - if (it.now) updateGUI(it.from) + if (it.now) overviewPlugin.refreshLoop(it.from) else scheduleUpdateGUI(it.from) }, fabricPrivacy::logException)) - disposable.add(rxBus - .toObservable(EventExtendedBolusChange::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ scheduleUpdateGUI("EventExtendedBolusChange") }, fabricPrivacy::logException)) - disposable.add(rxBus - .toObservable(EventTempBasalChange::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ scheduleUpdateGUI("EventTempBasalChange") }, fabricPrivacy::logException)) - disposable.add(rxBus - .toObservable(EventTreatmentChange::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ scheduleUpdateGUI("EventTreatmentChange") }, fabricPrivacy::logException)) - disposable.add(rxBus - .toObservable(EventTempTargetChange::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ scheduleUpdateGUI("EventTempTargetChange") }, fabricPrivacy::logException)) disposable.add(rxBus .toObservable(EventAcceptOpenLoopChange::class.java) .observeOn(aapsSchedulers.io) .subscribe({ scheduleUpdateGUI("EventAcceptOpenLoopChange") }, fabricPrivacy::logException)) - disposable.add(rxBus - .toObservable(EventTherapyEventChange::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ scheduleUpdateGUI("EventCareportalEventChange") }, fabricPrivacy::logException)) disposable.add(rxBus .toObservable(EventInitializationChanged::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ scheduleUpdateGUI("EventInitializationChanged") }, fabricPrivacy::logException)) - disposable.add(rxBus - .toObservable(EventAutosensCalculationFinished::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ scheduleUpdateGUI("EventAutosensCalculationFinished") }, fabricPrivacy::logException)) - disposable.add(rxBus - .toObservable(EventProfileSwitchChanged::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ scheduleUpdateGUI("EventProfileNeedsUpdate") }, fabricPrivacy::logException)) + .observeOn(aapsSchedulers.main) + .subscribe({ updateGUI("EventInitializationChanged", OverviewData.Property.TIME) }, fabricPrivacy::logException)) disposable.add(rxBus .toObservable(EventPreferenceChange::class.java) .observeOn(aapsSchedulers.io) @@ -271,18 +243,14 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList .toObservable(EventPumpStatusChanged::class.java) .observeOn(aapsSchedulers.main) .subscribe({ updatePumpStatus(it) }, fabricPrivacy::logException)) - disposable.add(rxBus - .toObservable(EventIobCalculationProgress::class.java) - .observeOn(aapsSchedulers.main) - .subscribe({ binding.graphsLayout.iobCalculationProgress.text = it.progress }, fabricPrivacy::logException)) refreshLoop = Runnable { - scheduleUpdateGUI("refreshLoop") - loopHandler.postDelayed(refreshLoop, 60 * 1000L) + overviewPlugin.refreshLoop("refreshLoop") + handler.postDelayed(refreshLoop, 60 * 1000L) } - loopHandler.postDelayed(refreshLoop, 60 * 1000L) + handler.postDelayed(refreshLoop, 60 * 1000L) - updateGUI("onResume") + for (p in OverviewData.Property.values()) updateGUI("onResume", p) } @Synchronized @@ -297,14 +265,14 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList if (childFragmentManager.isStateSaved) return activity?.let { activity -> when (v.id) { - R.id.treatment_button -> protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, UIRunnable { if (isAdded) TreatmentDialog().show(childFragmentManager, "Overview") }) - R.id.wizard_button -> protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, UIRunnable { if (isAdded) WizardDialog().show(childFragmentManager, "Overview") }) - R.id.insulin_button -> protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, UIRunnable { if (isAdded) InsulinDialog().show(childFragmentManager, "Overview") }) + R.id.treatment_button -> protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, UIRunnable { if (isAdded) TreatmentDialog().show(childFragmentManager, "Overview") }) + R.id.wizard_button -> protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, UIRunnable { if (isAdded) WizardDialog().show(childFragmentManager, "Overview") }) + R.id.insulin_button -> protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, UIRunnable { if (isAdded) InsulinDialog().show(childFragmentManager, "Overview") }) R.id.quick_wizard_button -> protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, UIRunnable { if (isAdded) onClickQuickWizard() }) - R.id.carbs_button -> protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, UIRunnable { if (isAdded) CarbsDialog().show(childFragmentManager, "Overview") }) - R.id.temp_target -> protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, UIRunnable { if (isAdded) TempTargetDialog().show(childFragmentManager, "Overview") }) + R.id.carbs_button -> protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, UIRunnable { if (isAdded) CarbsDialog().show(childFragmentManager, "Overview") }) + R.id.temp_target -> protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, UIRunnable { if (isAdded) TempTargetDialog().show(childFragmentManager, "Overview") }) - R.id.active_profile -> { + R.id.active_profile -> { ProfileViewerDialog().also { pvd -> pvd.arguments = Bundle().also { it.putLong("time", dateUtil.now()) @@ -313,7 +281,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList }.show(childFragmentManager, "ProfileViewDialog") } - R.id.cgm_button -> { + R.id.cgm_button -> { if (xdripPlugin.isEnabled(PluginType.BGSOURCE)) openCgmApp("com.eveningoutpost.dexdrip") else if (dexcomPlugin.isEnabled(PluginType.BGSOURCE)) { @@ -324,7 +292,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList } } - R.id.calibration_button -> { + R.id.calibration_button -> { if (xdripPlugin.isEnabled(PluginType.BGSOURCE)) { CalibrationDialog().show(childFragmentManager, "CalibrationDialog") } else if (dexcomPlugin.isEnabled(PluginType.BGSOURCE)) { @@ -339,27 +307,29 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList } } - R.id.accept_temp_button -> { + R.id.accept_temp_button -> { profileFunction.getProfile() ?: return if (loopPlugin.isEnabled(PluginType.LOOP)) { - val lastRun = loopPlugin.lastRun - loopPlugin.invoke("Accept temp button", false) - if (lastRun?.lastAPSRun != null && lastRun.constraintsProcessed?.isChangeRequested == true) { - protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, UIRunnable { - OKDialog.showConfirmation(activity, resourceHelper.gs(R.string.tempbasal_label), lastRun.constraintsProcessed?.toSpanned() - ?: "".toSpanned(), { - uel.log(Action.ACCEPTS_TEMP_BASAL, Sources.Overview) - binding.buttonsLayout.acceptTempButton.visibility = View.GONE - (context?.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).cancel(Constants.notificationID) - rxBus.send(EventWearInitiateAction("cancelChangeRequest")) - loopPlugin.acceptChangeRequest() + handler.post { + val lastRun = loopPlugin.lastRun + loopPlugin.invoke("Accept temp button", false) + if (lastRun?.lastAPSRun != null && lastRun.constraintsProcessed?.isChangeRequested == true) { + protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, UIRunnable { + OKDialog.showConfirmation(activity, resourceHelper.gs(R.string.tempbasal_label), lastRun.constraintsProcessed?.toSpanned() + ?: "".toSpanned(), { + uel.log(Action.ACCEPTS_TEMP_BASAL, Sources.Overview) + binding.buttonsLayout.acceptTempButton.visibility = View.GONE + (context?.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).cancel(Constants.notificationID) + rxBus.send(EventWearInitiateAction("cancelChangeRequest")) + Thread { loopPlugin.acceptChangeRequest() }.run() + }) }) - }) + } } } } - R.id.aps_mode -> { + R.id.aps_mode -> { protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, UIRunnable { if (isAdded) LoopDialog().also { dialog -> dialog.arguments = Bundle().also { it.putInt("showOkCancel", 1) } @@ -391,7 +361,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList return true } - R.id.aps_mode -> { + R.id.aps_mode -> { activity?.let { activity -> protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, UIRunnable { LoopDialog().also { dialog -> @@ -401,8 +371,8 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList } } - R.id.temp_target -> v.performClick() - R.id.active_profile -> activity?.let { activity -> protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, UIRunnable { ProfileSwitchDialog().show(childFragmentManager, "Overview") }) } + R.id.temp_target -> v.performClick() + R.id.active_profile -> activity?.let { activity -> protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, UIRunnable { ProfileSwitchDialog().show(childFragmentManager, "Overview") }) } } return false @@ -490,133 +460,11 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList } - private fun prepareGraphsIfNeeded(numOfGraphs: Int) { - synchronized(graphLock) { - if (numOfGraphs != secondaryGraphs.size - 1) { - //aapsLogger.debug("New secondary graph count ${numOfGraphs-1}") - // rebuild needed - secondaryGraphs.clear() - secondaryGraphsLabel.clear() - binding.graphsLayout.iobGraph.removeAllViews() - for (i in 1 until numOfGraphs) { - val relativeLayout = RelativeLayout(context) - relativeLayout.layoutParams = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) - - val graph = GraphView(context) - graph.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, resourceHelper.dpToPx(skinProvider.activeSkin().secondaryGraphHeight)).also { it.setMargins(0, resourceHelper.dpToPx(15), 0, resourceHelper.dpToPx(10)) } - graph.gridLabelRenderer?.gridColor = resourceHelper.gc(R.color.graphgrid) - graph.gridLabelRenderer?.reloadStyles() - graph.gridLabelRenderer?.isHorizontalLabelsVisible = false - graph.gridLabelRenderer?.labelVerticalWidth = axisWidth - graph.gridLabelRenderer?.numVerticalLabels = 3 - graph.viewport.backgroundColor = Color.argb(20, 255, 255, 255) // 8% of gray - relativeLayout.addView(graph) - - val label = TextView(context) - val layoutParams = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT).also { it.setMargins(resourceHelper.dpToPx(30), resourceHelper.dpToPx(25), 0, 0) } - layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP) - layoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT) - label.layoutParams = layoutParams - relativeLayout.addView(label) - secondaryGraphsLabel.add(label) - - binding.graphsLayout.iobGraph.addView(relativeLayout) - secondaryGraphs.add(graph) - } - } - } - } - - var task: Runnable? = null - - private fun scheduleUpdateGUI(from: String) { - class UpdateRunnable : Runnable { - - override fun run() { - updateGUI(from) - task = null - } - } - view?.removeCallbacks(task) - task = UpdateRunnable() - view?.postDelayed(task, 500) - } - - @SuppressLint("SetTextI18n") - fun updateGUI(from: String) { - if (_binding == null) return - aapsLogger.debug("UpdateGUI from $from") - - binding.infoLayout.time.text = dateUtil.timeString(dateUtil.now()) - - if (!profileFunction.isProfileValid("Overview")) { - binding.loopPumpStatusLayout.pumpStatus.setText(R.string.noprofileset) - binding.loopPumpStatusLayout.pumpStatusLayout.visibility = View.VISIBLE - binding.loopPumpStatusLayout.loopLayout.visibility = View.GONE - return - } - binding.notifications.let { notificationStore.updateNotifications(it) } - binding.loopPumpStatusLayout.pumpStatusLayout.visibility = View.GONE - binding.loopPumpStatusLayout.loopLayout.visibility = View.VISIBLE - - val profile = profileFunction.getProfile() ?: return - val actualBG = iobCobCalculator.ads.actualBg() - val lastBG = iobCobCalculator.ads.lastBg() + private fun processAps() { val pump = activePlugin.activePump - val units = profileFunction.getUnits() - val lowLine = defaultValueHelper.determineLowLine() - val highLine = defaultValueHelper.determineHighLine() - val lastRun = loopPlugin.lastRun - val predictionsAvailable = if (config.APS) lastRun?.request?.hasPredictions == true else config.NSCLIENT - - try { - updateGraph(lastRun, predictionsAvailable, lowLine, highLine, pump, profile) - } catch (e: IllegalStateException) { - return // view no longer exists - } - - //Start with updating the BG as it is unaffected by loop. - // **** BG value **** - if (lastBG != null) { - val color = when { - lastBG.valueToUnits(units) < lowLine -> resourceHelper.gc(R.color.low) - lastBG.valueToUnits(units) > highLine -> resourceHelper.gc(R.color.high) - else -> resourceHelper.gc(R.color.inrange) - } - - binding.infoLayout.bg.text = lastBG.valueToUnitsString(units) - binding.infoLayout.bg.setTextColor(color) - binding.infoLayout.arrow.setImageResource(trendCalculator.getTrendArrow(lastBG).directionToIcon()) - binding.infoLayout.arrow.setColorFilter(color) - - val glucoseStatus = glucoseStatusProvider.glucoseStatusData - if (glucoseStatus != null) { - binding.infoLayout.deltaLarge.text = Profile.toSignedUnitsString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units) - binding.infoLayout.deltaLarge.setTextColor(color) - binding.infoLayout.delta.text = Profile.toSignedUnitsString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units) - binding.infoLayout.avgDelta.text = Profile.toSignedUnitsString(glucoseStatus.shortAvgDelta, glucoseStatus.shortAvgDelta * Constants.MGDL_TO_MMOLL, units) - binding.infoLayout.longAvgDelta.text = Profile.toSignedUnitsString(glucoseStatus.longAvgDelta, glucoseStatus.longAvgDelta * Constants.MGDL_TO_MMOLL, units) - } else { - binding.infoLayout.delta.text = "Δ " + resourceHelper.gs(R.string.notavailable) - binding.infoLayout.avgDelta.text = "" - binding.infoLayout.longAvgDelta.text = "" - } - - // strike through if BG is old - binding.infoLayout.bg.let { overview_bg -> - var flag = overview_bg.paintFlags - flag = if (actualBG == null) { - flag or Paint.STRIKE_THRU_TEXT_FLAG - } else flag and Paint.STRIKE_THRU_TEXT_FLAG.inv() - overview_bg.paintFlags = flag - } - binding.infoLayout.timeAgo.text = dateUtil.minAgo(resourceHelper, lastBG.timestamp) - binding.infoLayout.timeAgoShort.text = "(" + dateUtil.minAgoShort(lastBG.timestamp) + ")" - - } - val closedLoopEnabled = constraintChecker.isClosedLoopAllowed() // aps mode + val closedLoopEnabled = constraintChecker.isClosedLoopAllowed() if (config.APS && pump.pumpDescription.isTempBasalCapable) { binding.infoLayout.apsMode.visibility = View.VISIBLE binding.infoLayout.timeLayout.visibility = View.GONE @@ -677,255 +525,267 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList binding.infoLayout.timeLayout.visibility = View.VISIBLE } - // temp target - val tempTarget: ValueWrapper = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet() - if (tempTarget is ValueWrapper.Existing) { - binding.loopPumpStatusLayout.tempTarget.setTextColor(resourceHelper.gc(R.color.ribbonTextWarning)) - binding.loopPumpStatusLayout.tempTarget.setBackgroundColor(resourceHelper.gc(R.color.ribbonWarning)) - binding.loopPumpStatusLayout.tempTarget.text = Profile.toTargetRangeString(tempTarget.value.lowTarget, tempTarget.value.highTarget, GlucoseUnit.MGDL, units) + " " + dateUtil.untilString(tempTarget.value.end, resourceHelper) - } else { - // If the target is not the same as set in the profile then oref has overridden it - val targetUsed = lastRun?.constraintsProcessed?.targetBG ?: 0.0 - - if (targetUsed != 0.0 && abs(profile.getTargetMgdl() - targetUsed) > 0.01) { - aapsLogger.debug("Adjusted target. Profile: ${profile.getTargetMgdl()} APS: $targetUsed") - binding.loopPumpStatusLayout.tempTarget.text = Profile.toTargetRangeString(targetUsed, targetUsed, GlucoseUnit.MGDL, units) - binding.loopPumpStatusLayout.tempTarget.setTextColor(resourceHelper.gc(R.color.ribbonTextWarning)) - binding.loopPumpStatusLayout.tempTarget.setBackgroundColor(resourceHelper.gc(R.color.tempTargetBackground)) - } else { - binding.loopPumpStatusLayout.tempTarget.setTextColor(resourceHelper.gc(R.color.ribbonTextDefault)) - binding.loopPumpStatusLayout.tempTarget.setBackgroundColor(resourceHelper.gc(R.color.ribbonDefault)) - binding.loopPumpStatusLayout.tempTarget.text = Profile.toTargetRangeString(profile.getTargetLowMgdl(), profile.getTargetHighMgdl(), GlucoseUnit.MGDL, units) - } - } + // pump status from ns + binding.pump.text = nsDeviceStatus.pumpStatus + binding.pump.setOnClickListener { activity?.let { OKDialog.show(it, resourceHelper.gs(R.string.pump), nsDeviceStatus.extendedPumpStatus) } } - // Basal, TBR - val activeTemp = iobCobCalculator.getTempBasalIncludingConvertedExtended(System.currentTimeMillis()) - binding.infoLayout.baseBasal.text = activeTemp?.let { "T:" + activeTemp.toStringShort() } - ?: resourceHelper.gs(R.string.pump_basebasalrate, profile.getBasal()) - binding.infoLayout.basalLayout.setOnClickListener { - var fullText = "${resourceHelper.gs(R.string.basebasalrate_label)}: ${resourceHelper.gs(R.string.pump_basebasalrate, profile.getBasal())}" - if (activeTemp != null) - fullText += "\n" + resourceHelper.gs(R.string.tempbasal_label) + ": " + activeTemp.toStringFull(profile, dateUtil) - activity?.let { - OKDialog.show(it, resourceHelper.gs(R.string.basal), fullText) - } - } - binding.infoLayout.baseBasal.setTextColor(activeTemp?.let { resourceHelper.gc(R.color.basal) } - ?: resourceHelper.gc(R.color.defaulttextcolor)) - - binding.infoLayout.baseBasalIcon.setImageResource(R.drawable.ic_cp_basal_no_tbr) - val percentRate = activeTemp?.convertedToPercent(System.currentTimeMillis(), profile) - ?: 100 - if (percentRate > 100) binding.infoLayout.baseBasalIcon.setImageResource(R.drawable.ic_cp_basal_tbr_high) - if (percentRate < 100) binding.infoLayout.baseBasalIcon.setImageResource(R.drawable.ic_cp_basal_tbr_low) - - // Extended bolus - val extendedBolus = repository.getExtendedBolusActiveAt(dateUtil.now()).blockingGet() - binding.infoLayout.extendedBolus.text = - if (extendedBolus is ValueWrapper.Existing && !pump.isFakingTempsByExtendedBoluses) - resourceHelper.gs(R.string.pump_basebasalrate, extendedBolus.value.rate) - else "" - binding.infoLayout.extendedBolus.setOnClickListener { - if (extendedBolus is ValueWrapper.Existing) activity?.let { - OKDialog.show(it, resourceHelper.gs(R.string.extended_bolus), extendedBolus.value.toStringFull(dateUtil)) - } - } - binding.infoLayout.extendedLayout.visibility = (extendedBolus is ValueWrapper.Existing && !pump.isFakingTempsByExtendedBoluses).toVisibility() + // OpenAPS status from ns + binding.openaps.text = nsDeviceStatus.openApsStatus + binding.openaps.setOnClickListener { activity?.let { OKDialog.show(it, resourceHelper.gs(R.string.openaps), nsDeviceStatus.extendedOpenApsStatus) } } - // Active profile - binding.loopPumpStatusLayout.activeProfile.text = profileFunction.getProfileNameWithRemainingTime() - if (profile.percentage != 100 || profile.timeshift != 0) { - binding.loopPumpStatusLayout.activeProfile.setBackgroundColor(resourceHelper.gc(R.color.ribbonWarning)) - binding.loopPumpStatusLayout.activeProfile.setTextColor(resourceHelper.gc(R.color.ribbonTextWarning)) - } else { - binding.loopPumpStatusLayout.activeProfile.setBackgroundColor(resourceHelper.gc(R.color.ribbonDefault)) - binding.loopPumpStatusLayout.activeProfile.setTextColor(resourceHelper.gc(R.color.ribbonTextDefault)) + // Uploader status from ns + binding.uploader.text = nsDeviceStatus.uploaderStatusSpanned + binding.uploader.setOnClickListener { activity?.let { OKDialog.show(it, resourceHelper.gs(R.string.uploader), nsDeviceStatus.extendedUploaderStatus) } } + } + + private fun prepareGraphsIfNeeded(numOfGraphs: Int) { + if (numOfGraphs != secondaryGraphs.size - 1) { + //aapsLogger.debug("New secondary graph count ${numOfGraphs-1}") + // rebuild needed + secondaryGraphs.clear() + secondaryGraphsLabel.clear() + binding.graphsLayout.iobGraph.removeAllViews() + for (i in 1 until numOfGraphs) { + val relativeLayout = RelativeLayout(context) + relativeLayout.layoutParams = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + + val graph = GraphView(context) + graph.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, resourceHelper.dpToPx(skinProvider.activeSkin().secondaryGraphHeight)).also { it.setMargins(0, resourceHelper.dpToPx(15), 0, resourceHelper.dpToPx(10)) } + graph.gridLabelRenderer?.gridColor = resourceHelper.gc(R.color.graphgrid) + graph.gridLabelRenderer?.reloadStyles() + graph.gridLabelRenderer?.isHorizontalLabelsVisible = false + graph.gridLabelRenderer?.labelVerticalWidth = axisWidth + graph.gridLabelRenderer?.numVerticalLabels = 3 + graph.viewport.backgroundColor = Color.argb(20, 255, 255, 255) // 8% of gray + relativeLayout.addView(graph) + + val label = TextView(context) + val layoutParams = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT).also { it.setMargins(resourceHelper.dpToPx(30), resourceHelper.dpToPx(25), 0, 0) } + layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP) + layoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT) + label.layoutParams = layoutParams + relativeLayout.addView(label) + secondaryGraphsLabel.add(label) + + binding.graphsLayout.iobGraph.addView(relativeLayout) + secondaryGraphs.add(graph) + } } + } - processButtonsVisibility() + var task: Runnable? = null - // iob - val bolusIob = iobCobCalculator.calculateIobFromBolus().round() - val basalIob = iobCobCalculator.calculateIobFromTempBasalsIncludingConvertedExtended().round() - binding.infoLayout.iob.text = resourceHelper.gs(R.string.formatinsulinunits, bolusIob.iob + basalIob.basaliob) + private fun scheduleUpdateGUI(from: String) { + class UpdateRunnable : Runnable { - binding.infoLayout.iobLayout.setOnClickListener { - activity?.let { - OKDialog.show(it, resourceHelper.gs(R.string.iob), - resourceHelper.gs(R.string.formatinsulinunits, bolusIob.iob + basalIob.basaliob) + "\n" + - resourceHelper.gs(R.string.bolus) + ": " + resourceHelper.gs(R.string.formatinsulinunits, bolusIob.iob) + "\n" + - resourceHelper.gs(R.string.basal) + ": " + resourceHelper.gs(R.string.formatinsulinunits, basalIob.basaliob) - ) + override fun run() { + overviewPlugin.refreshLoop(from) + task = null } } + handler.removeCallbacks(task) + task = UpdateRunnable() + handler.postDelayed(task, 500) + } - // Status lights - binding.statusLightsLayout.statusLights.visibility = (sp.getBoolean(R.string.key_show_statuslights, true) || config.NSCLIENT).toVisibility() - statusLightHandler.updateStatusLights(binding.statusLightsLayout.cannulaAge, binding.statusLightsLayout.insulinAge, binding.statusLightsLayout.reservoirLevel, binding.statusLightsLayout.sensorAge, null, binding.statusLightsLayout.pbAge, binding.statusLightsLayout.batteryLevel) - - // cob - var cobText: String = resourceHelper.gs(R.string.value_unavailable_short) - val cobInfo = iobCobCalculator.getCobInfo(false, "Overview COB") - if (cobInfo.displayCob != null) { - cobText = resourceHelper.gs(R.string.format_carbs, cobInfo.displayCob!!.toInt()) - if (cobInfo.futureCarbs > 0) cobText += "(" + DecimalFormatter.to0Decimal(cobInfo.futureCarbs) + ")" + @Suppress("UNUSED_PARAMETER") + @SuppressLint("SetTextI18n") + fun updateGUI(from: String, what: OverviewData.Property) { +// if (what != OverviewData.Property.CALC_PROGRESS) +// aapsLogger.debug(LTag.UI, "UpdateGui $from $what") + if (overviewData.profile == null) { + binding.loopPumpStatusLayout.pumpStatus.setText(R.string.noprofileset) + binding.loopPumpStatusLayout.pumpStatusLayout.visibility = View.VISIBLE + binding.loopPumpStatusLayout.loopLayout.visibility = View.GONE + return } + binding.notifications.let { notificationStore.updateNotifications(it) } + binding.loopPumpStatusLayout.pumpStatusLayout.visibility = View.GONE + binding.loopPumpStatusLayout.loopLayout.visibility = View.VISIBLE - if (config.APS && lastRun?.constraintsProcessed != null) { - if (lastRun.constraintsProcessed!!.carbsReq > 0) { - //only display carbsreq when carbs have not been entered recently - val lastCarb = repository.getLastCarbsRecordWrapped().blockingGet() - val lastCarbsTime = if (lastCarb is ValueWrapper.Existing) lastCarb.value.timestamp else 0L - if (lastCarbsTime < lastRun.lastAPSRun) { - cobText = cobText + " | " + lastRun.constraintsProcessed!!.carbsReq + " " + resourceHelper.gs(R.string.required) + val units = profileFunction.getUnits() + val pump = activePlugin.activePump + when (what) { + OverviewData.Property.BG -> { + binding.infoLayout.bg.text = overviewData.lastBg?.valueToUnitsString(units) + ?: resourceHelper.gs(R.string.notavailable) + binding.infoLayout.bg.setTextColor(overviewData.lastBgColor) + binding.infoLayout.arrow.setImageResource(trendCalculator.getTrendArrow(overviewData.lastBg).directionToIcon()) + binding.infoLayout.arrow.setColorFilter(overviewData.lastBgColor) + + val glucoseStatus = glucoseStatusProvider.glucoseStatusData + if (glucoseStatus != null) { + binding.infoLayout.deltaLarge.text = Profile.toSignedUnitsString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units) + binding.infoLayout.deltaLarge.setTextColor(overviewData.lastBgColor) + binding.infoLayout.delta.text = Profile.toSignedUnitsString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units) + binding.infoLayout.avgDelta.text = Profile.toSignedUnitsString(glucoseStatus.shortAvgDelta, glucoseStatus.shortAvgDelta * Constants.MGDL_TO_MMOLL, units) + binding.infoLayout.longAvgDelta.text = Profile.toSignedUnitsString(glucoseStatus.longAvgDelta, glucoseStatus.longAvgDelta * Constants.MGDL_TO_MMOLL, units) + } else { + binding.infoLayout.deltaLarge.text = "" + binding.infoLayout.delta.text = "Δ " + resourceHelper.gs(R.string.notavailable) + binding.infoLayout.avgDelta.text = "" + binding.infoLayout.longAvgDelta.text = "" } - binding.infoLayout.cob.text = cobText - if (carbAnimation?.isRunning == false) - carbAnimation?.start() - } else { - binding.infoLayout.cob.text = cobText - carbAnimation?.stop() - carbAnimation?.selectDrawable(0) + + // strike through if BG is old + binding.infoLayout.bg.paintFlags = + if (!overviewData.isActualBg) binding.infoLayout.bg.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG + else binding.infoLayout.bg.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv() + binding.infoLayout.timeAgo.text = dateUtil.minAgo(resourceHelper, overviewData.lastBg?.timestamp) + binding.infoLayout.timeAgoShort.text = "(" + dateUtil.minAgoShort(overviewData.lastBg?.timestamp) + ")" } - } else binding.infoLayout.cob.text = cobText - // pump status from ns - binding.pump.text = nsDeviceStatus.pumpStatus - binding.pump.setOnClickListener { activity?.let { OKDialog.show(it, resourceHelper.gs(R.string.pump), nsDeviceStatus.extendedPumpStatus) } } + OverviewData.Property.PROFILE -> { + binding.loopPumpStatusLayout.activeProfile.text = overviewData.profileNameWithRemainingTime + ?: "" + binding.loopPumpStatusLayout.activeProfile.setBackgroundColor(overviewData.profileBackgroundColor) + binding.loopPumpStatusLayout.activeProfile.setTextColor(overviewData.profileTextColor) + } - // OpenAPS status from ns - binding.openaps.text = nsDeviceStatus.openApsStatus - binding.openaps.setOnClickListener { activity?.let { OKDialog.show(it, resourceHelper.gs(R.string.openaps), nsDeviceStatus.extendedOpenApsStatus) } } + OverviewData.Property.TEMPORARY_BASAL -> { + binding.infoLayout.baseBasal.text = overviewData.temporaryBasalText + binding.infoLayout.baseBasal.setTextColor(overviewData.temporaryBasalColor) + binding.infoLayout.baseBasalIcon.setImageResource(overviewData.temporaryBasalIcon) + binding.infoLayout.basalLayout.setOnClickListener { + activity?.let { OKDialog.show(it, resourceHelper.gs(R.string.basal), overviewData.temporaryBasalDialogText) } + } + } - // Uploader status from ns - binding.uploader.text = nsDeviceStatus.uploaderStatusSpanned - binding.uploader.setOnClickListener { activity?.let { OKDialog.show(it, resourceHelper.gs(R.string.uploader), nsDeviceStatus.extendedUploaderStatus) } } + OverviewData.Property.EXTENDED_BOLUS -> { + binding.infoLayout.extendedBolus.text = overviewData.extendedBolusText + binding.infoLayout.extendedBolus.setOnClickListener { + activity?.let { OKDialog.show(it, resourceHelper.gs(R.string.extended_bolus), overviewData.extendedBolusDialogText) } + } + binding.infoLayout.extendedLayout.visibility = (overviewData.extendedBolus != null && !pump.isFakingTempsByExtendedBoluses).toVisibility() - // Sensitivity - if (sp.getBoolean(R.string.key_openapsama_useautosens, false) && constraintChecker.isAutosensModeEnabled().value()) { - binding.infoLayout.sensitivityIcon.setImageResource(R.drawable.ic_swap_vert_black_48dp_green) - } else { - binding.infoLayout.sensitivityIcon.setImageResource(R.drawable.ic_x_swap_vert) - } + } - binding.infoLayout.sensitivity.text = - iobCobCalculator.ads.getLastAutosensData("Overview", aapsLogger, dateUtil)?.let { autosensData -> - String.format(Locale.ENGLISH, "%.0f%%", autosensData.autosensResult.ratio * 100) - } ?: "" - } + OverviewData.Property.TIME -> { + binding.infoLayout.time.text = dateUtil.timeString(dateUtil.now()) + // Status lights + binding.statusLightsLayout.statusLights.visibility = (sp.getBoolean(R.string.key_show_statuslights, true) || config.NSCLIENT).toVisibility() + statusLightHandler.updateStatusLights(binding.statusLightsLayout.cannulaAge, binding.statusLightsLayout.insulinAge, binding.statusLightsLayout.reservoirLevel, binding.statusLightsLayout.sensorAge, null, binding.statusLightsLayout.pbAge, binding.statusLightsLayout.batteryLevel) + processButtonsVisibility() + processAps() + } - private fun updateGraph(lastRun: LoopInterface.LastRun?, predictionsAvailable: Boolean, lowLine: Double, highLine: Double, pump: Pump, profile: Profile) { - viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) { - if (_binding == null) return@launch - val menuChartSettings = overviewMenus.setting - prepareGraphsIfNeeded(menuChartSettings.size) - val graphData = GraphData(injector, binding.graphsLayout.bgGraph, iobCobCalculator) - val secondaryGraphsData: ArrayList = ArrayList() - - // do preparation in different thread - withContext(Dispatchers.Default) { - // align to hours - val calendar = Calendar.getInstance() - calendar.timeInMillis = System.currentTimeMillis() - calendar[Calendar.MILLISECOND] = 0 - calendar[Calendar.SECOND] = 0 - calendar[Calendar.MINUTE] = 0 - calendar.add(Calendar.HOUR, 1) - val hoursToFetch: Int - val toTime: Long - val fromTime: Long - val endTime: Long - val apsResult = if (config.APS) lastRun?.constraintsProcessed else nsDeviceStatus.getAPSResult(injector) - if (predictionsAvailable && apsResult != null && menuChartSettings[0][OverviewMenus.CharType.PRE.ordinal]) { - var predictionHours = (ceil(apsResult.latestPredictionsTime - System.currentTimeMillis().toDouble()) / (60 * 60 * 1000)).toInt() - predictionHours = min(2, predictionHours) - predictionHours = max(0, predictionHours) - hoursToFetch = rangeToDisplay - predictionHours - toTime = calendar.timeInMillis + 100000 // little bit more to avoid wrong rounding - GraphView specific - fromTime = toTime - T.hours(hoursToFetch.toLong()).msecs() - endTime = toTime + T.hours(predictionHours.toLong()).msecs() - } else { - hoursToFetch = rangeToDisplay - toTime = calendar.timeInMillis + 100000 // little bit more to avoid wrong rounding - GraphView specific - fromTime = toTime - T.hours(hoursToFetch.toLong()).msecs() - endTime = toTime + OverviewData.Property.IOB_COB -> { + binding.infoLayout.iob.text = overviewData.iobText + binding.infoLayout.iobLayout.setOnClickListener { + activity?.let { OKDialog.show(it, resourceHelper.gs(R.string.iob), overviewData.iobDialogText) } } - val now = System.currentTimeMillis() - - // ------------------ 1st graph + // cob + var cobText: String = resourceHelper.gs(R.string.value_unavailable_short) + overviewData.cobInfo?.let { cobInfo -> + if (cobInfo.displayCob != null) { + cobText = resourceHelper.gs(R.string.format_carbs, cobInfo.displayCob!!.toInt()) + if (cobInfo.futureCarbs > 0) cobText += "(" + DecimalFormatter.to0Decimal(cobInfo.futureCarbs) + ")" + } + } + binding.infoLayout.cob.text = cobText - // **** In range Area **** - graphData.addInRangeArea(fromTime, endTime, lowLine, highLine) + val constraintsProcessed = loopPlugin.lastRun?.constraintsProcessed + val lastRun = loopPlugin.lastRun + if (config.APS && constraintsProcessed != null && lastRun != null) { + if (constraintsProcessed.carbsReq > 0) { + //only display carbsreq when carbs have not been entered recently + if (overviewData.lastCarbsTime < lastRun.lastAPSRun) { + cobText = cobText + " | " + constraintsProcessed.carbsReq + " " + resourceHelper.gs(R.string.required) + } + if (carbAnimation?.isRunning == false) + carbAnimation?.start() + } else { + carbAnimation?.stop() + carbAnimation?.selectDrawable(0) + } + } + } - // **** BG **** - if (predictionsAvailable && menuChartSettings[0][OverviewMenus.CharType.PRE.ordinal]) - graphData.addBgReadings(fromTime, toTime, highLine, apsResult?.predictions?.map { bg-> GlucoseValueDataPoint(bg, defaultValueHelper, profileFunction, resourceHelper) }?.toMutableList()) - else graphData.addBgReadings(fromTime, toTime, highLine, null) - if (buildHelper.isDev()) graphData.addBucketedData(fromTime, toTime) + OverviewData.Property.TEMPORARY_TARGET -> { + // temp target + if (overviewData.temporaryTarget?.isInProgress(dateUtil) == false) overviewData.temporaryTarget = null + val tempTarget = overviewData.temporaryTarget + if (tempTarget != null) { + binding.loopPumpStatusLayout.tempTarget.setTextColor(resourceHelper.gc(R.color.ribbonTextWarning)) + binding.loopPumpStatusLayout.tempTarget.setBackgroundColor(resourceHelper.gc(R.color.ribbonWarning)) + binding.loopPumpStatusLayout.tempTarget.text = Profile.toTargetRangeString(tempTarget.lowTarget, tempTarget.highTarget, GlucoseUnit.MGDL, units) + " " + dateUtil.untilString(tempTarget.end, resourceHelper) + } else { + // If the target is not the same as set in the profile then oref has overridden it + overviewData.profile?.let { profile -> + val targetUsed = loopPlugin.lastRun?.constraintsProcessed?.targetBG ?: 0.0 + + if (targetUsed != 0.0 && abs(profile.getTargetMgdl() - targetUsed) > 0.01) { + aapsLogger.debug("Adjusted target. Profile: ${profile.getTargetMgdl()} APS: $targetUsed") + binding.loopPumpStatusLayout.tempTarget.text = Profile.toTargetRangeString(targetUsed, targetUsed, GlucoseUnit.MGDL, units) + binding.loopPumpStatusLayout.tempTarget.setTextColor(resourceHelper.gc(R.color.ribbonTextWarning)) + binding.loopPumpStatusLayout.tempTarget.setBackgroundColor(resourceHelper.gc(R.color.tempTargetBackground)) + } else { + binding.loopPumpStatusLayout.tempTarget.setTextColor(resourceHelper.gc(R.color.ribbonTextDefault)) + binding.loopPumpStatusLayout.tempTarget.setBackgroundColor(resourceHelper.gc(R.color.ribbonDefault)) + binding.loopPumpStatusLayout.tempTarget.text = Profile.toTargetRangeString(profile.getTargetLowMgdl(), profile.getTargetHighMgdl(), GlucoseUnit.MGDL, units) + } + } + } + } - // Treatments - graphData.addTreatments(fromTime, endTime) + OverviewData.Property.GRAPH -> { + val graphData = GraphData(injector, binding.graphsLayout.bgGraph, overviewData) + val menuChartSettings = overviewMenus.setting + graphData.addInRangeArea(overviewData.fromTime, overviewData.endTime, defaultValueHelper.determineLowLine(), defaultValueHelper.determineHighLine()) + graphData.addBgReadings(menuChartSettings[0][OverviewMenus.CharType.PRE.ordinal]) + if (buildHelper.isDev()) graphData.addBucketedData() + graphData.addTreatments() + if (menuChartSettings[0][OverviewMenus.CharType.ACT.ordinal]) + graphData.addActivity(0.8) + if (pump.pumpDescription.isTempBasalCapable && menuChartSettings[0][OverviewMenus.CharType.BAS.ordinal]) + graphData.addBasals() + graphData.addTargetLine() + graphData.addNowLine(dateUtil.now()) // set manual x bounds to have nice steps graphData.setNumVerticalLabels() - graphData.formatAxis(fromTime, endTime) + graphData.formatAxis(overviewData.fromTime, overviewData.endTime) + graphData.performUpdate() - if (menuChartSettings[0][OverviewMenus.CharType.ACT.ordinal]) - graphData.addActivity(fromTime, endTime, false, 0.8) + // 2nd graphs + prepareGraphsIfNeeded(menuChartSettings.size) + val secondaryGraphsData: ArrayList = ArrayList() - // add basal data - if (pump.pumpDescription.isTempBasalCapable && menuChartSettings[0][OverviewMenus.CharType.BAS.ordinal]) - graphData.addBasals(fromTime, now, lowLine / graphData.maxY / 1.2) - - // add target line - graphData.addTargetLine(fromTime, toTime, profile, loopPlugin.lastRun) - - // **** NOW line **** - graphData.addNowLine(now) - - // ------------------ 2nd graph - synchronized(graphLock) { - for (g in 0 until min(secondaryGraphs.size, menuChartSettings.size + 1)) { - val secondGraphData = GraphData(injector, secondaryGraphs[g], iobCobCalculator) - var useABSForScale = false - var useIobForScale = false - var useCobForScale = false - var useDevForScale = false - var useRatioForScale = false - var useDSForScale = false - var useBGIForScale = false - when { - menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] -> useABSForScale = true - menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal] -> useIobForScale = true - menuChartSettings[g + 1][OverviewMenus.CharType.COB.ordinal] -> useCobForScale = true - menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] -> useDevForScale = true - menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] -> useBGIForScale = true - menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal] -> useRatioForScale = true - menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] -> useDSForScale = true - } - val alignIobScale = menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal] - val alignDevBgiScale = menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] - - if (menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal]) secondGraphData.addAbsIob(fromTime, now, useABSForScale, 1.0) - if (menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal]) secondGraphData.addIob(fromTime, now, useIobForScale, 1.0, menuChartSettings[g + 1][OverviewMenus.CharType.PRE.ordinal], alignIobScale) - if (menuChartSettings[g + 1][OverviewMenus.CharType.COB.ordinal]) secondGraphData.addCob(fromTime, now, useCobForScale, if (useCobForScale) 1.0 else 0.5) - if (menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal]) secondGraphData.addDeviations(fromTime, now, useDevForScale, 1.0, alignDevBgiScale) - if (menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal]) secondGraphData.addMinusBGI(fromTime, endTime, useBGIForScale, if (alignDevBgiScale) 1.0 else 0.8, alignDevBgiScale) - if (menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal]) secondGraphData.addRatio(fromTime, now, useRatioForScale, if (useRatioForScale) 1.0 else 0.8) - if (menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] && buildHelper.isDev()) secondGraphData.addDeviationSlope(fromTime, now, useDSForScale, 1.0) - - // set manual x bounds to have nice steps - secondGraphData.formatAxis(fromTime, endTime) - secondGraphData.addNowLine(now) - secondaryGraphsData.add(secondGraphData) + val now = System.currentTimeMillis() + for (g in 0 until min(secondaryGraphs.size, menuChartSettings.size + 1)) { + val secondGraphData = GraphData(injector, secondaryGraphs[g], overviewData) + var useABSForScale = false + var useIobForScale = false + var useCobForScale = false + var useDevForScale = false + var useRatioForScale = false + var useDSForScale = false + var useBGIForScale = false + when { + menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] -> useABSForScale = true + menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal] -> useIobForScale = true + menuChartSettings[g + 1][OverviewMenus.CharType.COB.ordinal] -> useCobForScale = true + menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] -> useDevForScale = true + menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] -> useBGIForScale = true + menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal] -> useRatioForScale = true + menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] -> useDSForScale = true } + val alignDevBgiScale = menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] + + if (menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal]) secondGraphData.addAbsIob(useABSForScale, 1.0) + if (menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal]) secondGraphData.addIob(useIobForScale, 1.0) + if (menuChartSettings[g + 1][OverviewMenus.CharType.COB.ordinal]) secondGraphData.addCob(useCobForScale, if (useCobForScale) 1.0 else 0.5) + if (menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal]) secondGraphData.addDeviations(useDevForScale, 1.0) + if (menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal]) secondGraphData.addMinusBGI(useBGIForScale, if (alignDevBgiScale) 1.0 else 0.8) + if (menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal]) secondGraphData.addRatio(useRatioForScale, if (useRatioForScale) 1.0 else 0.8) + if (menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] && buildHelper.isDev()) secondGraphData.addDeviationSlope(useDSForScale, if(useDSForScale) 1.0 else 0.8, useRatioForScale) + + // set manual x bounds to have nice steps + secondGraphData.formatAxis(overviewData.fromTime, overviewData.endTime) + secondGraphData.addNowLine(now) + secondaryGraphsData.add(secondGraphData) } - } - // finally enforce drawing of graphs in UI thread - graphData.performUpdate() - synchronized(graphLock) { for (g in 0 until min(secondaryGraphs.size, menuChartSettings.size + 1)) { secondaryGraphsLabel[g].text = overviewMenus.enabledTypes(g + 1) secondaryGraphs[g].visibility = ( @@ -940,6 +800,23 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList secondaryGraphsData[g].performUpdate() } } + + OverviewData.Property.CALC_PROGRESS -> { + binding.graphsLayout.iobCalculationProgress.text = overviewData.calcProgress + } + + OverviewData.Property.SENSITIVITY -> { + if (sp.getBoolean(R.string.key_openapsama_useautosens, false) && constraintChecker.isAutosensModeEnabled().value()) { + binding.infoLayout.sensitivityIcon.setImageResource(R.drawable.ic_swap_vert_black_48dp_green) + } else { + binding.infoLayout.sensitivityIcon.setImageResource(R.drawable.ic_x_swap_vert) + } + + binding.infoLayout.sensitivity.text = + overviewData.lastAutosensData?.let { autosensData -> + String.format(Locale.ENGLISH, "%.0f%%", autosensData.autosensResult.ratio * 100) + } ?: "" + } } } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewMenus.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewMenus.kt index 1dfa7342474..6f7f464d5e7 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewMenus.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewMenus.kt @@ -34,18 +34,19 @@ class OverviewMenus @Inject constructor( enum class CharType(@StringRes val nameId: Int, @ColorRes val colorId: Int, val primary: Boolean, val secondary: Boolean, @StringRes val shortnameId: Int) { PRE(R.string.overview_show_predictions, R.color.prediction, primary = true, secondary = false, shortnameId = R.string.prediction_shortname), - BAS(R.string.overview_show_basals, R.color.basal, primary = true, secondary = false,shortnameId = R.string.basal_shortname), - ABS(R.string.overview_show_absinsulin, R.color.iob, primary = false, secondary = true,shortnameId = R.string.abs_insulin_shortname), - IOB(R.string.overview_show_iob, R.color.iob, primary = false, secondary = true,shortnameId = R.string.iob), - COB(R.string.overview_show_cob, R.color.cob, primary = false, secondary = true,shortnameId = R.string.cob), - DEV(R.string.overview_show_deviations, R.color.bgi, primary = false, secondary = true,shortnameId = R.string.deviation_shortname), - BGI(R.string.overview_show_bgi, R.color.bgi, primary = false, secondary = true,shortnameId = R.string.bgi_shortname), - SEN(R.string.overview_show_sensitivity, R.color.ratio, primary = false, secondary = true,shortnameId = R.string.sensitivity_shortname), - ACT(R.string.overview_show_activity, R.color.activity, primary = true, secondary = false,shortnameId = R.string.activity_shortname), - DEVSLOPE(R.string.overview_show_deviationslope, R.color.devslopepos, primary = false, secondary = true,shortnameId = R.string.devslope_shortname) + BAS(R.string.overview_show_basals, R.color.basal, primary = true, secondary = false, shortnameId = R.string.basal_shortname), + ABS(R.string.overview_show_absinsulin, R.color.iob, primary = false, secondary = true, shortnameId = R.string.abs_insulin_shortname), + IOB(R.string.overview_show_iob, R.color.iob, primary = false, secondary = true, shortnameId = R.string.iob), + COB(R.string.overview_show_cob, R.color.cob, primary = false, secondary = true, shortnameId = R.string.cob), + DEV(R.string.overview_show_deviations, R.color.bgi, primary = false, secondary = true, shortnameId = R.string.deviation_shortname), + BGI(R.string.overview_show_bgi, R.color.bgi, primary = false, secondary = true, shortnameId = R.string.bgi_shortname), + SEN(R.string.overview_show_sensitivity, R.color.ratio, primary = false, secondary = true, shortnameId = R.string.sensitivity_shortname), + ACT(R.string.overview_show_activity, R.color.activity, primary = true, secondary = false, shortnameId = R.string.activity_shortname), + DEVSLOPE(R.string.overview_show_deviationslope, R.color.devslopepos, primary = false, secondary = true, shortnameId = R.string.devslope_shortname) } companion object { + const val MAX_GRAPHS = 5 // including main } @@ -58,12 +59,10 @@ class OverviewMenus @Inject constructor( return r.toString() } - - private var _setting: MutableList> = ArrayList() val setting: List> - get() = _setting.toMutableList() // implicitly does a list copy + get() = _setting.toMutableList() // implicitly does a list copy private fun storeGraphConfig() { val sts = Gson().toJson(_setting) @@ -71,7 +70,7 @@ class OverviewMenus @Inject constructor( aapsLogger.debug(sts) } - private fun loadGraphConfig() { + fun loadGraphConfig() { val sts = sp.getString(R.string.key_graphconfig, "") if (sts.isNotEmpty()) { _setting = Gson().fromJson(sts, Array>::class.java).toMutableList() @@ -88,7 +87,6 @@ class OverviewMenus @Inject constructor( } fun setupChartMenu(chartButton: ImageButton) { - loadGraphConfig() val settingsCopy = setting val numOfGraphs = settingsCopy.size // 1 main + x secondary @@ -100,6 +98,8 @@ class OverviewMenus @Inject constructor( } val popup = PopupMenu(v.context, v) + val used = arrayListOf() + for (g in 0 until numOfGraphs) { if (g != 0 && g < numOfGraphs) { val dividerItem = popup.menu.add(Menu.NONE, g, Menu.NONE, "------- ${resourceHelper.gs(R.string.graph_menu_divider_header)} $g -------") @@ -112,6 +112,10 @@ class OverviewMenus @Inject constructor( var insert = true if (m == CharType.PRE) insert = predictionsAvailable if (m == CharType.DEVSLOPE) insert = buildHelper.isDev() + if (used.contains(m.ordinal)) insert = false + for (g2 in g + 1 until numOfGraphs) { + if (settingsCopy[g2][m.ordinal]) insert = false + } if (insert) { val item = popup.menu.add(Menu.NONE, m.ordinal + 100 * (g + 1), Menu.NONE, resourceHelper.gs(m.nameId)) val title = item.title @@ -120,6 +124,7 @@ class OverviewMenus @Inject constructor( item.title = s item.isCheckable = true item.isChecked = settingsCopy[g][m.ordinal] + if (settingsCopy[g][m.ordinal]) used.add(m.ordinal) } } } @@ -131,16 +136,20 @@ class OverviewMenus @Inject constructor( popup.setOnMenuItemClickListener { // id < 100 graph header - divider 1, 2, 3 ..... - if (it.itemId == numOfGraphs) { - // add new empty - _setting.add(Array(CharType.values().size) { false }) - } else if (it.itemId < 100) { - // remove graph - _setting.removeAt(it.itemId) - } else { - val graphNumber = it.itemId / 100 - 1 - val item = it.itemId % 100 - _setting[graphNumber][item] = !it.isChecked + when { + it.itemId == numOfGraphs -> { + // add new empty + _setting.add(Array(CharType.values().size) { false }) + } + it.itemId < 100 -> { + // remove graph + _setting.removeAt(it.itemId) + } + else -> { + val graphNumber = it.itemId / 100 - 1 + val item = it.itemId % 100 + _setting[graphNumber][item] = !it.isChecked + } } storeGraphConfig() setupChartMenu(chartButton) @@ -153,4 +162,11 @@ class OverviewMenus @Inject constructor( } } + fun isEnabledIn(type: CharType): Int { + val settingsCopy = setting + val numOfGraphs = settingsCopy.size // 1 main + x secondary + for (g in 0 until numOfGraphs) if (settingsCopy[g][type.ordinal]) return g + return -1 + } + } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewPlugin.kt index 8aadd2ea347..97fbb823a1b 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 @@ -3,20 +3,27 @@ package info.nightscout.androidaps.plugins.general.overview import androidx.preference.PreferenceFragmentCompat import androidx.preference.SwitchPreference import dagger.android.HasAndroidInjector -import info.nightscout.androidaps.interfaces.Config import info.nightscout.androidaps.R -import info.nightscout.androidaps.events.EventRefreshOverview +import info.nightscout.androidaps.database.AppRepository +import info.nightscout.androidaps.database.ValueWrapper +import info.nightscout.androidaps.events.* import info.nightscout.androidaps.extensions.* -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.interfaces.* import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.plugins.aps.events.EventLoopInvoked import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification +import info.nightscout.androidaps.plugins.general.overview.events.EventUpdateOverview +import info.nightscout.androidaps.plugins.general.overview.graphExtensions.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.Translator import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.rx.AapsSchedulers import info.nightscout.androidaps.utils.sharedPreferences.SP @@ -28,47 +35,121 @@ import javax.inject.Singleton @Singleton class OverviewPlugin @Inject constructor( - injector: HasAndroidInjector, - private val notificationStore: NotificationStore, - private val fabricPrivacy: FabricPrivacy, - private val rxBus: RxBusWrapper, - private val sp: SP, - aapsLogger: AAPSLogger, - private val aapsSchedulers: AapsSchedulers, - resourceHelper: ResourceHelper, - private val config: Config + injector: HasAndroidInjector, + private val notificationStore: NotificationStore, + private val fabricPrivacy: FabricPrivacy, + private val rxBus: RxBusWrapper, + private val sp: SP, + aapsLogger: AAPSLogger, + private val aapsSchedulers: AapsSchedulers, + resourceHelper: ResourceHelper, + private val config: Config, + private val dateUtil: DateUtil, + private val translator: Translator, +// private val profiler: Profiler, + private val profileFunction: ProfileFunction, + private val iobCobCalculator: IobCobCalculator, + private val repository: AppRepository, + private val overviewData: OverviewData, + private val overviewMenus: OverviewMenus ) : PluginBase(PluginDescription() - .mainType(PluginType.GENERAL) - .fragmentClass(OverviewFragment::class.qualifiedName) - .alwaysVisible(true) - .alwaysEnabled(true) - .pluginIcon(R.drawable.ic_home) - .pluginName(R.string.overview) - .shortName(R.string.overview_shortname) - .preferencesId(R.xml.pref_overview) - .description(R.string.description_overview), - aapsLogger, resourceHelper, injector + .mainType(PluginType.GENERAL) + .fragmentClass(OverviewFragment::class.qualifiedName) + .alwaysVisible(true) + .alwaysEnabled(true) + .pluginIcon(R.drawable.ic_home) + .pluginName(R.string.overview) + .shortName(R.string.overview_shortname) + .preferencesId(R.xml.pref_overview) + .description(R.string.description_overview), + aapsLogger, resourceHelper, injector ), Overview { private var disposable: CompositeDisposable = CompositeDisposable() + override val overviewBus = RxBusWrapper(aapsSchedulers) + + class DeviationDataPoint(x: Double, y: Double, var color: Int, scale: Scale) : ScaledDataPoint(x, y, scale) + override fun onStart() { super.onStart() + overviewMenus.loadGraphConfig() + overviewData.initRange() + notificationStore.createNotificationChannel() disposable += rxBus - .toObservable(EventNewNotification::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ n -> - if (notificationStore.add(n.notification)) - rxBus.send(EventRefreshOverview("EventNewNotification")) - }, fabricPrivacy::logException) + .toObservable(EventNewNotification::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ n -> + if (notificationStore.add(n.notification)) + rxBus.send(EventRefreshOverview("EventNewNotification")) + }, fabricPrivacy::logException) + disposable += rxBus + .toObservable(EventDismissNotification::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ n -> + if (notificationStore.remove(n.id)) + rxBus.send(EventRefreshOverview("EventDismissNotification")) + }, fabricPrivacy::logException) + disposable += rxBus + .toObservable(EventIobCalculationProgress::class.java) + .observeOn(aapsSchedulers.main) + .subscribe({ overviewData.calcProgress = it.progress; overviewBus.send(EventUpdateOverview("EventIobCalculationProgress", OverviewData.Property.CALC_PROGRESS)) }, fabricPrivacy::logException) + disposable += rxBus + .toObservable(EventTempBasalChange::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ loadTemporaryBasal("EventTempBasalChange") }, fabricPrivacy::logException) + disposable += rxBus + .toObservable(EventExtendedBolusChange::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ loadExtendedBolus("EventExtendedBolusChange") }, fabricPrivacy::logException) + disposable += rxBus + .toObservable(EventNewBG::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ loadBg("EventNewBG") }, fabricPrivacy::logException) + disposable += rxBus + .toObservable(EventTempTargetChange::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ loadTemporaryTarget("EventTempTargetChange") }, fabricPrivacy::logException) disposable += rxBus - .toObservable(EventDismissNotification::class.java) - .observeOn(aapsSchedulers.io) - .subscribe({ n -> - if (notificationStore.remove(n.id)) - rxBus.send(EventRefreshOverview("EventDismissNotification")) - }, fabricPrivacy::logException) + .toObservable(EventTreatmentChange::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ + loadIobCobResults("EventTreatmentChange") + overviewData.prepareTreatmentsData("EventTreatmentChange") + overviewBus.send(EventUpdateOverview("EventTreatmentChange", OverviewData.Property.GRAPH)) + }, fabricPrivacy::logException) + disposable += rxBus + .toObservable(EventTherapyEventChange::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ + overviewData.prepareTreatmentsData("EventTherapyEventChange") + overviewBus.send(EventUpdateOverview("EventTherapyEventChange", OverviewData.Property.GRAPH)) + }, fabricPrivacy::logException) + disposable += rxBus + .toObservable(EventBucketedDataCreated::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ + overviewData.prepareBucketedData("EventBucketedDataCreated") + overviewData.prepareBgData("EventBucketedDataCreated") + overviewBus.send(EventUpdateOverview("EventBucketedDataCreated", OverviewData.Property.GRAPH)) + }, fabricPrivacy::logException) + disposable += rxBus + .toObservable(EventLoopInvoked::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ overviewData.preparePredictions("EventLoopInvoked") }, fabricPrivacy::logException) + disposable.add(rxBus + .toObservable(EventNewBasalProfile::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ loadProfile("EventNewBasalProfile") }, fabricPrivacy::logException)) + disposable.add(rxBus + .toObservable(EventAutosensCalculationFinished::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ + if (it.cause !is EventCustomCalculationFinished) refreshLoop("EventAutosensCalculationFinished") + }, fabricPrivacy::logException)) + + Thread { loadAll("onResume") }.start() } override fun onStop() { @@ -91,55 +172,144 @@ class OverviewPlugin @Inject constructor( } override fun configuration(): JSONObject = - JSONObject() - .putString(R.string.key_quickwizard, sp, resourceHelper) - .putInt(R.string.key_eatingsoon_duration, sp, resourceHelper) - .putDouble(R.string.key_eatingsoon_target, sp, resourceHelper) - .putInt(R.string.key_activity_duration, sp, resourceHelper) - .putDouble(R.string.key_activity_target, sp, resourceHelper) - .putInt(R.string.key_hypo_duration, sp, resourceHelper) - .putDouble(R.string.key_hypo_target, sp, resourceHelper) - .putDouble(R.string.key_low_mark, sp, resourceHelper) - .putDouble(R.string.key_high_mark, sp, resourceHelper) - .putDouble(R.string.key_statuslights_cage_warning, sp, resourceHelper) - .putDouble(R.string.key_statuslights_cage_critical, sp, resourceHelper) - .putDouble(R.string.key_statuslights_iage_warning, sp, resourceHelper) - .putDouble(R.string.key_statuslights_iage_critical, sp, resourceHelper) - .putDouble(R.string.key_statuslights_sage_warning, sp, resourceHelper) - .putDouble(R.string.key_statuslights_sage_critical, sp, resourceHelper) - .putDouble(R.string.key_statuslights_sbat_warning, sp, resourceHelper) - .putDouble(R.string.key_statuslights_sbat_critical, sp, resourceHelper) - .putDouble(R.string.key_statuslights_bage_warning, sp, resourceHelper) - .putDouble(R.string.key_statuslights_bage_critical, sp, resourceHelper) - .putDouble(R.string.key_statuslights_res_warning, sp, resourceHelper) - .putDouble(R.string.key_statuslights_res_critical, sp, resourceHelper) - .putDouble(R.string.key_statuslights_bat_warning, sp, resourceHelper) - .putDouble(R.string.key_statuslights_bat_critical, sp, resourceHelper) + JSONObject() + .putString(R.string.key_quickwizard, sp, resourceHelper) + .putInt(R.string.key_eatingsoon_duration, sp, resourceHelper) + .putDouble(R.string.key_eatingsoon_target, sp, resourceHelper) + .putInt(R.string.key_activity_duration, sp, resourceHelper) + .putDouble(R.string.key_activity_target, sp, resourceHelper) + .putInt(R.string.key_hypo_duration, sp, resourceHelper) + .putDouble(R.string.key_hypo_target, sp, resourceHelper) + .putDouble(R.string.key_low_mark, sp, resourceHelper) + .putDouble(R.string.key_high_mark, sp, resourceHelper) + .putDouble(R.string.key_statuslights_cage_warning, sp, resourceHelper) + .putDouble(R.string.key_statuslights_cage_critical, sp, resourceHelper) + .putDouble(R.string.key_statuslights_iage_warning, sp, resourceHelper) + .putDouble(R.string.key_statuslights_iage_critical, sp, resourceHelper) + .putDouble(R.string.key_statuslights_sage_warning, sp, resourceHelper) + .putDouble(R.string.key_statuslights_sage_critical, sp, resourceHelper) + .putDouble(R.string.key_statuslights_sbat_warning, sp, resourceHelper) + .putDouble(R.string.key_statuslights_sbat_critical, sp, resourceHelper) + .putDouble(R.string.key_statuslights_bage_warning, sp, resourceHelper) + .putDouble(R.string.key_statuslights_bage_critical, sp, resourceHelper) + .putDouble(R.string.key_statuslights_res_warning, sp, resourceHelper) + .putDouble(R.string.key_statuslights_res_critical, sp, resourceHelper) + .putDouble(R.string.key_statuslights_bat_warning, sp, resourceHelper) + .putDouble(R.string.key_statuslights_bat_critical, sp, resourceHelper) override fun applyConfiguration(configuration: JSONObject) { configuration - .storeString(R.string.key_quickwizard, sp, resourceHelper) - .storeInt(R.string.key_eatingsoon_duration, sp, resourceHelper) - .storeDouble(R.string.key_eatingsoon_target, sp, resourceHelper) - .storeInt(R.string.key_activity_duration, sp, resourceHelper) - .storeDouble(R.string.key_activity_target, sp, resourceHelper) - .storeInt(R.string.key_hypo_duration, sp, resourceHelper) - .storeDouble(R.string.key_hypo_target, sp, resourceHelper) - .storeDouble(R.string.key_low_mark, sp, resourceHelper) - .storeDouble(R.string.key_high_mark, sp, resourceHelper) - .storeDouble(R.string.key_statuslights_cage_warning, sp, resourceHelper) - .storeDouble(R.string.key_statuslights_cage_critical, sp, resourceHelper) - .storeDouble(R.string.key_statuslights_iage_warning, sp, resourceHelper) - .storeDouble(R.string.key_statuslights_iage_critical, sp, resourceHelper) - .storeDouble(R.string.key_statuslights_sage_warning, sp, resourceHelper) - .storeDouble(R.string.key_statuslights_sage_critical, sp, resourceHelper) - .storeDouble(R.string.key_statuslights_sbat_warning, sp, resourceHelper) - .storeDouble(R.string.key_statuslights_sbat_critical, sp, resourceHelper) - .storeDouble(R.string.key_statuslights_bage_warning, sp, resourceHelper) - .storeDouble(R.string.key_statuslights_bage_critical, sp, resourceHelper) - .storeDouble(R.string.key_statuslights_res_warning, sp, resourceHelper) - .storeDouble(R.string.key_statuslights_res_critical, sp, resourceHelper) - .storeDouble(R.string.key_statuslights_bat_warning, sp, resourceHelper) - .storeDouble(R.string.key_statuslights_bat_critical, sp, resourceHelper) + .storeString(R.string.key_quickwizard, sp, resourceHelper) + .storeInt(R.string.key_eatingsoon_duration, sp, resourceHelper) + .storeDouble(R.string.key_eatingsoon_target, sp, resourceHelper) + .storeInt(R.string.key_activity_duration, sp, resourceHelper) + .storeDouble(R.string.key_activity_target, sp, resourceHelper) + .storeInt(R.string.key_hypo_duration, sp, resourceHelper) + .storeDouble(R.string.key_hypo_target, sp, resourceHelper) + .storeDouble(R.string.key_low_mark, sp, resourceHelper) + .storeDouble(R.string.key_high_mark, sp, resourceHelper) + .storeDouble(R.string.key_statuslights_cage_warning, sp, resourceHelper) + .storeDouble(R.string.key_statuslights_cage_critical, sp, resourceHelper) + .storeDouble(R.string.key_statuslights_iage_warning, sp, resourceHelper) + .storeDouble(R.string.key_statuslights_iage_critical, sp, resourceHelper) + .storeDouble(R.string.key_statuslights_sage_warning, sp, resourceHelper) + .storeDouble(R.string.key_statuslights_sage_critical, sp, resourceHelper) + .storeDouble(R.string.key_statuslights_sbat_warning, sp, resourceHelper) + .storeDouble(R.string.key_statuslights_sbat_critical, sp, resourceHelper) + .storeDouble(R.string.key_statuslights_bage_warning, sp, resourceHelper) + .storeDouble(R.string.key_statuslights_bage_critical, sp, resourceHelper) + .storeDouble(R.string.key_statuslights_res_warning, sp, resourceHelper) + .storeDouble(R.string.key_statuslights_res_critical, sp, resourceHelper) + .storeDouble(R.string.key_statuslights_bat_warning, sp, resourceHelper) + .storeDouble(R.string.key_statuslights_bat_critical, sp, resourceHelper) + } + + @Volatile + var runningRefresh = false + override fun refreshLoop(from: String) { + if (runningRefresh) return + runningRefresh = true + loadIobCobResults(from) + overviewBus.send(EventUpdateOverview(from, OverviewData.Property.BG)) + overviewBus.send(EventUpdateOverview(from, OverviewData.Property.TIME)) + overviewBus.send(EventUpdateOverview(from, OverviewData.Property.TEMPORARY_BASAL)) + overviewBus.send(EventUpdateOverview(from, OverviewData.Property.EXTENDED_BOLUS)) + overviewBus.send(EventUpdateOverview(from, OverviewData.Property.IOB_COB)) + overviewBus.send(EventUpdateOverview(from, OverviewData.Property.TEMPORARY_TARGET)) + overviewBus.send(EventUpdateOverview(from, OverviewData.Property.SENSITIVITY)) + loadAsData(from) + overviewData.preparePredictions(from) + overviewData.prepareBasalData(from) + overviewData.prepareTemporaryTargetData(from) + overviewData.prepareTreatmentsData(from) + overviewData.prepareIobAutosensData(from) + overviewBus.send(EventUpdateOverview(from, OverviewData.Property.GRAPH)) + aapsLogger.debug(LTag.UI, "refreshLoop finished") + runningRefresh = false + } + + @Suppress("SameParameterValue") + private fun loadAll(from: String) { + loadBg(from) + loadProfile(from) + loadTemporaryBasal(from) + loadExtendedBolus(from) + loadTemporaryTarget(from) + loadIobCobResults(from) + loadAsData(from) + overviewData.prepareBasalData(from) + overviewData.prepareTemporaryTargetData(from) + overviewData.prepareTreatmentsData(from) +// prepareIobAutosensData(from) +// preparePredictions(from) + overviewBus.send(EventUpdateOverview(from, OverviewData.Property.GRAPH)) + aapsLogger.debug(LTag.UI, "loadAll finished") } + + private fun loadProfile(from: String) { + overviewData.profile = profileFunction.getProfile() + overviewData.profileName = profileFunction.getProfileName() + overviewData.profileNameWithRemainingTime = profileFunction.getProfileNameWithRemainingTime() + overviewBus.send(EventUpdateOverview(from, OverviewData.Property.PROFILE)) + } + + private fun loadTemporaryBasal(from: String) { + overviewData.temporaryBasal = iobCobCalculator.getTempBasalIncludingConvertedExtended(dateUtil.now()) + overviewBus.send(EventUpdateOverview(from, OverviewData.Property.TEMPORARY_BASAL)) + } + + private fun loadExtendedBolus(from: String) { + overviewData.extendedBolus = iobCobCalculator.getExtendedBolus(dateUtil.now()) + overviewBus.send(EventUpdateOverview(from, OverviewData.Property.EXTENDED_BOLUS)) + } + + private fun loadTemporaryTarget(from: String) { + val tempTarget = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet() + if (tempTarget is ValueWrapper.Existing) overviewData.temporaryTarget = tempTarget.value + else overviewData.temporaryTarget = null + overviewBus.send(EventUpdateOverview(from, OverviewData.Property.TEMPORARY_TARGET)) + } + + private fun loadAsData(from: String) { + overviewData.lastAutosensData = iobCobCalculator.ads.getLastAutosensData("Overview", aapsLogger, dateUtil) + overviewBus.send(EventUpdateOverview(from, OverviewData.Property.SENSITIVITY)) + } + + private fun loadBg(from: String) { + val gvWrapped = repository.getLastGlucoseValueWrapped().blockingGet() + if (gvWrapped is ValueWrapper.Existing) overviewData.lastBg = gvWrapped.value + else overviewData.lastBg = null + overviewBus.send(EventUpdateOverview(from, OverviewData.Property.BG)) + } + + private fun loadIobCobResults(from: String) { + overviewData.bolusIob = iobCobCalculator.calculateIobFromBolus().round() + overviewData.basalIob = iobCobCalculator.calculateIobFromTempBasalsIncludingConvertedExtended().round() + overviewData.cobInfo = iobCobCalculator.getCobInfo(false, "Overview COB") + val lastCarbs = repository.getLastCarbsRecordWrapped().blockingGet() + overviewData.lastCarbsTime = if (lastCarbs is ValueWrapper.Existing) lastCarbs.value.timestamp else 0L + + overviewBus.send(EventUpdateOverview(from, OverviewData.Property.IOB_COB)) + } + } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverview.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverview.kt new file mode 100644 index 00000000000..969ed4feed1 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/events/EventUpdateOverview.kt @@ -0,0 +1,6 @@ +package info.nightscout.androidaps.plugins.general.overview.events + +import info.nightscout.androidaps.events.Event +import info.nightscout.androidaps.plugins.general.overview.OverviewData + +class EventUpdateOverview(val from: String, val what: OverviewData.Property) : Event() \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphData/GraphData.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphData/GraphData.kt index a4da17f1592..3fcc046cbde 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphData/GraphData.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphData/GraphData.kt @@ -4,52 +4,39 @@ import android.graphics.Color import android.graphics.DashPathEffect import android.graphics.Paint import com.jjoe64.graphview.GraphView -import com.jjoe64.graphview.series.BarGraphSeries import com.jjoe64.graphview.series.DataPoint import com.jjoe64.graphview.series.LineGraphSeries import com.jjoe64.graphview.series.Series import dagger.android.HasAndroidInjector import info.nightscout.androidaps.R -import info.nightscout.androidaps.data.IobTotal -import info.nightscout.androidaps.database.AppRepository -import info.nightscout.androidaps.database.ValueWrapper -import info.nightscout.androidaps.database.entities.Bolus -import info.nightscout.androidaps.database.entities.GlucoseValue -import info.nightscout.androidaps.extensions.target -import info.nightscout.androidaps.interfaces.* +import info.nightscout.androidaps.interfaces.GlucoseUnit +import info.nightscout.androidaps.interfaces.ProfileFunction import info.nightscout.androidaps.logging.AAPSLogger -import info.nightscout.androidaps.logging.LTag -import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults -import info.nightscout.androidaps.plugins.general.overview.graphExtensions.* -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult -import info.nightscout.androidaps.utils.* +import info.nightscout.androidaps.plugins.general.overview.OverviewData +import info.nightscout.androidaps.plugins.general.overview.graphExtensions.AreaGraphSeries +import info.nightscout.androidaps.plugins.general.overview.graphExtensions.DoubleDataPoint +import info.nightscout.androidaps.plugins.general.overview.graphExtensions.TimeAsXAxisLabelFormatter +import info.nightscout.androidaps.utils.DefaultValueHelper +import info.nightscout.androidaps.utils.Round import info.nightscout.androidaps.utils.resources.ResourceHelper import java.util.* import javax.inject.Inject import kotlin.math.abs import kotlin.math.max -import kotlin.math.min class GraphData( injector: HasAndroidInjector, private val graph: GraphView, - private val iobCobCalculator: IobCobCalculator + private val overviewData: OverviewData ) { - // IobCobCalculatorPlugin Cannot be injected: HistoryBrowser @Inject lateinit var aapsLogger: AAPSLogger @Inject lateinit var profileFunction: ProfileFunction @Inject lateinit var resourceHelper: ResourceHelper - @Inject lateinit var activePlugin: ActivePlugin - @Inject lateinit var databaseHelper: DatabaseHelperInterface - @Inject lateinit var repository: AppRepository - @Inject lateinit var dateUtil: DateUtil @Inject lateinit var defaultValueHelper: DefaultValueHelper - @Inject lateinit var translator: Translator - var maxY = Double.MIN_VALUE + private var maxY = Double.MIN_VALUE private var minY = Double.MAX_VALUE - private var bgReadingsArray: List? = null private val units: GlucoseUnit private val series: MutableList> = ArrayList() @@ -58,587 +45,142 @@ class GraphData( units = profileFunction.getUnits() } - fun addBucketedData(fromTime: Long, toTime: Long) { - val bucketedData = iobCobCalculator.ads.getBucketedDataTableCopy() ?: return - if (bucketedData.isEmpty()) { - aapsLogger.debug("No bucketed data.") - return - } - val bucketedListArray: MutableList = ArrayList() - for (inMemoryGlucoseValue in bucketedData) { - if (inMemoryGlucoseValue.timestamp < fromTime || inMemoryGlucoseValue.timestamp > toTime) continue - bucketedListArray.add(InMemoryGlucoseValueDataPoint(inMemoryGlucoseValue, profileFunction, resourceHelper)) - } - addSeries(PointsWithLabelGraphSeries(Array(bucketedListArray.size) { i -> bucketedListArray[i] })) + fun addBucketedData() { + addSeries(overviewData.bucketedGraphSeries) } - fun addBgReadings(fromTime: Long, toTime: Long, highLine: Double, predictions: MutableList?) { - var maxBgValue = Double.MIN_VALUE - bgReadingsArray = repository.compatGetBgReadingsDataFromTime(fromTime, toTime, false).blockingGet() - if (bgReadingsArray?.isEmpty() != false) { - aapsLogger.debug("No BG data.") - maxY = if (units == GlucoseUnit.MGDL) 180.0 else 10.0 - minY = 0.0 - return - } - val bgListArray: MutableList = ArrayList() - for (bg in bgReadingsArray!!) { - if (bg.timestamp < fromTime || bg.timestamp > toTime) continue - if (bg.value > maxBgValue) maxBgValue = bg.value - bgListArray.add(GlucoseValueDataPoint(bg, defaultValueHelper, profileFunction, resourceHelper)) - } - if (predictions != null) { - predictions.sortWith(Comparator { o1: GlucoseValueDataPoint, o2: GlucoseValueDataPoint -> o1.x.compareTo(o2.x) }) - for (prediction in predictions) if (prediction.data.value >= 40) bgListArray.add(prediction) - } - maxBgValue = Profile.fromMgdlToUnits(maxBgValue, units) - maxBgValue = addUpperChartMargin(maxBgValue) - if (highLine > maxBgValue) maxBgValue = highLine - maxY = maxBgValue + fun addBgReadings(addPredictions: Boolean) { + maxY = if (overviewData.bgReadingsArray.isEmpty()) { + if (units == GlucoseUnit.MGDL) 180.0 else 10.0 + } else overviewData.maxBgValue minY = 0.0 - addSeries(PointsWithLabelGraphSeries(Array(bgListArray.size) { i -> bgListArray[i] })) + addSeries(overviewData.bgReadingGraphSeries) + if (addPredictions) addSeries(overviewData.predictionsGraphSeries) } - private fun addUpperChartMargin(maxBgValue: Double) = - if (units == GlucoseUnit.MGDL) Round.roundTo(maxBgValue, 40.0) + 80 else Round.roundTo(maxBgValue, 2.0) + 4 - fun addInRangeArea(fromTime: Long, toTime: Long, lowLine: Double, highLine: Double) { - val inRangeAreaSeries: AreaGraphSeries val inRangeAreaDataPoints = arrayOf( DoubleDataPoint(fromTime.toDouble(), lowLine, highLine), DoubleDataPoint(toTime.toDouble(), lowLine, highLine) ) - inRangeAreaSeries = AreaGraphSeries(inRangeAreaDataPoints) - inRangeAreaSeries.color = 0 - inRangeAreaSeries.isDrawBackground = true - inRangeAreaSeries.backgroundColor = resourceHelper.gc(R.color.inrangebackground) - addSeries(inRangeAreaSeries) - } - - // scale in % of vertical size (like 0.3) - fun addBasals(fromTime: Long, toTime: Long, scale: Double) { - var maxBasalValueFound = 0.0 - val basalScale = Scale() - val baseBasalArray: MutableList = ArrayList() - val tempBasalArray: MutableList = ArrayList() - val basalLineArray: MutableList = ArrayList() - val absoluteBasalLineArray: MutableList = ArrayList() - var lastLineBasal = 0.0 - var lastAbsoluteLineBasal = -1.0 - var lastBaseBasal = 0.0 - var lastTempBasal = 0.0 - var time = fromTime - while (time < toTime) { - val profile = profileFunction.getProfile(time) - if (profile == null) { - time += 60 * 1000L - continue - } - val basalData = iobCobCalculator.getBasalData(profile, time) - val baseBasalValue = basalData.basal - var absoluteLineValue = baseBasalValue - var tempBasalValue = 0.0 - var basal = 0.0 - if (basalData.isTempBasalRunning) { - tempBasalValue = basalData.tempBasalAbsolute - absoluteLineValue = tempBasalValue - if (tempBasalValue != lastTempBasal) { - tempBasalArray.add(ScaledDataPoint(time, lastTempBasal, basalScale)) - tempBasalArray.add(ScaledDataPoint(time, tempBasalValue.also { basal = it }, basalScale)) - } - if (lastBaseBasal != 0.0) { - baseBasalArray.add(ScaledDataPoint(time, lastBaseBasal, basalScale)) - baseBasalArray.add(ScaledDataPoint(time, 0.0, basalScale)) - lastBaseBasal = 0.0 - } - } else { - if (baseBasalValue != lastBaseBasal) { - baseBasalArray.add(ScaledDataPoint(time, lastBaseBasal, basalScale)) - baseBasalArray.add(ScaledDataPoint(time, baseBasalValue.also { basal = it }, basalScale)) - lastBaseBasal = baseBasalValue - } - if (lastTempBasal != 0.0) { - tempBasalArray.add(ScaledDataPoint(time, lastTempBasal, basalScale)) - tempBasalArray.add(ScaledDataPoint(time, 0.0, basalScale)) - } - } - if (baseBasalValue != lastLineBasal) { - basalLineArray.add(ScaledDataPoint(time, lastLineBasal, basalScale)) - basalLineArray.add(ScaledDataPoint(time, baseBasalValue, basalScale)) - } - if (absoluteLineValue != lastAbsoluteLineBasal) { - absoluteBasalLineArray.add(ScaledDataPoint(time, lastAbsoluteLineBasal, basalScale)) - absoluteBasalLineArray.add(ScaledDataPoint(time, basal, basalScale)) - } - lastAbsoluteLineBasal = absoluteLineValue - lastLineBasal = baseBasalValue - lastTempBasal = tempBasalValue - maxBasalValueFound = max(maxBasalValueFound, max(tempBasalValue, baseBasalValue)) - time += 60 * 1000L - } - - // final points - basalLineArray.add(ScaledDataPoint(toTime, lastLineBasal, basalScale)) - baseBasalArray.add(ScaledDataPoint(toTime, lastBaseBasal, basalScale)) - tempBasalArray.add(ScaledDataPoint(toTime, lastTempBasal, basalScale)) - absoluteBasalLineArray.add(ScaledDataPoint(toTime, lastAbsoluteLineBasal, basalScale)) - - // create series - addSeries(LineGraphSeries(Array(baseBasalArray.size) { i -> baseBasalArray[i] }).also { + addSeries(AreaGraphSeries(inRangeAreaDataPoints).also { + it.color = 0 it.isDrawBackground = true - it.backgroundColor = resourceHelper.gc(R.color.basebasal) - it.thickness = 0 - }) - addSeries(LineGraphSeries(Array(tempBasalArray.size) { i -> tempBasalArray[i] }).also { - it.isDrawBackground = true - it.backgroundColor = resourceHelper.gc(R.color.tempbasal) - it.thickness = 0 - }) - addSeries(LineGraphSeries(Array(basalLineArray.size) { i -> basalLineArray[i] }).also { - it.setCustomPaint(Paint().also { paint -> - paint.style = Paint.Style.STROKE - paint.strokeWidth = resourceHelper.getDisplayMetrics().scaledDensity * 2 - paint.pathEffect = DashPathEffect(floatArrayOf(2f, 4f), 0f) - paint.color = resourceHelper.gc(R.color.basal) - }) + it.backgroundColor = resourceHelper.gc(R.color.inrangebackground) }) - addSeries(LineGraphSeries(Array(absoluteBasalLineArray.size) { i -> absoluteBasalLineArray[i] }).also { - it.setCustomPaint(Paint().also { absolutePaint -> - absolutePaint.style = Paint.Style.STROKE - absolutePaint.strokeWidth = resourceHelper.getDisplayMetrics().scaledDensity * 2 - absolutePaint.color = resourceHelper.gc(R.color.basal) - }) - }) - basalScale.setMultiplier(maxY * scale / maxBasalValueFound) } - fun addTargetLine(fromTime: Long, toTimeParam: Long, profile: Profile, lastRun: LoopInterface.LastRun?) { - var toTime = toTimeParam - val targetsSeriesArray: MutableList = ArrayList() - var lastTarget = -1.0 - lastRun?.constraintsProcessed?.let { toTime = max(it.latestPredictionsTime, toTime) } - var time = fromTime - while (time < toTime) { - val tt = repository.getTemporaryTargetActiveAt(time).blockingGet() - val value: Double = if (tt is ValueWrapper.Existing) { - Profile.fromMgdlToUnits(tt.value.target(), units) - } else { - Profile.fromMgdlToUnits((profile.getTargetLowMgdl(time) + profile.getTargetHighMgdl(time)) / 2, units) - } - if (lastTarget != value) { - if (lastTarget != -1.0) targetsSeriesArray.add(DataPoint(time.toDouble(), lastTarget)) - targetsSeriesArray.add(DataPoint(time.toDouble(), value)) - } - lastTarget = value - time += 5 * 60 * 1000L - } - // final point - targetsSeriesArray.add(DataPoint(toTime.toDouble(), lastTarget)) - // create series - addSeries(LineGraphSeries(Array(targetsSeriesArray.size) { i -> targetsSeriesArray[i] }).also { - it.isDrawBackground = false - it.color = resourceHelper.gc(R.color.tempTargetBackground) - it.thickness = 2 - }) + fun addBasals() { + val scale = defaultValueHelper.determineLowLine() / maxY / 1.2 + addSeries(overviewData.baseBasalGraphSeries) + addSeries(overviewData.tempBasalGraphSeries) + addSeries(overviewData.basalLineGraphSeries) + addSeries(overviewData.absoluteBasalGraphSeries) + overviewData.basalScale.multiplier = maxY * scale / overviewData.maxBasalValueFound } - fun addTreatments(fromTime: Long, endTime: Long) { - val filteredTreatments: MutableList = ArrayList() - repository.getBolusesIncludingInvalidFromTimeToTime(fromTime, endTime, true).blockingGet() - .map { BolusDataPoint(it, resourceHelper, activePlugin, defaultValueHelper) } - .filter { it.data.type != Bolus.Type.SMB || it.data.isValid } - .forEach { - it.y = getNearestBg(it.x.toLong()) - filteredTreatments.add(it) - } - repository.getCarbsIncludingInvalidFromTimeToTimeExpanded(fromTime, endTime, true).blockingGet() - .map { CarbsDataPoint(it, resourceHelper) } - .forEach { - it.y = getNearestBg(it.x.toLong()) - filteredTreatments.add(it) - } - - // ProfileSwitch - repository.getEffectiveProfileSwitchDataFromTimeToTime(fromTime, endTime, true).blockingGet() - .map { EffectiveProfileSwitchDataPoint(it) } - .forEach(filteredTreatments::add) - - // Extended bolus - if (!activePlugin.activePump.isFakingTempsByExtendedBoluses) { - repository.getExtendedBolusDataFromTimeToTime(fromTime, endTime, true).blockingGet() - .map { ExtendedBolusDataPoint(it) } - .filter { it.duration != 0L } - .forEach { - it.y = getNearestBg(it.x.toLong()) - filteredTreatments.add(it) - } - } - - // Careportal - repository.compatGetTherapyEventDataFromToTime(fromTime - T.hours(6).msecs(), endTime).blockingGet() - .map { TherapyEventDataPoint(it, resourceHelper, profileFunction, translator) } - .filterTimeframe(fromTime, endTime) - .forEach { - if (it.y == 0.0) it.y = getNearestBg(it.x.toLong()) - filteredTreatments.add(it) - } - - // increase maxY if a treatment forces it's own height that's higher than a BG value - filteredTreatments.map { it.y } - .maxOrNull() - ?.let(::addUpperChartMargin) - ?.let { maxY = maxOf(maxY, it) } - - addSeries(PointsWithLabelGraphSeries(filteredTreatments.toTypedArray())) + fun addTargetLine() { + addSeries(overviewData.temporaryTargetSeries) } - private fun getNearestBg(date: Long): Double { - bgReadingsArray?.let { bgReadingsArray -> - for (reading in bgReadingsArray) { - if (reading.timestamp > date) continue - return Profile.fromMgdlToUnits(reading.value, units) - } - return if (bgReadingsArray.isNotEmpty()) Profile.fromMgdlToUnits(bgReadingsArray[0].value, units) else Profile.fromMgdlToUnits(100.0, units) - } ?: return Profile.fromMgdlToUnits(100.0, units) + fun addTreatments() { + maxY = maxOf(maxY, overviewData.maxTreatmentsValue) + addSeries(overviewData.treatmentsSeries) } - fun addActivity(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) { - val actArrayHist: MutableList = ArrayList() - val actArrayPrediction: MutableList = ArrayList() - val now = System.currentTimeMillis().toDouble() - val actScale = Scale() - var total: IobTotal - var maxIAValue = 0.0 - var time = fromTime - while (time <= toTime) { - val profile = profileFunction.getProfile(time) - if (profile == null) { - time += 5 * 60 * 1000L - continue - } - total = iobCobCalculator.calculateFromTreatmentsAndTemps(time, profile) - val act: Double = total.activity - if (time <= now) actArrayHist.add(ScaledDataPoint(time, act, actScale)) else actArrayPrediction.add(ScaledDataPoint(time, act, actScale)) - maxIAValue = max(maxIAValue, abs(act)) - time += 5 * 60 * 1000L - } - addSeries(FixedLineGraphSeries(Array(actArrayHist.size) { i -> actArrayHist[i] }).also { - it.isDrawBackground = false - it.color = resourceHelper.gc(R.color.activity) - it.thickness = 3 - }) - addSeries(FixedLineGraphSeries(Array(actArrayPrediction.size) { i -> actArrayPrediction[i] }).also { - it.setCustomPaint(Paint().also { paint -> - paint.style = Paint.Style.STROKE - paint.strokeWidth = 3f - paint.pathEffect = DashPathEffect(floatArrayOf(4f, 4f), 0f) - paint.color = resourceHelper.gc(R.color.activity) - }) - }) - if (useForScale) { - maxY = maxIAValue - minY = -maxIAValue - } - actScale.setMultiplier(maxY * scale / maxIAValue) + fun addActivity(scale: Double) { + addSeries(overviewData.activitySeries) + addSeries(overviewData.activityPredictionSeries) + overviewData.actScale.multiplier = maxY * scale / overviewData.maxIAValue } //Function below show -BGI to be able to compare curves with deviations - fun addMinusBGI(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double, devBgiScale: Boolean) { - val bgiArrayHist: MutableList = ArrayList() - val bgiArrayPrediction: MutableList = ArrayList() - val now = System.currentTimeMillis().toDouble() - val bgiScale = Scale() - var total: IobTotal - var maxBGIValue = 0.0 - var time = fromTime - while (time <= toTime) { - val profile = profileFunction.getProfile(time) - if (profile == null) { - time += 5 * 60 * 1000L - continue - } - val deviation = if (devBgiScale) iobCobCalculator.ads.getAutosensDataAtTime(time)?.deviation - ?: 0.0 else 0.0 - - total = iobCobCalculator.calculateFromTreatmentsAndTemps(time, profile) - val bgi: Double = total.activity * profile.getIsfMgdl(time) * 5.0 - if (time <= now) bgiArrayHist.add(ScaledDataPoint(time, bgi, bgiScale)) else bgiArrayPrediction.add(ScaledDataPoint(time, bgi, bgiScale)) - maxBGIValue = max(maxBGIValue, max(abs(bgi), deviation)) - time += 5 * 60 * 1000L - } - addSeries(FixedLineGraphSeries(Array(bgiArrayHist.size) { i -> bgiArrayHist[i] }).also { - it.isDrawBackground = false - it.color = resourceHelper.gc(R.color.bgi) - it.thickness = 3 - }) - addSeries(FixedLineGraphSeries(Array(bgiArrayPrediction.size) { i -> bgiArrayPrediction[i] }).also { - it.setCustomPaint(Paint().also { paint -> - paint.style = Paint.Style.STROKE - paint.strokeWidth = 3f - paint.pathEffect = DashPathEffect(floatArrayOf(4f, 4f), 0f) - paint.color = resourceHelper.gc(R.color.bgi) - }) - }) + fun addMinusBGI(useForScale: Boolean, scale: Double) { if (useForScale) { - maxY = maxBGIValue - minY = -maxBGIValue + maxY = overviewData.maxBGIValue + minY = -overviewData.maxBGIValue } - bgiScale.setMultiplier(maxY * scale / maxBGIValue) + overviewData.bgiScale.multiplier = maxY * scale / overviewData.maxBGIValue + addSeries(overviewData.minusBgiSeries) + addSeries(overviewData.minusBgiHistSeries) } // scale in % of vertical size (like 0.3) - fun addIob(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double, showPrediction: Boolean, absScale: Boolean) { - val iobSeries: FixedLineGraphSeries - val iobArray: MutableList = ArrayList() - var maxIobValueFound = Double.MIN_VALUE - var lastIob = 0.0 - val iobScale = Scale() - var time = fromTime - while (time <= toTime) { - val profile = profileFunction.getProfile(time) - if (profile == null) { - time += 5 * 60 * 1000L - continue - } - var iob = 0.0 - var absIob = 0.0 - iob = iobCobCalculator.calculateFromTreatmentsAndTemps(time, profile).iob - if (absScale) absIob = iobCobCalculator.calculateAbsInsulinFromTreatmentsAndTemps(time).iob - if (abs(lastIob - iob) > 0.02) { - if (abs(lastIob - iob) > 0.2) iobArray.add(ScaledDataPoint(time, lastIob, iobScale)) - iobArray.add(ScaledDataPoint(time, iob, iobScale)) - maxIobValueFound = if (absScale) max(maxIobValueFound, abs(absIob)) else max(maxIobValueFound, abs(iob)) - lastIob = iob - } - time += 5 * 60 * 1000L - } - iobSeries = FixedLineGraphSeries(Array(iobArray.size) { i -> iobArray[i] }).also { - it.isDrawBackground = true - it.backgroundColor = -0x7f000001 and resourceHelper.gc(R.color.iob) //50% - it.color = resourceHelper.gc(R.color.iob) - it.thickness = 3 - } - if (showPrediction) { - val autosensData = iobCobCalculator.getLastAutosensDataWithWaitForCalculationFinish("GraphData") - val lastAutosensResult = autosensData?.autosensResult ?: AutosensResult() - val isTempTarget = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet() is ValueWrapper.Existing - val iobPrediction: MutableList = ArrayList() - val iobPredictionArray = iobCobCalculator.calculateIobArrayForSMB(lastAutosensResult, SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget) - for (i in iobPredictionArray) { - iobPrediction.add(i.setColor(resourceHelper.gc(R.color.iobPredAS))) - maxIobValueFound = max(maxIobValueFound, abs(i.iob)) - } - addSeries(PointsWithLabelGraphSeries(Array(iobPrediction.size) { i -> iobPrediction[i] })) - val iobPrediction2: MutableList = ArrayList() - val iobPredictionArray2 = iobCobCalculator.calculateIobArrayForSMB(AutosensResult(), SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget) - for (i in iobPredictionArray2) { - iobPrediction2.add(i.setColor(resourceHelper.gc(R.color.iobPred))) - maxIobValueFound = max(maxIobValueFound, abs(i.iob)) - } - addSeries(PointsWithLabelGraphSeries(Array(iobPrediction2.size) { i -> iobPrediction2[i] })) - aapsLogger.debug(LTag.AUTOSENS, "IOB prediction for AS=" + DecimalFormatter.to2Decimal(lastAutosensResult.ratio) + ": " + iobCobCalculator.iobArrayToString(iobPredictionArray)) - aapsLogger.debug(LTag.AUTOSENS, "IOB prediction for AS=" + DecimalFormatter.to2Decimal(1.0) + ": " + iobCobCalculator.iobArrayToString(iobPredictionArray2)) - } + fun addIob(useForScale: Boolean, scale: Double) { if (useForScale) { - maxY = maxIobValueFound - minY = -maxIobValueFound + maxY = overviewData.maxIobValueFound + minY = -overviewData.maxIobValueFound } - iobScale.setMultiplier(maxY * scale / maxIobValueFound) - addSeries(iobSeries) + overviewData.iobScale.multiplier = maxY * scale / overviewData.maxIobValueFound + addSeries(overviewData.iobSeries) + addSeries(overviewData.iobPredictions1Series) + addSeries(overviewData.iobPredictions2Series) } // scale in % of vertical size (like 0.3) - fun addAbsIob(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) { - val iobSeries: FixedLineGraphSeries - val iobArray: MutableList = ArrayList() - var maxIobValueFound = Double.MIN_VALUE - var lastIob = 0.0 - val iobScale = Scale() - var time = fromTime - while (time <= toTime) { - val profile = profileFunction.getProfile(time) - if (profile == null) { - time += 5 * 60 * 1000L - continue - } - var iob = 0.0 - iob = iobCobCalculator.calculateAbsInsulinFromTreatmentsAndTemps(time).iob - if (abs(lastIob - iob) > 0.02) { - if (abs(lastIob - iob) > 0.2) iobArray.add(ScaledDataPoint(time, lastIob, iobScale)) - iobArray.add(ScaledDataPoint(time, iob, iobScale)) - maxIobValueFound = max(maxIobValueFound, abs(iob)) - lastIob = iob - } - time += 5 * 60 * 1000L - } - iobSeries = FixedLineGraphSeries(Array(iobArray.size) { i -> iobArray[i] }).also { - it.isDrawBackground = true - it.backgroundColor = -0x7f000001 and resourceHelper.gc(R.color.iob) //50% - it.color = resourceHelper.gc(R.color.iob) - it.thickness = 3 - } + fun addAbsIob(useForScale: Boolean, scale: Double) { if (useForScale) { - maxY = maxIobValueFound - minY = -maxIobValueFound + maxY = overviewData.maxIobValueFound + minY = -overviewData.maxIobValueFound } - iobScale.setMultiplier(maxY * scale / maxIobValueFound) - addSeries(iobSeries) + overviewData.iobScale.multiplier = maxY * scale / overviewData.maxIobValueFound + addSeries(overviewData.absIobSeries) } // scale in % of vertical size (like 0.3) - fun addCob(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) { - val minFailOverActiveList: MutableList = ArrayList() - val cobArray: MutableList = ArrayList() - var maxCobValueFound = 0.0 - var lastCob = 0 - val cobScale = Scale() - var time = fromTime - while (time <= toTime) { - iobCobCalculator.ads.getAutosensDataAtTime(time)?.let { autosensData -> - val cob = autosensData.cob.toInt() - if (cob != lastCob) { - if (autosensData.carbsFromBolus > 0) cobArray.add(ScaledDataPoint(time, lastCob.toDouble(), cobScale)) - cobArray.add(ScaledDataPoint(time, cob.toDouble(), cobScale)) - maxCobValueFound = max(maxCobValueFound, cob.toDouble()) - lastCob = cob - } - if (autosensData.failoverToMinAbsorbtionRate) { - autosensData.setScale(cobScale) - autosensData.setChartTime(time) - minFailOverActiveList.add(autosensData) - } - } - time += 5 * 60 * 1000L - } - - // COB - addSeries(FixedLineGraphSeries(Array(cobArray.size) { i -> cobArray[i] }).also { - it.isDrawBackground = true - it.backgroundColor = -0x7f000001 and resourceHelper.gc(R.color.cob) //50% - it.color = resourceHelper.gc(R.color.cob) - it.thickness = 3 - }) + fun addCob(useForScale: Boolean, scale: Double) { if (useForScale) { - maxY = maxCobValueFound + maxY = overviewData.maxCobValueFound minY = 0.0 } - cobScale.setMultiplier(maxY * scale / maxCobValueFound) - addSeries(PointsWithLabelGraphSeries(Array(minFailOverActiveList.size) { i -> minFailOverActiveList[i] })) + overviewData.cobScale.multiplier = maxY * scale / overviewData.maxCobValueFound + addSeries(overviewData.cobSeries) + addSeries(overviewData.cobMinFailOverSeries) } // scale in % of vertical size (like 0.3) - fun addDeviations(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double, devBgiScale: Boolean) { - class DeviationDataPoint(x: Double, y: Double, var color: Int, scale: Scale) : ScaledDataPoint(x, y, scale) - - val devArray: MutableList = ArrayList() - var maxDevValueFound = 0.0 - val devScale = Scale() - var time = fromTime - var total: IobTotal - - while (time <= toTime) { - // if align Dev Scale with BGI scale, then calculate BGI value, else bgi = 0.0 - val bgi: Double = if (devBgiScale) { - val profile = profileFunction.getProfile(time) - if (profile == null) { - time += 5 * 60 * 1000L - continue - } - total = iobCobCalculator.calculateFromTreatmentsAndTemps(time, profile) - total.activity * profile.getIsfMgdl(time) * 5.0 - } else 0.0 - - iobCobCalculator.ads.getAutosensDataAtTime(time)?.let { autosensData -> - var color = resourceHelper.gc(R.color.deviationblack) // "=" - if (autosensData.type == "" || autosensData.type == "non-meal") { - if (autosensData.pastSensitivity == "C") color = resourceHelper.gc(R.color.deviationgrey) - if (autosensData.pastSensitivity == "+") color = resourceHelper.gc(R.color.deviationgreen) - if (autosensData.pastSensitivity == "-") color = resourceHelper.gc(R.color.deviationred) - } else if (autosensData.type == "uam") { - color = resourceHelper.gc(R.color.uam) - } else if (autosensData.type == "csf") { - color = resourceHelper.gc(R.color.deviationgrey) - } - devArray.add(DeviationDataPoint(time.toDouble(), autosensData.deviation, color, devScale)) - maxDevValueFound = max(maxDevValueFound, max(abs(autosensData.deviation), abs(bgi))) - } - time += 5 * 60 * 1000L - } - - // DEVIATIONS - addSeries(BarGraphSeries(Array(devArray.size) { i -> devArray[i] }).also { - it.setValueDependentColor { data: DeviationDataPoint -> data.color } - }) + fun addDeviations(useForScale: Boolean, scale: Double) { if (useForScale) { - maxY = maxDevValueFound + maxY = overviewData.maxDevValueFound minY = -maxY } - devScale.setMultiplier(maxY * scale / maxDevValueFound) + overviewData.devScale.multiplier = maxY * scale / overviewData.maxDevValueFound + addSeries(overviewData.deviationsSeries) } // scale in % of vertical size (like 0.3) - fun addRatio(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) { - val ratioArray: MutableList = ArrayList() - var maxRatioValueFound = 5.0 //even if sens data equals 0 for all the period, minimum scale is between 95% and 105% - var minRatioValueFound = -maxRatioValueFound - val ratioScale = if (useForScale) Scale(100.0) else Scale() - var time = fromTime - while (time <= toTime) { - iobCobCalculator.ads.getAutosensDataAtTime(time)?.let { autosensData -> - ratioArray.add(ScaledDataPoint(time, 100.0 * (autosensData.autosensResult.ratio - 1), ratioScale)) - maxRatioValueFound = max(maxRatioValueFound, 100.0 * (autosensData.autosensResult.ratio - 1)) - minRatioValueFound = min(minRatioValueFound, 100.0 * (autosensData.autosensResult.ratio - 1)) - } - time += 5 * 60 * 1000L - } - - // RATIOS - addSeries(LineGraphSeries(Array(ratioArray.size) { i -> ratioArray[i] }).also { - it.color = resourceHelper.gc(R.color.ratio) - it.thickness = 3 - }) + fun addRatio(useForScale: Boolean, scale: Double) { if (useForScale) { - maxY = 100.0 + max(maxRatioValueFound, abs(minRatioValueFound)) - minY = 100.0 - max(maxRatioValueFound, abs(minRatioValueFound)) - ratioScale.setMultiplier(1.0) - } else - ratioScale.setMultiplier(maxY * scale / max(maxRatioValueFound, abs(minRatioValueFound))) + maxY = 100.0 + max(overviewData.maxRatioValueFound, abs(overviewData.minRatioValueFound)) + minY = 100.0 - max(overviewData.maxRatioValueFound, abs(overviewData.minRatioValueFound)) + overviewData.ratioScale.multiplier = 1.0 + overviewData.ratioScale.shift = 100.0 + } else { + overviewData.ratioScale.multiplier = maxY * scale / max(overviewData.maxRatioValueFound, abs(overviewData.minRatioValueFound)) + overviewData.ratioScale.shift = 0.0 + } + addSeries(overviewData.ratioSeries) } // scale in % of vertical size (like 0.3) - fun addDeviationSlope(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) { - val dsMaxArray: MutableList = ArrayList() - val dsMinArray: MutableList = ArrayList() - var maxFromMaxValueFound = 0.0 - var maxFromMinValueFound = 0.0 - val dsMaxScale = Scale() - val dsMinScale = Scale() - var time = fromTime - while (time <= toTime) { - iobCobCalculator.ads.getAutosensDataAtTime(time)?.let { autosensData -> - dsMaxArray.add(ScaledDataPoint(time, autosensData.slopeFromMaxDeviation, dsMaxScale)) - dsMinArray.add(ScaledDataPoint(time, autosensData.slopeFromMinDeviation, dsMinScale)) - maxFromMaxValueFound = max(maxFromMaxValueFound, abs(autosensData.slopeFromMaxDeviation)) - maxFromMinValueFound = max(maxFromMinValueFound, abs(autosensData.slopeFromMinDeviation)) - } - time += 5 * 60 * 1000L - } - - // Slopes - addSeries(LineGraphSeries(Array(dsMaxArray.size) { i -> dsMaxArray[i] }).also { - it.color = resourceHelper.gc(R.color.devslopepos) - it.thickness = 3 - }) - addSeries(LineGraphSeries(Array(dsMinArray.size) { i -> dsMinArray[i] }).also { - it.color = resourceHelper.gc(R.color.devslopeneg) - it.thickness = 3 - }) + fun addDeviationSlope(useForScale: Boolean, scale: Double, isRatioScale: Boolean = false) { if (useForScale) { - maxY = max(maxFromMaxValueFound, maxFromMinValueFound) + maxY = max(overviewData.maxFromMaxValueFound, overviewData.maxFromMinValueFound) minY = -maxY } - dsMaxScale.setMultiplier(maxY * scale / maxFromMaxValueFound) - dsMinScale.setMultiplier(maxY * scale / maxFromMinValueFound) + var graphMaxY = maxY + if (isRatioScale) { + graphMaxY = maxY - 100.0 + overviewData.dsMinScale.shift = 100.0 + overviewData.dsMaxScale.shift = 100.0 + } else { + overviewData.dsMinScale.shift = 0.0 + overviewData.dsMaxScale.shift = 0.0 + } + overviewData.dsMaxScale.multiplier = graphMaxY * scale / overviewData.maxFromMaxValueFound + overviewData.dsMinScale.multiplier = graphMaxY * scale / overviewData.maxFromMinValueFound + addSeries(overviewData.dsMaxSeries) + addSeries(overviewData.dsMinSeries) } // scale in % of vertical size (like 0.3) @@ -695,5 +237,3 @@ class GraphData( } } -private fun List.filterTimeframe(fromTime: Long, endTime: Long): List = - filter { it.x + it.duration >= fromTime && it.x <= endTime } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/TherapyEventDataPoint.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/TherapyEventDataPoint.kt index d273f4e7f26..0467839741c 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 @@ -31,11 +31,11 @@ class TherapyEventDataPoint @Inject constructor( if (data.glucose != null && data.glucose != 0.0) { var mmol = 0.0 var mgdl = 0.0 - if (units == GlucoseUnit.MGDL) { + if (data.glucoseUnit == TherapyEvent.GlucoseUnit.MGDL) { mgdl = data.glucose!! mmol = data.glucose!! * Constants.MGDL_TO_MMOLL } - if (units == GlucoseUnit.MMOL) { + if (data.glucoseUnit == TherapyEvent.GlucoseUnit.MMOL) { mmol = data.glucose!! mgdl = data.glucose!! * Constants.MMOLL_TO_MGDL } 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 b6136e1445e..e8ecaf62382 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 @@ -11,25 +11,26 @@ import androidx.work.Worker import androidx.work.WorkerParameters import androidx.work.workDataOf import dagger.android.HasAndroidInjector -import info.nightscout.androidaps.interfaces.Config import info.nightscout.androidaps.Constants import info.nightscout.androidaps.R import info.nightscout.androidaps.data.DetailedBolusInfo -import info.nightscout.androidaps.interfaces.Profile import info.nightscout.androidaps.database.AppRepository -import info.nightscout.androidaps.database.entities.ValueWithUnit +import info.nightscout.androidaps.database.entities.OfflineEvent 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.transactions.CancelCurrentOfflineEventIfAnyTransaction import info.nightscout.androidaps.database.transactions.CancelCurrentTemporaryTargetIfAnyTransaction -import info.nightscout.androidaps.database.transactions.InsertTemporaryTargetAndCancelCurrentTransaction +import info.nightscout.androidaps.database.transactions.InsertAndCancelCurrentOfflineEventTransaction +import info.nightscout.androidaps.database.transactions.InsertAndCancelCurrentTemporaryTargetTransaction import info.nightscout.androidaps.events.EventPreferenceChange import info.nightscout.androidaps.events.EventRefreshOverview +import info.nightscout.androidaps.extensions.valueToUnitsString import info.nightscout.androidaps.interfaces.* import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.logging.UserEntryLogger -import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart @@ -41,7 +42,6 @@ import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProv import info.nightscout.androidaps.queue.Callback import info.nightscout.androidaps.receivers.DataWorker import info.nightscout.androidaps.utils.* -import info.nightscout.androidaps.extensions.valueToUnitsString import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.rx.AapsSchedulers import info.nightscout.androidaps.utils.sharedPreferences.SP @@ -72,7 +72,7 @@ class SmsCommunicatorPlugin @Inject constructor( private val fabricPrivacy: FabricPrivacy, private val activePlugin: ActivePlugin, private val commandQueue: CommandQueueProvider, - private val loopPlugin: LoopPlugin, + private val loop: Loop, private val iobCobCalculator: IobCobCalculator, private val xdripCalibrations: XdripCalibrations, private var otp: OneTimePassword, @@ -181,6 +181,7 @@ class SmsCommunicatorPlugin @Inject constructor( } @Suppress("SpellCheckingInspection") + @kotlin.ExperimentalStdlibApi override fun doWork(): Result { val bundle = dataWorker.pickupBundle(inputData.getLong(DataWorker.STORE_KEY, -1)) ?: return Result.failure(workDataOf("Error" to "missing input data")) @@ -223,6 +224,7 @@ class SmsCommunicatorPlugin @Inject constructor( return false } + @kotlin.ExperimentalStdlibApi fun processSms(receivedSms: Sms) { if (!isEnabled(PluginType.GENERAL)) { aapsLogger.debug(LTag.SMS, "Ignoring SMS. Plugin disabled.") @@ -246,8 +248,8 @@ class SmsCommunicatorPlugin @Inject constructor( T.mins(sp.getLong(R.string.key_smscommunicator_remotebolusmindistance, T.msecs(Constants.remoteBolusMinDistance).mins())).msecs() else Constants.remoteBolusMinDistance - if (divided.isNotEmpty() && isCommand(divided[0].toUpperCase(Locale.getDefault()), receivedSms.phoneNumber)) { - when (divided[0].toUpperCase(Locale.getDefault())) { + if (divided.isNotEmpty() && isCommand(divided[0].uppercase(Locale.getDefault()), receivedSms.phoneNumber)) { + when (divided[0].uppercase(Locale.getDefault())) { "BG" -> if (divided.size == 1) processBG(receivedSms) else sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.wrongformat))) @@ -334,17 +336,18 @@ class SmsCommunicatorPlugin @Inject constructor( receivedSms.processed = true } + @kotlin.ExperimentalStdlibApi private fun processLOOP(divided: Array, receivedSms: Sms) { - when (divided[1].toUpperCase(Locale.getDefault())) { + when (divided[1].uppercase(Locale.getDefault())) { "DISABLE", "STOP" -> { - if (loopPlugin.isEnabled(PluginType.LOOP)) { + if (loop.enabled) { val passCode = generatePassCode() val reply = String.format(resourceHelper.gs(R.string.smscommunicator_loopdisablereplywithcode), passCode) receivedSms.processed = true messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction() { override fun run() { uel.log(Action.LOOP_DISABLED, Sources.SMS) - loopPlugin.setPluginEnabled(PluginType.LOOP, false) + loop.enabled = false commandQueue.cancelTempBasal(true, object : Callback() { override fun run() { rxBus.send(EventRefreshOverview("SMS_LOOP_STOP")) @@ -361,14 +364,14 @@ class SmsCommunicatorPlugin @Inject constructor( } "ENABLE", "START" -> { - if (!loopPlugin.isEnabled(PluginType.LOOP)) { + if (!loop.enabled) { val passCode = generatePassCode() val reply = String.format(resourceHelper.gs(R.string.smscommunicator_loopenablereplywithcode), passCode) receivedSms.processed = true messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction() { override fun run() { uel.log(Action.LOOP_ENABLED, Sources.SMS) - loopPlugin.setPluginEnabled(PluginType.LOOP, true) + loop.enabled= true sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.smscommunicator_loophasbeenenabled))) rxBus.send(EventRefreshOverview("SMS_LOOP_START")) } @@ -379,8 +382,8 @@ class SmsCommunicatorPlugin @Inject constructor( } "STATUS" -> { - val reply = if (loopPlugin.isEnabled(PluginType.LOOP)) { - if (loopPlugin.isSuspended) String.format(resourceHelper.gs(R.string.loopsuspendedfor), loopPlugin.minutesToEndOfSuspend()) + val reply = if (loop.enabled) { + if (loop.isSuspended) String.format(resourceHelper.gs(R.string.loopsuspendedfor), loop.minutesToEndOfSuspend()) else resourceHelper.gs(R.string.smscommunicator_loopisenabled) } else resourceHelper.gs(R.string.loopisdisabled) @@ -395,7 +398,12 @@ class SmsCommunicatorPlugin @Inject constructor( messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction() { override fun run() { uel.log(Action.RESUME, Sources.SMS) - loopPlugin.suspendTo(0L) + disposable += repository.runTransactionForResult(CancelCurrentOfflineEventIfAnyTransaction(dateUtil.now())) + .subscribe({ result -> + result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated OfflineEvent $it") } + }, { + aapsLogger.error(LTag.DATABASE, "Error while saving OfflineEvent", it) + }) rxBus.send(EventRefreshOverview("SMS_LOOP_RESUME")) commandQueue.cancelTempBasal(true, object : Callback() { override fun run() { @@ -406,7 +414,6 @@ class SmsCommunicatorPlugin @Inject constructor( } } }) - loopPlugin.createOfflineEvent(0) sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.smscommunicator_loopresumed))) } }) @@ -431,8 +438,13 @@ class SmsCommunicatorPlugin @Inject constructor( commandQueue.cancelTempBasal(true, object : Callback() { override fun run() { if (result.success) { - loopPlugin.suspendTo(dateUtil.now() + anInteger() * 60L * 1000) - loopPlugin.createOfflineEvent(anInteger() * 60) + disposable += repository.runTransactionForResult(InsertAndCancelCurrentOfflineEventTransaction(dateUtil.now(), T.mins(anInteger().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) + }) rxBus.send(EventRefreshOverview("SMS_LOOP_SUSPENDED")) val replyText = resourceHelper.gs(R.string.smscommunicator_loopsuspended) + " " + resourceHelper.gs(if (result.success) R.string.smscommunicator_tempbasalcanceled else R.string.smscommunicator_tempbasalcancelfailed) @@ -453,8 +465,9 @@ class SmsCommunicatorPlugin @Inject constructor( } } + @kotlin.ExperimentalStdlibApi private fun processNSCLIENT(divided: Array, receivedSms: Sms) { - if (divided[1].toUpperCase(Locale.getDefault()) == "RESTART") { + if (divided[1].uppercase(Locale.getDefault()) == "RESTART") { rxBus.send(EventNSClientRestart()) sendSMS(Sms(receivedSms.phoneNumber, "NSCLIENT RESTART SENT")) receivedSms.processed = true @@ -462,6 +475,7 @@ class SmsCommunicatorPlugin @Inject constructor( sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.wrongformat))) } + @kotlin.ExperimentalStdlibApi private fun processHELP(divided: Array, receivedSms: Sms) { when { divided.size == 1 -> { @@ -469,8 +483,8 @@ class SmsCommunicatorPlugin @Inject constructor( receivedSms.processed = true } - isCommand(divided[1].toUpperCase(Locale.getDefault()), receivedSms.phoneNumber) -> { - commands[divided[1].toUpperCase(Locale.getDefault())]?.let { + isCommand(divided[1].uppercase(Locale.getDefault()), receivedSms.phoneNumber) -> { + commands[divided[1].uppercase(Locale.getDefault())]?.let { sendSMS(Sms(receivedSms.phoneNumber, it)) receivedSms.processed = true } @@ -507,10 +521,14 @@ class SmsCommunicatorPlugin @Inject constructor( if (!result.success) { sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.smscommunicator_pumpconnectfail))) } else { - loopPlugin.suspendTo(0L) + disposable += repository.runTransactionForResult(CancelCurrentOfflineEventIfAnyTransaction(dateUtil.now())) + .subscribe({ result -> + result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated OfflineEvent $it") } + }, { + aapsLogger.error(LTag.DATABASE, "Error while saving OfflineEvent", it) + }) sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.smscommunicator_reconnect))) rxBus.send(EventRefreshOverview("SMS_PUMP_START")) - loopPlugin.createOfflineEvent(0) } } }) @@ -531,8 +549,8 @@ class SmsCommunicatorPlugin @Inject constructor( messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction() { override fun run() { uel.log(Action.DISCONNECT, Sources.SMS) - val profile = profileFunction.getProfile() - loopPlugin.disconnectPump(duration, profile) + val profile = profileFunction.getProfile() ?: return + loop.goToZeroTemp(duration, profile, OfflineEvent.Reason.DISCONNECT_PUMP) rxBus.send(EventRefreshOverview("SMS_PUMP_DISCONNECT")) sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.smscommunicator_pumpdisconnected))) } @@ -544,6 +562,7 @@ class SmsCommunicatorPlugin @Inject constructor( } } + @kotlin.ExperimentalStdlibApi private fun processPROFILE(divided: Array, receivedSms: Sms) { // load profiles val anInterface = activePlugin.activeProfileSource val store = anInterface.profile @@ -554,9 +573,9 @@ class SmsCommunicatorPlugin @Inject constructor( } val profileName = profileFunction.getProfileName() val list = store.getProfileList() - if (divided[1].toUpperCase(Locale.getDefault()) == "STATUS") { + if (divided[1].uppercase(Locale.getDefault()) == "STATUS") { sendSMS(Sms(receivedSms.phoneNumber, profileName)) - } else if (divided[1].toUpperCase(Locale.getDefault()) == "LIST") { + } else if (divided[1].uppercase(Locale.getDefault()) == "LIST") { if (list.isEmpty()) sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.invalidprofile))) else { var reply = "" @@ -597,8 +616,9 @@ class SmsCommunicatorPlugin @Inject constructor( receivedSms.processed = true } + @kotlin.ExperimentalStdlibApi private fun processBASAL(divided: Array, receivedSms: Sms) { - if (divided[1].toUpperCase(Locale.getDefault()) == "CANCEL" || divided[1].toUpperCase(Locale.getDefault()) == "STOP") { + if (divided[1].uppercase(Locale.getDefault()) == "CANCEL" || divided[1].uppercase(Locale.getDefault()) == "STOP") { val passCode = generatePassCode() val reply = String.format(resourceHelper.gs(R.string.smscommunicator_basalstopreplywithcode), passCode) receivedSms.processed = true @@ -713,8 +733,9 @@ class SmsCommunicatorPlugin @Inject constructor( } } + @kotlin.ExperimentalStdlibApi private fun processEXTENDED(divided: Array, receivedSms: Sms) { - if (divided[1].toUpperCase(Locale.getDefault()) == "CANCEL" || divided[1].toUpperCase(Locale.getDefault()) == "STOP") { + if (divided[1].uppercase(Locale.getDefault()) == "CANCEL" || divided[1].uppercase(Locale.getDefault()) == "STOP") { val passCode = generatePassCode() val reply = String.format(resourceHelper.gs(R.string.smscommunicator_extendedstopreplywithcode), passCode) receivedSms.processed = true @@ -824,7 +845,7 @@ class SmsCommunicatorPlugin @Inject constructor( currentProfile.units == GlucoseUnit.MMOL -> Constants.defaultEatingSoonTTmmol else -> Constants.defaultEatingSoonTTmgdl } - disposable += repository.runTransactionForResult(InsertTemporaryTargetAndCancelCurrentTransaction( + disposable += repository.runTransactionForResult(InsertAndCancelCurrentTemporaryTargetTransaction( timestamp = dateUtil.now(), duration = TimeUnit.MINUTES.toMillis(eatingSoonTTDuration.toLong()), reason = TemporaryTarget.Reason.EATING_SOON, @@ -879,11 +900,12 @@ class SmsCommunicatorPlugin @Inject constructor( return retVal } + @kotlin.ExperimentalStdlibApi private fun processCARBS(divided: Array, receivedSms: Sms) { var grams = SafeParse.stringToInt(divided[1]) var time = dateUtil.now() if (divided.size > 2) { - time = toTodayTime(divided[2].toUpperCase(Locale.getDefault())) + time = toTodayTime(divided[2].uppercase(Locale.getDefault())) if (time == 0L) { sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.wrongformat))) return @@ -922,6 +944,7 @@ class SmsCommunicatorPlugin @Inject constructor( } } + @kotlin.ExperimentalStdlibApi private fun processTARGET(divided: Array, receivedSms: Sms) { val isMeal = divided[1].equals("MEAL", ignoreCase = true) val isActivity = divided[1].equals("ACTIVITY", ignoreCase = true) @@ -929,7 +952,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(resourceHelper.gs(R.string.smscommunicator_temptargetwithcode), divided[1].toUpperCase(Locale.getDefault()), passCode) + val reply = String.format(resourceHelper.gs(R.string.smscommunicator_temptargetwithcode), divided[1].uppercase(Locale.getDefault()), passCode) receivedSms.processed = true messageToConfirm = AuthRequest(injector, receivedSms, reply, passCode, object : SmsAction() { override fun run() { @@ -969,7 +992,7 @@ class SmsCommunicatorPlugin @Inject constructor( var tt = sp.getDouble(keyTarget, if (units == GlucoseUnit.MMOL) defaultTargetMMOL else defaultTargetMGDL) tt = Profile.toCurrentUnits(profileFunction, tt) tt = if (tt > 0) tt else if (units == GlucoseUnit.MMOL) defaultTargetMMOL else defaultTargetMGDL - disposable += repository.runTransactionForResult(InsertTemporaryTargetAndCancelCurrentTransaction( + disposable += repository.runTransactionForResult(InsertAndCancelCurrentTemporaryTargetTransaction( timestamp = dateUtil.now(), duration = TimeUnit.MINUTES.toMillis(ttDuration.toLong()), reason = TemporaryTarget.Reason.EATING_SOON, 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 03858237281..df0881d9080 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 @@ -20,7 +20,7 @@ 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.CancelCurrentTemporaryTargetIfAnyTransaction -import info.nightscout.androidaps.database.transactions.InsertTemporaryTargetAndCancelCurrentTransaction +import info.nightscout.androidaps.database.transactions.InsertAndCancelCurrentTemporaryTargetTransaction import info.nightscout.androidaps.extensions.total import info.nightscout.androidaps.extensions.valueToUnits import info.nightscout.androidaps.interfaces.* @@ -572,7 +572,7 @@ class ActionStringHandler @Inject constructor( private fun generateTempTarget(duration: Int, low: Double, high: Double) { if (duration != 0) { - disposable += repository.runTransactionForResult(InsertTemporaryTargetAndCancelCurrentTransaction( + disposable += repository.runTransactionForResult(InsertAndCancelCurrentTemporaryTargetTransaction( timestamp = System.currentTimeMillis(), duration = TimeUnit.MINUTES.toMillis(duration.toLong()), reason = TemporaryTarget.Reason.WEAR, 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 e5662653c0b..6a0d5bb2de0 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobCalculatorPlugin.kt @@ -7,7 +7,6 @@ import info.nightscout.androidaps.Constants import info.nightscout.androidaps.R import info.nightscout.androidaps.data.IobTotal import info.nightscout.androidaps.data.MealData -import info.nightscout.androidaps.interfaces.Profile import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.ValueWrapper import info.nightscout.androidaps.database.entities.Bolus @@ -71,7 +70,6 @@ open class IobCobCalculatorPlugin @Inject constructor( private val disposable = CompositeDisposable() private var iobTable = LongSparseArray() // oldest at index 0 - private var absIobTable = LongSparseArray() // oldest at index 0, absolute insulin in the body private var basalDataTable = LongSparseArray() // oldest at index 0 override var ads: AutosensDataStore = AutosensDataStore() @@ -169,56 +167,37 @@ open class IobCobCalculatorPlugin @Inject constructor( return getBGDataFrom } - override fun calculateFromTreatmentsAndTemps(fromTime: Long, profile: Profile): IobTotal { - synchronized(dataLock) { - val now = System.currentTimeMillis() - val time = ads.roundUpTime(fromTime) - val cacheHit = iobTable[time] - if (time < now && cacheHit != null) { - //og.debug(">>> calculateFromTreatmentsAndTemps Cache hit " + new Date(time).toLocaleString()); - return cacheHit - } // else log.debug(">>> calculateFromTreatmentsAndTemps Cache miss " + new Date(time).toLocaleString()); - val bolusIob = calculateIobFromBolusToTime(time).round() - val basalIob = calculateIobToTimeFromTempBasalsIncludingConvertedExtended(time).round() - // OpenAPSSMB only - // Add expected zero temp basal for next 240 minutes - val basalIobWithZeroTemp = basalIob.copy() - val t = TemporaryBasal( - timestamp = now + 60 * 1000L, - duration = 240, - rate = 0.0, - isAbsolute = true, - type = TemporaryBasal.Type.NORMAL) - if (t.timestamp < time) { - val calc = t.iobCalc(time, profile, activePlugin.activeInsulin) - basalIobWithZeroTemp.plus(calc) - } - basalIob.iobWithZeroTemp = IobTotal.combine(bolusIob, basalIobWithZeroTemp).round() - val iobTotal = IobTotal.combine(bolusIob, basalIob).round() - if (time < System.currentTimeMillis()) { - iobTable.put(time, iobTotal) - } - return iobTotal + override fun calculateFromTreatmentsAndTemps(toTime: Long, profile: Profile): IobTotal { + val now = System.currentTimeMillis() + val time = ads.roundUpTime(toTime) + val cacheHit = iobTable[time] + if (time < now && cacheHit != null) { + //og.debug(">>> calculateFromTreatmentsAndTemps Cache hit " + new Date(time).toLocaleString()); + return cacheHit + } // else log.debug(">>> calculateFromTreatmentsAndTemps Cache miss " + new Date(time).toLocaleString()); + val bolusIob = calculateIobFromBolusToTime(time).round() + val basalIob = calculateIobToTimeFromTempBasalsIncludingConvertedExtended(time).round() + // OpenAPSSMB only + // Add expected zero temp basal for next 240 minutes + val basalIobWithZeroTemp = basalIob.copy() + val t = TemporaryBasal( + timestamp = now + 60 * 1000L, + duration = 240, + rate = 0.0, + isAbsolute = true, + type = TemporaryBasal.Type.NORMAL) + if (t.timestamp < time) { + val calc = t.iobCalc(time, profile, activePlugin.activeInsulin) + basalIobWithZeroTemp.plus(calc) } - } - - override fun calculateAbsInsulinFromTreatmentsAndTemps(fromTime: Long): IobTotal { - synchronized(dataLock) { - val now = System.currentTimeMillis() - val time = ads.roundUpTime(fromTime) - val cacheHit = absIobTable[time] - if (time < now && cacheHit != null) { - //log.debug(">>> calculateFromTreatmentsAndTemps Cache hit " + new Date(time).toLocaleString()); - return cacheHit - } // else log.debug(">>> calculateFromTreatmentsAndTemps Cache miss " + new Date(time).toLocaleString()); - val bolusIob = calculateIobFromBolusToTime(time).round() - val basalIob = calculateAbsoluteIobTempBasals(time).round() - val iobTotal = IobTotal.combine(bolusIob, basalIob).round() - if (time < System.currentTimeMillis()) { - absIobTable.put(time, iobTotal) + basalIob.iobWithZeroTemp = IobTotal.combine(bolusIob, basalIobWithZeroTemp).round() + val iobTotal = IobTotal.combine(bolusIob, basalIob).round() + if (time < System.currentTimeMillis()) { + synchronized(dataLock) { + iobTable.put(time, iobTotal) } - return iobTotal } + return iobTotal } private fun calculateFromTreatmentsAndTemps(time: Long, lastAutosensResult: AutosensResult, exercise_mode: Boolean, half_basal_exercise_target: Int, isTempTarget: Boolean): IobTotal { @@ -246,28 +225,28 @@ open class IobCobCalculatorPlugin @Inject constructor( } override fun getBasalData(profile: Profile, fromTime: Long): BasalData { - synchronized(dataLock) { - val now = System.currentTimeMillis() - val time = ads.roundUpTime(fromTime) - var retVal = basalDataTable[time] - if (retVal == null) { - //log.debug(">>> getBasalData Cache miss " + new Date(time).toLocaleString()); - retVal = BasalData() - val tb = getTempBasalIncludingConvertedExtended(time) - retVal.basal = profile.getBasal(time) - if (tb != null) { - retVal.isTempBasalRunning = true - retVal.tempBasalAbsolute = tb.convertedToAbsolute(time, profile) - } else { - retVal.isTempBasalRunning = false - retVal.tempBasalAbsolute = retVal.basal - } - if (time < now) { + val now = System.currentTimeMillis() + val time = ads.roundUpTime(fromTime) + var retVal = basalDataTable[time] + if (retVal == null) { + //log.debug(">>> getBasalData Cache miss " + new Date(time).toLocaleString()); + retVal = BasalData() + val tb = getTempBasalIncludingConvertedExtended(time) + retVal.basal = profile.getBasal(time) + if (tb != null) { + retVal.isTempBasalRunning = true + retVal.tempBasalAbsolute = tb.convertedToAbsolute(time, profile) + } else { + retVal.isTempBasalRunning = false + retVal.tempBasalAbsolute = retVal.basal + } + if (time < now) { + synchronized(dataLock) { basalDataTable.append(time, retVal) } - } //else log.debug(">>> getBasalData Cache hit " + new Date(time).toLocaleString()); - return retVal - } + } + } //else log.debug(">>> getBasalData Cache hit " + new Date(time).toLocaleString()); + return retVal } override fun getLastAutosensDataWithWaitForCalculationFinish(reason: String): AutosensData? { @@ -408,14 +387,6 @@ open class IobCobCalculatorPlugin @Inject constructor( break } } - for (index in absIobTable.size() - 1 downTo 0) { - if (absIobTable.keyAt(index) > time) { - aapsLogger.debug(LTag.AUTOSENS, "Removing from absIobTable: " + dateUtil.dateAndTimeAndSecondsString(absIobTable.keyAt(index))) - absIobTable.removeAt(index) - } else { - break - } - } for (index in basalDataTable.size() - 1 downTo 0) { if (basalDataTable.keyAt(index) > time) { aapsLogger.debug(LTag.AUTOSENS, "Removing from basalDataTable: " + dateUtil.dateAndTimeAndSecondsString(basalDataTable.keyAt(index))) @@ -459,8 +430,8 @@ open class IobCobCalculatorPlugin @Inject constructor( * Time range to the past for IOB calculation * @return milliseconds */ - fun range(): Long = ((profileFunction.getProfile()?.dia - ?: Constants.defaultDIA) * 60 * 60 * 1000).toLong() + fun range(): Long = ((/*overviewData.rangeToDisplay + */(profileFunction.getProfile()?.dia + ?: Constants.defaultDIA)) * 60 * 60 * 1000).toLong() override fun calculateIobFromBolus(): IobTotal = calculateIobFromBolusToTime(dateUtil.now()) @@ -533,6 +504,7 @@ open class IobCobCalculatorPlugin @Inject constructor( } override fun getTempBasalIncludingConvertedExtended(timestamp: Long): TemporaryBasal? { + val tb = repository.getTemporaryBasalActiveAt(timestamp).blockingGet() if (tb is ValueWrapper.Existing) return tb.value val eb = repository.getExtendedBolusActiveAt(timestamp).blockingGet() @@ -542,7 +514,7 @@ open class IobCobCalculatorPlugin @Inject constructor( return null } - override fun calculateAbsoluteIobTempBasals(toTime: Long): IobTotal { + override fun calculateAbsoluteIobFromBaseBasals(toTime: Long): IobTotal { val total = IobTotal(toTime) var i = toTime - range() while (i < toTime) { @@ -551,8 +523,7 @@ open class IobCobCalculatorPlugin @Inject constructor( i += T.mins(5).msecs() continue } - val runningTBR = getTempBasalIncludingConvertedExtended(i) - val running = runningTBR?.convertedToAbsolute(i, profile) ?: profile.getBasal(i) + val running = profile.getBasal(i) val bolus = Bolus( timestamp = i, amount = running * 5.0 / 60.0, diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.kt b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.kt index e77ff521a01..51ef6da52fc 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.kt @@ -82,7 +82,7 @@ class IobCobOref1Thread internal constructor( //log.debug("Locking calculateSensitivityData"); val oldestTimeWithData = iobCobCalculatorPlugin.calculateDetectionStart(end, limitDataToOldestAvailable) if (bgDataReload) { - iobCobCalculatorPlugin.ads.loadBgData(end, repository, aapsLogger, dateUtil) + iobCobCalculatorPlugin.ads.loadBgData(end, repository, aapsLogger, dateUtil, rxBus) iobCobCalculatorPlugin.clearCache() } // work on local copy and set back when finished @@ -99,7 +99,7 @@ class IobCobOref1Thread internal constructor( // 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)) + rxBus.send(EventIobCalculationProgress(progress, cause)) if (iobCobCalculatorPlugin.stopCalculationTrigger) { iobCobCalculatorPlugin.stopCalculationTrigger = false aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (trigger): $from") @@ -325,7 +325,7 @@ class IobCobOref1Thread internal constructor( }.start() } finally { mWakeLock?.release() - rxBus.send(EventIobCalculationProgress("")) + rxBus.send(EventIobCalculationProgress("", cause)) aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread ended: $from") profiler.log(LTag.AUTOSENS, "IobCobOref1Thread", start) } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.kt b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.kt index 18389b33f9a..cd3c12c4387 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.kt @@ -81,7 +81,7 @@ class IobCobThread @Inject internal constructor( //log.debug("Locking calculateSensitivityData"); val oldestTimeWithData = iobCobCalculatorPlugin.calculateDetectionStart(end, limitDataToOldestAvailable) if (bgDataReload) { - iobCobCalculatorPlugin.ads.loadBgData(end, repository, aapsLogger, dateUtil) + iobCobCalculatorPlugin.ads.loadBgData(end, repository, aapsLogger, dateUtil, rxBus) iobCobCalculatorPlugin.clearCache() } // work on local copy and set back when finished @@ -98,7 +98,7 @@ class IobCobThread @Inject internal constructor( // 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)) + rxBus.send(EventIobCalculationProgress(progress, cause)) if (iobCobCalculatorPlugin.stopCalculationTrigger) { iobCobCalculatorPlugin.stopCalculationTrigger = false aapsLogger.debug(LTag.AUTOSENS, "Aborting calculation thread (trigger): $from") @@ -272,7 +272,7 @@ class IobCobThread @Inject internal constructor( }.start() } finally { mWakeLock?.release() - rxBus.send(EventIobCalculationProgress("")) + rxBus.send(EventIobCalculationProgress("", cause)) aapsLogger.debug(LTag.AUTOSENS, "AUTOSENSDATA thread ended: $from") profiler.log(LTag.AUTOSENS, "IobCobThread", start) } 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 f2e8059b108..a5326e4ad3f 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 @@ -2,4 +2,4 @@ package info.nightscout.androidaps.plugins.iob.iobCobCalculator.events import info.nightscout.androidaps.events.Event -class EventIobCalculationProgress(var progress: String) : Event() \ No newline at end of file +class EventIobCalculationProgress(val progress: String, 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 24878924142..cef0babae1f 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 @@ -54,6 +54,9 @@ class LocalProfileFragment : DaggerFragment() { private val save = Runnable { doEdit() basalView?.updateLabel(resourceHelper.gs(R.string.basal_label) + ": " + sumLabel()) + localProfilePlugin.profile?.getSpecificProfile(spinner?.selectedItem.toString())?.let { + binding.basalGraph.show(ProfileSealed.Pure(it)) + } } private val textWatch = object : TextWatcher { @@ -67,7 +70,7 @@ class LocalProfileFragment : DaggerFragment() { } private fun sumLabel(): String { - val profile = localProfilePlugin.createProfileStore().getDefaultProfile() + val profile = localProfilePlugin.profile?.getDefaultProfile() val sum = profile?.let { ProfileSealed.Pure(profile).baseBasalSum() } ?: 0.0 return " ∑" + DecimalFormatter.to2Decimal(sum) + resourceHelper.gs(R.string.insulin_unit_shortname) } @@ -124,7 +127,7 @@ class LocalProfileFragment : DaggerFragment() { binding.dia.setParams(currentProfile.dia, hardLimits.minDia(), hardLimits.maxDia(), 0.1, DecimalFormat("0.0"), false, binding.save, textWatch) binding.dia.tag = "LP_DIA" TimeListEdit(context, aapsLogger, dateUtil, view, R.id.ic, "IC", resourceHelper.gs(R.string.ic_label), currentProfile.ic, null, hardLimits.minIC(), hardLimits.maxIC(), 0.1, DecimalFormat("0.0"), save) - basalView = TimeListEdit(context, aapsLogger, dateUtil, view, R.id.basal, "BASAL", resourceHelper.gs(R.string.basal_label) + ": " + sumLabel(), currentProfile.basal, null, pumpDescription.basalMinimumRate, 10.0, 0.01, DecimalFormat("0.00"), save) + basalView = TimeListEdit(context, aapsLogger, dateUtil, view, R.id.basal_holder, "BASAL", resourceHelper.gs(R.string.basal_label) + ": " + sumLabel(), currentProfile.basal, null, pumpDescription.basalMinimumRate, 10.0, 0.01, DecimalFormat("0.00"), save) if (units == Constants.MGDL) { TimeListEdit(context, aapsLogger, dateUtil, view, R.id.isf, "ISF", resourceHelper.gs(R.string.isf_label), currentProfile.isf, null, HardLimits.MIN_ISF, HardLimits.MAX_ISF, 1.0, DecimalFormat("0"), save) TimeListEdit(context, aapsLogger, dateUtil, view, R.id.target, "TARGET", resourceHelper.gs(R.string.target_label), currentProfile.targetLow, currentProfile.targetHigh, HardLimits.VERY_HARD_LIMIT_TARGET_BG[0].toDouble(), HardLimits.VERY_HARD_LIMIT_TARGET_BG[1].toDouble(), 1.0, DecimalFormat("0"), save) @@ -162,6 +165,9 @@ class LocalProfileFragment : DaggerFragment() { } } }) + localProfilePlugin.profile?.getSpecificProfile(spinner?.selectedItem.toString())?.let { + binding.basalGraph.show(ProfileSealed.Pure(it)) + } binding.profileAdd.setOnClickListener { if (localProfilePlugin.isEdited) { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/profile/local/LocalProfilePlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/profile/local/LocalProfilePlugin.kt index 12d657fe793..2feeab89d80 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/profile/local/LocalProfilePlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/profile/local/LocalProfilePlugin.kt @@ -1,6 +1,10 @@ package info.nightscout.androidaps.plugins.profile.local +import android.content.Context import androidx.fragment.app.FragmentActivity +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,7 +16,8 @@ import info.nightscout.androidaps.interfaces.* import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.plugins.bus.RxBusWrapper -import info.nightscout.androidaps.plugins.general.nsclient.NSUpload +import info.nightscout.androidaps.plugins.profile.local.events.EventLocalProfileChanged +import info.nightscout.androidaps.receivers.DataWorker import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.DecimalFormatter import info.nightscout.androidaps.utils.HardLimits @@ -22,6 +27,7 @@ import info.nightscout.androidaps.utils.sharedPreferences.SP import org.json.JSONArray import org.json.JSONException import org.json.JSONObject +import java.lang.Integer.min import java.util.* import javax.inject.Inject import javax.inject.Singleton @@ -35,7 +41,6 @@ class LocalProfilePlugin @Inject constructor( resourceHelper: ResourceHelper, private val sp: SP, private val profileFunction: ProfileFunction, - private val nsUpload: NSUpload, private val activePlugin: ActivePlugin, private val hardLimits: HardLimits, private val dateUtil: DateUtil @@ -134,6 +139,7 @@ class LocalProfilePlugin @Inject constructor( } sp.putInt(Constants.LOCAL_PROFILE + "_profiles", numOfProfiles) + sp.putLong(R.string.key_local_profile_last_change, dateUtil.now()) createAndStoreConvertedProfile() isEdited = false aapsLogger.debug(LTag.PROFILE, "Storing settings: " + rawProfile?.data.toString()) @@ -143,12 +149,9 @@ class LocalProfilePlugin @Inject constructor( val name = it.name ?: "." if (name.contains(".")) namesOK = false } - if (namesOK) - rawProfile?.let { nsUpload.uploadProfileStore(it.data) } - else - activity?.let { - OKDialog.show(it, "", resourceHelper.gs(R.string.profilenamecontainsdot)) - } + if (!namesOK) activity?.let { + OKDialog.show(it, "", resourceHelper.gs(R.string.profilenamecontainsdot)) + } } @Synchronized @@ -167,61 +170,64 @@ class LocalProfilePlugin @Inject constructor( p.dia = sp.getDouble(localProfileNumbered + "dia", Constants.defaultDIA) try { p.ic = JSONArray(sp.getString(localProfileNumbered + "ic", defaultArray)) - } catch (e1: JSONException) { - try { - p.ic = JSONArray(defaultArray) - } catch (ignored: JSONException) { - } - aapsLogger.error("Exception", e1) - } - - try { p.isf = JSONArray(sp.getString(localProfileNumbered + "isf", defaultArray)) - } catch (e1: JSONException) { - try { - p.isf = JSONArray(defaultArray) - } catch (ignored: JSONException) { - } - aapsLogger.error("Exception", e1) - } - - try { p.basal = JSONArray(sp.getString(localProfileNumbered + "basal", defaultArray)) - } catch (e1: JSONException) { - try { - p.basal = JSONArray(defaultArray) - } catch (ignored: JSONException) { - } - aapsLogger.error("Exception", e1) - } - - try { p.targetLow = JSONArray(sp.getString(localProfileNumbered + "targetlow", defaultArray)) - } catch (e1: JSONException) { - try { - p.targetLow = JSONArray(defaultArray) - } catch (ignored: JSONException) { - } - aapsLogger.error("Exception", e1) - } - - try { p.targetHigh = JSONArray(sp.getString(localProfileNumbered + "targethigh", defaultArray)) - } catch (e1: JSONException) { - try { - p.targetHigh = JSONArray(defaultArray) - } catch (ignored: JSONException) { - } - aapsLogger.error("Exception", e1) + profiles.add(p) + } catch (e: JSONException) { + aapsLogger.error("Exception", e) } - - profiles.add(p) } + // create at least one profile if doesn't exist + if (profiles.size < 1) profiles.add(defaultProfile()) isEdited = false numOfProfiles = profiles.size createAndStoreConvertedProfile() } + @Synchronized + fun loadFromStore(store: ProfileStore) { + try { + val newProfiles: ArrayList = ArrayList() + for (p in store.getProfileList()) { + store.getSpecificProfile(p.toString())?.let { + val sp = copyFrom(it, p.toString()) + sp.name = p.toString() + newProfiles.add(sp) + } + } + if (newProfiles.size > 0) { + profiles = newProfiles + numOfProfiles = profiles.size + currentProfileIndex = 0 + isEdited = false + createAndStoreConvertedProfile() + aapsLogger.debug(LTag.PROFILE, "Accepted ${profiles.size} profiles") + rxBus.send(EventLocalProfileChanged()) + } else + aapsLogger.debug(LTag.PROFILE, "ProfileStore not accepted") + } catch (e: Exception) { + aapsLogger.error("Error loading ProfileStore", e) + } + } + + private fun defaultProfile(): SingleProfile = + SingleProfile().also { p -> + p.name = Constants.LOCAL_PROFILE + p.mgdl = profileFunction.getUnits() == GlucoseUnit.MGDL + p.dia = Constants.defaultDIA + try { + p.ic = JSONArray(defaultArray) + p.isf = JSONArray(defaultArray) + p.basal = JSONArray(defaultArray) + p.targetLow = JSONArray(defaultArray) + p.targetHigh = JSONArray(defaultArray) + } catch (e: JSONException) { + aapsLogger.error("Exception", e) + } + } + fun copyFrom(pureProfile: PureProfile, newName: String): SingleProfile { var verifiedName = newName if (rawProfile?.getSpecificProfile(newName) != null) { @@ -364,7 +370,8 @@ class LocalProfilePlugin @Inject constructor( } } if (numOfProfiles > 0) json.put("defaultProfile", currentProfile()?.name) - json.put("startDate", dateUtil.toISOAsUTC(dateUtil.now())) + val startDate = sp.getLong(R.string.key_local_profile_last_change, dateUtil.now()) + json.put("startDate", dateUtil.toISOAsUTC(startDate)) json.put("store", store) } catch (e: JSONException) { aapsLogger.error("Unhandled exception", e) @@ -380,4 +387,44 @@ class LocalProfilePlugin @Inject constructor( get() = rawProfile?.getDefaultProfile()?.let { DecimalFormatter.to2Decimal(ProfileSealed.Pure(it).percentageBasalSum()) + "U " } ?: "INVALID" + + // cannot be inner class because of needed injection + class NSProfileWorker( + context: Context, + params: WorkerParameters + ) : Worker(context, params) { + + @Inject lateinit var injector: HasAndroidInjector + @Inject lateinit var aapsLogger: AAPSLogger + @Inject lateinit var rxBus: RxBusWrapper + @Inject lateinit var dateUtil: DateUtil + @Inject lateinit var dataWorker: DataWorker + @Inject lateinit var sp: SP + @Inject lateinit var config: Config + @Inject lateinit var localProfilePlugin: LocalProfilePlugin + + init { + (context.applicationContext as HasAndroidInjector).androidInjector().inject(this) + } + + override fun doWork(): Result { + val profileJson = dataWorker.pickupJSONObject(inputData.getLong(DataWorker.STORE_KEY, -1)) + ?: return Result.failure(workDataOf("Error" to "missing input data")) + if (sp.getBoolean(R.string.key_ns_receive_profile_store, false) || config.NSCLIENT) { + val store = ProfileStore(injector, profileJson, dateUtil) + val startDate = store.getStartDate() + val lastLocalChange = sp.getLong(R.string.key_local_profile_last_change, 0) + aapsLogger.debug(LTag.PROFILE, "Received profileStore: StartDate: $startDate Local last modification: $lastLocalChange") + @Suppress("LiftReturnOrAssignment") + if (startDate > lastLocalChange || startDate % 1000 == 0L) {// whole second means edited in NS + localProfilePlugin.loadFromStore(store) + aapsLogger.debug(LTag.PROFILE, "Received profileStore: $profileJson") + return Result.success(workDataOf("Data" to profileJson.toString().substring(0..min(5000, profileJson.length())))) + } else + return Result.success(workDataOf("Result" to "Unchanged. Ignoring")) + } + return Result.success(workDataOf("Result" to "Sync not enabled")) + } + } + } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/profile/ns/NSProfileFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/profile/ns/NSProfileFragment.kt deleted file mode 100644 index ea465996938..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/profile/ns/NSProfileFragment.kt +++ /dev/null @@ -1,170 +0,0 @@ -package info.nightscout.androidaps.plugins.profile.ns - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.AdapterView -import android.widget.ArrayAdapter -import dagger.android.support.DaggerFragment -import info.nightscout.androidaps.R -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.NsprofileFragmentBinding -import info.nightscout.androidaps.interfaces.ActivePlugin -import info.nightscout.androidaps.interfaces.Config -import info.nightscout.androidaps.interfaces.ProfileFunction -import info.nightscout.androidaps.logging.UserEntryLogger -import info.nightscout.androidaps.plugins.bus.RxBusWrapper -import info.nightscout.androidaps.plugins.profile.ns.events.EventNSProfileUpdateGUI -import info.nightscout.androidaps.utils.DateUtil -import info.nightscout.androidaps.utils.DecimalFormatter -import info.nightscout.androidaps.utils.FabricPrivacy -import info.nightscout.androidaps.utils.alertDialogs.OKDialog -import info.nightscout.androidaps.utils.resources.ResourceHelper -import info.nightscout.androidaps.utils.rx.AapsSchedulers -import io.reactivex.disposables.CompositeDisposable -import io.reactivex.rxkotlin.plusAssign -import javax.inject.Inject - -class NSProfileFragment : DaggerFragment() { - - @Inject lateinit var rxBus: RxBusWrapper - @Inject lateinit var resourceHelper: ResourceHelper - @Inject lateinit var fabricPrivacy: FabricPrivacy - @Inject lateinit var profileFunction: ProfileFunction - @Inject lateinit var nsProfilePlugin: NSProfilePlugin - @Inject lateinit var aapsSchedulers: AapsSchedulers - @Inject lateinit var dateUtil: DateUtil - @Inject lateinit var uel: UserEntryLogger - @Inject lateinit var activePlugin: ActivePlugin - @Inject lateinit var config: Config - - private var disposable: CompositeDisposable = CompositeDisposable() - - private var _binding: NsprofileFragmentBinding? = null - - // This property is only valid between onCreateView and - // onDestroyView. - private val binding get() = _binding!! - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?): View { - _binding = NsprofileFragmentBinding.inflate(inflater, container, false) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.profileviewer.closeLayout.close.visibility = View.GONE // not needed for fragment - - binding.profileswitch.setOnClickListener { - val name = binding.spinner.selectedItem?.toString() ?: "" - nsProfilePlugin.profile?.let { store -> - store.getSpecificProfile(name)?.let { - activity?.let { activity -> - OKDialog.showConfirmation(activity, resourceHelper.gs(R.string.nsprofile), - resourceHelper.gs(R.string.activate_profile) + ": " + name + " ?", Runnable { - uel.log(Action.PROFILE_SWITCH, Sources.NSProfile, - ValueWithUnit.SimpleString(name), - ValueWithUnit.Percent(100)) - profileFunction.createProfileSwitch(store, name, 0, 100, 0, dateUtil.now()) - }) - } - } - } - } - - binding.spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { - override fun onNothingSelected(parent: AdapterView<*>?) { - if (_binding == null) return - binding.profileviewer.invalidprofile.visibility = View.VISIBLE - binding.profileviewer.noprofile.visibility = View.VISIBLE - binding.profileviewer.units.text = "" - binding.profileviewer.dia.text = "" - binding.profileviewer.activeprofile.text = "" - binding.profileviewer.ic.text = "" - binding.profileviewer.isf.text = "" - binding.profileviewer.basal.text = "" - binding.profileviewer.basaltotal.text = "" - binding.profileviewer.target.text = "" - binding.profileswitch.visibility = View.GONE - } - - override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { - if (_binding == null) return - val name = binding.spinner.getItemAtPosition(position).toString() - - binding.profileswitch.visibility = View.GONE - - nsProfilePlugin.profile?.let { store -> - store.getSpecificProfile(name)?.let { profile -> - if (_binding == null) return - val pss = ProfileSealed.Pure(profile) - binding.profileviewer.units.text = pss.units.asText - binding.profileviewer.dia.text = resourceHelper.gs(R.string.format_hours, pss.dia) - binding.profileviewer.activeprofile.text = name - binding.profileviewer.ic.text = pss.getIcList(resourceHelper, dateUtil) - binding.profileviewer.isf.text = pss.getIsfList(resourceHelper, dateUtil) - binding.profileviewer.basal.text = pss.getBasalList(resourceHelper, dateUtil) - binding.profileviewer.basaltotal.text = String.format(resourceHelper.gs(R.string.profile_total), DecimalFormatter.to2Decimal(pss.baseBasalSum())) - binding.profileviewer.target.text = pss.getTargetList(resourceHelper, dateUtil) - binding.profileviewer.basalGraph.show(pss) - if (pss.isValid("NSProfileFragment", activePlugin.activePump, config, resourceHelper, rxBus)) { - binding.profileviewer.invalidprofile.visibility = View.GONE - binding.profileswitch.visibility = View.VISIBLE - } else { - binding.profileviewer.invalidprofile.visibility = View.VISIBLE - binding.profileswitch.visibility = View.GONE - } - } - } - } - } - } - - @Synchronized - override fun onResume() { - super.onResume() - disposable += rxBus - .toObservable(EventNSProfileUpdateGUI::class.java) - .observeOn(aapsSchedulers.main) - .subscribe({ updateGUI() }, fabricPrivacy::logException) - updateGUI() - } - - @Synchronized - override fun onPause() { - super.onPause() - disposable.clear() - } - - @Synchronized - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } - - @Synchronized - fun updateGUI() { - if (_binding == null) return - binding.profileviewer.noprofile.visibility = View.VISIBLE - - nsProfilePlugin.profile?.let { profileStore -> - context?.let { context -> - val profileList = profileStore.getProfileList() - val adapter = ArrayAdapter(context, R.layout.spinner_centered, profileList) - binding.spinner.adapter = adapter - // set selected to actual profile - for (p in profileList.indices) { - if (profileList[p] == profileFunction.getProfileName()) - binding.spinner.setSelection(p) - } - binding.profileviewer.noprofile.visibility = View.GONE - } - } - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/profile/ns/NSProfilePlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/profile/ns/NSProfilePlugin.kt deleted file mode 100644 index e2cf6875a78..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/profile/ns/NSProfilePlugin.kt +++ /dev/null @@ -1,110 +0,0 @@ -package info.nightscout.androidaps.plugins.profile.ns - -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.Config -import info.nightscout.androidaps.R -import info.nightscout.androidaps.events.EventProfileStoreChanged -import info.nightscout.androidaps.interfaces.PluginBase -import info.nightscout.androidaps.interfaces.PluginDescription -import info.nightscout.androidaps.interfaces.PluginType -import info.nightscout.androidaps.interfaces.ProfileSource -import info.nightscout.androidaps.interfaces.ProfileStore -import info.nightscout.androidaps.logging.AAPSLogger -import info.nightscout.androidaps.logging.LTag -import info.nightscout.androidaps.plugins.bus.RxBusWrapper -import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart -import info.nightscout.androidaps.plugins.profile.ns.events.EventNSProfileUpdateGUI -import info.nightscout.androidaps.receivers.DataWorker -import info.nightscout.androidaps.utils.DateUtil -import info.nightscout.androidaps.utils.resources.ResourceHelper -import info.nightscout.androidaps.utils.sharedPreferences.SP -import org.json.JSONObject -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class NSProfilePlugin @Inject constructor( - injector: HasAndroidInjector, - aapsLogger: AAPSLogger, - private val rxBus: RxBusWrapper, - resourceHelper: ResourceHelper, - private val sp: SP, - private val dateUtil: DateUtil, - config: Config -) : PluginBase(PluginDescription() - .mainType(PluginType.PROFILE) - .fragmentClass(NSProfileFragment::class.java.name) - .pluginIcon(R.drawable.ic_nightscout_profile) - .pluginName(R.string.nsprofile) - .shortName(R.string.profileviewer_shortname) - .alwaysEnabled(config.NSCLIENT) - .alwaysVisible(config.NSCLIENT) - .showInList(!config.NSCLIENT) - .description(R.string.description_profile_nightscout), - aapsLogger, resourceHelper, injector -), ProfileSource { - - override var profile: ProfileStore? = null - - override val profileName: String? - get() = profile?.getDefaultProfileName() - - override fun onStart() { - super.onStart() - loadNSProfile() - } - - private fun storeNSProfile() { - sp.putString("profile", profile!!.data.toString()) - aapsLogger.debug(LTag.PROFILE, "Storing profile") - } - - private fun loadNSProfile() { - aapsLogger.debug(LTag.PROFILE, "Loading stored profile") - val profileString = sp.getStringOrNull("profile", null) - if (profileString != null) { - aapsLogger.debug(LTag.PROFILE, "Loaded profile: $profileString") - profile = ProfileStore(injector, JSONObject(profileString), dateUtil) - } else { - aapsLogger.debug(LTag.PROFILE, "Stored profile not found") - // force restart of nsclient to fetch profile - rxBus.send(EventNSClientRestart()) - } - } - - - // cannot be inner class because of needed injection - class NSProfileWorker( - context: Context, - params: WorkerParameters - ) : Worker(context, params) { - - @Inject lateinit var injector: HasAndroidInjector - @Inject lateinit var nsProfilePlugin: NSProfilePlugin - @Inject lateinit var aapsLogger: AAPSLogger - @Inject lateinit var rxBus: RxBusWrapper - @Inject lateinit var dateUtil: DateUtil - @Inject lateinit var dataWorker: DataWorker - - init { - (context.applicationContext as HasAndroidInjector).androidInjector().inject(this) - } - - override fun doWork(): Result { - val profileString = dataWorker.pickupJSONObject(inputData.getLong(DataWorker.STORE_KEY, -1)) - ?: return Result.failure(workDataOf("Error" to "missing input data")) - nsProfilePlugin.profile = ProfileStore(injector, profileString, dateUtil) - nsProfilePlugin.storeNSProfile() - if (nsProfilePlugin.isEnabled()) { - rxBus.send(EventProfileStoreChanged()) - rxBus.send(EventNSProfileUpdateGUI()) - } - aapsLogger.debug(LTag.PROFILE, "Received profileStore: ${nsProfilePlugin.profile}") - return Result.success() - } - } -} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/profile/ns/events/EventNSProfileUpdateGUI.kt b/app/src/main/java/info/nightscout/androidaps/plugins/profile/ns/events/EventNSProfileUpdateGUI.kt deleted file mode 100644 index 0d7c320876b..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/profile/ns/events/EventNSProfileUpdateGUI.kt +++ /dev/null @@ -1,5 +0,0 @@ -package info.nightscout.androidaps.plugins.profile.ns.events - -import info.nightscout.androidaps.events.EventUpdateGui - -class EventNSProfileUpdateGUI : EventUpdateGui() diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/mdi/MDIPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/mdi/MDIPlugin.kt index b8761d0f3ab..936f2c007ad 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/mdi/MDIPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/mdi/MDIPlugin.kt @@ -3,7 +3,6 @@ package info.nightscout.androidaps.plugins.pump.mdi import dagger.android.HasAndroidInjector import info.nightscout.androidaps.R import info.nightscout.androidaps.data.DetailedBolusInfo -import info.nightscout.androidaps.interfaces.Profile import info.nightscout.androidaps.data.PumpEnactResult import info.nightscout.androidaps.interfaces.* import info.nightscout.androidaps.logging.AAPSLogger @@ -12,7 +11,6 @@ import info.nightscout.androidaps.plugins.common.ManufacturerType import info.nightscout.androidaps.plugins.pump.common.defs.PumpType import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.InstanceId.instanceId -import info.nightscout.androidaps.utils.T import info.nightscout.androidaps.utils.resources.ResourceHelper import org.json.JSONException import org.json.JSONObject @@ -61,7 +59,7 @@ class MDIPlugin @Inject constructor( override fun waitForDisconnectionInSeconds(): Int = 0 override fun stopConnecting() {} override fun getPumpStatus(reason: String) {} - override fun setNewBasalProfile(profile: Profile): PumpEnactResult = PumpEnactResult(injector).success(true) + override fun setNewBasalProfile(profile: Profile): PumpEnactResult = PumpEnactResult(injector).success(true).enacted(true) override fun isThisProfileSet(profile: Profile): Boolean = false override fun lastDataTime(): Long = System.currentTimeMillis() override val baseBasalRate: Double = 0.0 @@ -84,7 +82,7 @@ class MDIPlugin @Inject constructor( pumpSerial = serialNumber()) if (detailedBolusInfo.carbs > 0) pumpSync.syncCarbsWithTimestamp( - timestamp = detailedBolusInfo.timestamp + T.mins(detailedBolusInfo.carbTime.toLong()).msecs(), + timestamp = detailedBolusInfo.carbsTimestamp ?: detailedBolusInfo.timestamp, amount = detailedBolusInfo.carbs, pumpId = null, pumpType = PumpType.MDI, diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpPlugin.kt index 984b137af2f..21e474b310e 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpPlugin.kt @@ -4,12 +4,12 @@ import android.os.SystemClock import androidx.preference.PreferenceFragmentCompat import androidx.preference.SwitchPreference import dagger.android.HasAndroidInjector -import info.nightscout.androidaps.interfaces.Config import info.nightscout.androidaps.R import info.nightscout.androidaps.data.DetailedBolusInfo -import info.nightscout.androidaps.interfaces.Profile import info.nightscout.androidaps.data.PumpEnactResult import info.nightscout.androidaps.events.EventPreferenceChange +import info.nightscout.androidaps.extensions.convertedToAbsolute +import info.nightscout.androidaps.extensions.plannedRemainingMinutes import info.nightscout.androidaps.interfaces.* import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.LTag @@ -25,8 +25,6 @@ import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.InstanceId.instanceId import info.nightscout.androidaps.utils.T import info.nightscout.androidaps.utils.TimeChangeType -import info.nightscout.androidaps.extensions.convertedToAbsolute -import info.nightscout.androidaps.extensions.plannedRemainingMinutes import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.rx.AapsSchedulers import info.nightscout.androidaps.utils.sharedPreferences.SP @@ -139,7 +137,6 @@ open class VirtualPumpPlugin @Inject constructor( override fun isHandshakeInProgress(): Boolean = false override fun connect(reason: String) { - //if (!Config.NSCLIENT) NSUpload.uploadDeviceStatus() lastDataTime = System.currentTimeMillis() } @@ -152,16 +149,14 @@ open class VirtualPumpPlugin @Inject constructor( override fun setNewBasalProfile(profile: Profile): PumpEnactResult { lastDataTime = System.currentTimeMillis() + rxBus.send(EventNewNotification(Notification(Notification.PROFILE_SET_OK, resourceHelper.gs(R.string.profile_set_ok), Notification.INFO, 60))) // Do nothing here. we are using database profile - val result = PumpEnactResult(injector) - result.success = true - val notification = Notification(Notification.PROFILE_SET_OK, resourceHelper.gs(R.string.profile_set_ok), Notification.INFO, 60) - rxBus.send(EventNewNotification(notification)) - return result + return PumpEnactResult(injector).success(true).enacted(true) } override fun isThisProfileSet(profile: Profile): Boolean { - return true + val running = pumpSync.expectedPumpState().profile + return running?.isEqual(profile) ?: false } override fun lastDataTime(): Long { @@ -211,7 +206,7 @@ open class VirtualPumpPlugin @Inject constructor( pumpSerial = serialNumber()) if (detailedBolusInfo.carbs > 0) pumpSync.syncCarbsWithTimestamp( - timestamp = detailedBolusInfo.timestamp + T.mins(detailedBolusInfo.carbTime.toLong()).msecs(), + timestamp = detailedBolusInfo.carbsTimestamp ?: detailedBolusInfo.timestamp, amount = detailedBolusInfo.carbs, pumpId = null, pumpType = pumpType ?: PumpType.GENERIC_AAPS, @@ -400,7 +395,7 @@ open class VirtualPumpPlugin @Inject constructor( aapsLogger.debug(LTag.PUMP, "Pump in configuration: $pumpType, PumpType object: $pumpTypeNew") if (this.pumpType == pumpTypeNew) return aapsLogger.debug(LTag.PUMP, "New pump configuration found ($pumpTypeNew), changing from previous (${this.pumpType})") - pumpDescription.setPumpDescription(pumpTypeNew) + pumpDescription.fillFor(pumpTypeNew) this.pumpType = pumpTypeNew } 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 e7c9f064079..d744d813407 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 @@ -8,7 +8,6 @@ import androidx.work.Worker import androidx.work.WorkerParameters import androidx.work.workDataOf import dagger.android.HasAndroidInjector -import info.nightscout.androidaps.interfaces.Config import info.nightscout.androidaps.R import info.nightscout.androidaps.activities.RequestDexcomPermissionActivity import info.nightscout.androidaps.database.AppRepository @@ -18,10 +17,8 @@ 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.CgmSourceTransaction -import info.nightscout.androidaps.interfaces.BgSource -import info.nightscout.androidaps.interfaces.PluginBase -import info.nightscout.androidaps.interfaces.PluginDescription -import info.nightscout.androidaps.interfaces.PluginType +import info.nightscout.androidaps.extensions.fromConstant +import info.nightscout.androidaps.interfaces.* import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.logging.UserEntryLogger @@ -97,7 +94,7 @@ class DexcomPlugin @Inject constructor( override fun doWork(): Result { var ret = Result.success() - if (!dexcomPlugin.isEnabled(PluginType.BGSOURCE)) return Result.success() + if (!dexcomPlugin.isEnabled(PluginType.BGSOURCE)) return Result.success(workDataOf("Result" to "Plugin not enabled")) val bundle = dataWorker.pickupBundle(inputData.getLong(DataWorker.STORE_KEY, -1)) ?: return Result.failure(workDataOf("Error" to "missing input data")) try { @@ -126,11 +123,12 @@ class DexcomPlugin @Inject constructor( meters.getBundle(i.toString())?.let { val timestamp = it.getLong("timestamp") * 1000 val now = dateUtil.now() + val value = it.getInt("meterValue").toDouble() if (timestamp > now - T.months(1).msecs() && timestamp < now) { calibrations.add(CgmSourceTransaction.Calibration( timestamp = it.getLong("timestamp") * 1000, - value = it.getInt("meterValue").toDouble(), - glucoseUnit = TherapyEvent.GlucoseUnit.MGDL + value = value, + glucoseUnit = TherapyEvent.GlucoseUnit.fromConstant(Profile.unit(value)) )) } } @@ -158,14 +156,14 @@ class DexcomPlugin @Inject constructor( } result.sensorInsertionsInserted.forEach { uel.log(Action.CAREPORTAL, - Sources.BG, + Sources.Dexcom, ValueWithUnit.Timestamp(it.timestamp), ValueWithUnit.TherapyEventType(it.type)) aapsLogger.debug(LTag.DATABASE, "Inserted sensor insertion $it") } result.calibrationsInserted.forEach { uel.log(Action.CAREPORTAL, - Sources.BG, + Sources.Dexcom, ValueWithUnit.Timestamp(it.timestamp), ValueWithUnit.TherapyEventType(it.type)) aapsLogger.debug(LTag.DATABASE, "Inserted calibration $it") diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/EversensePlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/source/EversensePlugin.kt index 434e5eb873b..a6c51cf5de9 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/source/EversensePlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/EversensePlugin.kt @@ -69,7 +69,7 @@ class EversensePlugin @Inject constructor( override fun doWork(): Result { var ret = Result.success() - if (!eversensePlugin.isEnabled(PluginType.BGSOURCE)) return Result.success() + if (!eversensePlugin.isEnabled(PluginType.BGSOURCE)) return Result.success(workDataOf("Result" to "Plugin not enabled")) val bundle = dataWorker.pickupBundle(inputData.getLong(DataWorker.STORE_KEY, -1)) ?: return Result.failure(workDataOf("Error" to "missing input data")) if (bundle.containsKey("currentCalibrationPhase")) aapsLogger.debug(LTag.BGSOURCE, "currentCalibrationPhase: " + bundle.getString("currentCalibrationPhase")) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/GlimpPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/source/GlimpPlugin.kt index 36724eb4b50..d2b167a43d0 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/source/GlimpPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/GlimpPlugin.kt @@ -56,7 +56,7 @@ class GlimpPlugin @Inject constructor( override fun doWork(): Result { var ret = Result.success() - if (!glimpPlugin.isEnabled(PluginType.BGSOURCE)) return Result.success() + if (!glimpPlugin.isEnabled(PluginType.BGSOURCE)) return Result.success(workDataOf("Result" to "Plugin not enabled")) aapsLogger.debug(LTag.BGSOURCE, "Received Glimp Data: $inputData}") val glucoseValues = mutableListOf() glucoseValues += CgmSourceTransaction.TransactionGlucoseValue( diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/NSClientSourcePlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/source/NSClientSourcePlugin.kt index d9c2f221e50..a0aa888901d 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/source/NSClientSourcePlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/NSClientSourcePlugin.kt @@ -5,12 +5,12 @@ import androidx.work.Worker import androidx.work.WorkerParameters import androidx.work.workDataOf import dagger.android.HasAndroidInjector -import info.nightscout.androidaps.interfaces.Config import info.nightscout.androidaps.R import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.entities.GlucoseValue import info.nightscout.androidaps.database.transactions.CgmSourceTransaction import info.nightscout.androidaps.interfaces.BgSource +import info.nightscout.androidaps.interfaces.Config import info.nightscout.androidaps.interfaces.PluginBase import info.nightscout.androidaps.interfaces.PluginDescription import info.nightscout.androidaps.interfaces.PluginType @@ -115,7 +115,7 @@ class NSClientSourcePlugin @Inject constructor( override fun doWork(): Result { var ret = Result.success() - if (!nsClientSourcePlugin.isEnabled() && !sp.getBoolean(R.string.key_ns_autobackfill, true) && !dexcomPlugin.isEnabled()) return Result.success() + if (!nsClientSourcePlugin.isEnabled() && !sp.getBoolean(R.string.key_ns_receive_cgm, true) && !dexcomPlugin.isEnabled()) return Result.success(workDataOf("Result" to "Sync not enabled")) val sgvs = dataWorker.pickupJSONArray(inputData.getLong(DataWorker.STORE_KEY, -1)) ?: return Result.failure(workDataOf("Error" to "missing input data")) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/PoctechPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/source/PoctechPlugin.kt index 17aefac5b5a..fa7e0a278a3 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/source/PoctechPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/PoctechPlugin.kt @@ -60,7 +60,7 @@ class PoctechPlugin @Inject constructor( override fun doWork(): Result { var ret = Result.success() - if (!poctechPlugin.isEnabled(PluginType.BGSOURCE)) return Result.success() + if (!poctechPlugin.isEnabled(PluginType.BGSOURCE)) return Result.success(workDataOf("Result" to "Plugin not enabled")) aapsLogger.debug(LTag.BGSOURCE, "Received Poctech Data $inputData") try { val glucoseValues = mutableListOf() diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/TomatoPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/source/TomatoPlugin.kt index cf6fce3da51..52773e1d767 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/source/TomatoPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/TomatoPlugin.kt @@ -59,7 +59,7 @@ class TomatoPlugin @Inject constructor( override fun doWork(): Result { var ret = Result.success() - if (!tomatoPlugin.isEnabled(PluginType.BGSOURCE)) return Result.success() + if (!tomatoPlugin.isEnabled(PluginType.BGSOURCE)) return Result.success(workDataOf("Result" to "Plugin not enabled")) val glucoseValues = mutableListOf() glucoseValues += CgmSourceTransaction.TransactionGlucoseValue( timestamp = inputData.getLong("com.fanqies.tomatofn.Extras.Time", 0), diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/XdripPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/source/XdripPlugin.kt index 622d69c2841..bbb8b2c7e13 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/source/XdripPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/XdripPlugin.kt @@ -73,7 +73,7 @@ class XdripPlugin @Inject constructor( override fun doWork(): Result { var ret = Result.success() - if (!xdripPlugin.isEnabled(PluginType.BGSOURCE)) return Result.success() + if (!xdripPlugin.isEnabled(PluginType.BGSOURCE)) return Result.success(workDataOf("Result" to "Plugin not enabled")) val bundle = dataWorker.pickupBundle(inputData.getLong(DataWorker.STORE_KEY, -1)) ?: return Result.failure(workDataOf("Error" to "missing input data")) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentService.java b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentService.java index 3619e209f6a..a8293002f82 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentService.java @@ -105,25 +105,10 @@ public List queryForAll() throws SQLException { return wrapped.queryForAll(); } - public void delete(Treatment data) throws SQLException { - wrapped.delete(data); - openHumansUploader.enqueueTreatment(data, true); - } - - public void create(Treatment data) throws SQLException { - wrapped.create(data); - openHumansUploader.enqueueTreatment(data); - } - public Treatment queryForId(long id) throws SQLException { return wrapped.queryForId(id); } - public void update(Treatment data) throws SQLException { - wrapped.update(data); - openHumansUploader.enqueueTreatment(data); - } - public QueryBuilder queryBuilder() { return wrapped.queryBuilder(); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsPlugin.java index 734d9edd244..04345c3c6d9 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsPlugin.java @@ -26,7 +26,6 @@ import info.nightscout.androidaps.interfaces.TreatmentsInterface; import info.nightscout.androidaps.logging.AAPSLogger; import info.nightscout.androidaps.plugins.bus.RxBusWrapper; -import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; import info.nightscout.androidaps.utils.DateUtil; import info.nightscout.androidaps.utils.FabricPrivacy; import info.nightscout.androidaps.utils.resources.ResourceHelper; @@ -37,12 +36,10 @@ @Singleton public class TreatmentsPlugin extends PluginBase implements TreatmentsInterface { - private final AapsSchedulers aapsSchedulers; private final SP sp; private final RxBusWrapper rxBus; private final ProfileFunction profileFunction; private final ActivePlugin activePlugin; - private final NSUpload nsUpload; private final FabricPrivacy fabricPrivacy; private final DateUtil dateUtil; private final DatabaseHelperInterface databaseHelper; @@ -63,7 +60,6 @@ public TreatmentsPlugin( SP sp, ProfileFunction profileFunction, ActivePlugin activePlugin, - NSUpload nsUpload, FabricPrivacy fabricPrivacy, DateUtil dateUtil, DatabaseHelperInterface databaseHelper, @@ -71,7 +67,6 @@ public TreatmentsPlugin( ) { super(new PluginDescription() .mainType(PluginType.TREATMENT) - .fragmentClass(TreatmentsFragment.class.getName()) .pluginIcon(R.drawable.ic_treatments) .pluginName(R.string.treatments) .shortName(R.string.treatments_shortname) @@ -81,13 +76,11 @@ public TreatmentsPlugin( aapsLogger, resourceHelper, injector ); this.rxBus = rxBus; - this.aapsSchedulers = aapsSchedulers; this.sp = sp; this.profileFunction = profileFunction; this.activePlugin = activePlugin; this.fabricPrivacy = fabricPrivacy; this.dateUtil = dateUtil; - this.nsUpload = nsUpload; this.databaseHelper = databaseHelper; this.repository = repository; } diff --git a/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.kt b/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.kt index 448e917ae98..cdaba94dc64 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.kt +++ b/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.kt @@ -86,7 +86,6 @@ open class CommandQueue @Inject constructor( ErrorHelperActivity.runAlarm(context, result.comment, resourceHelper.gs(R.string.failedupdatebasalprofile), R.raw.boluserror) } if (result.enacted) { - rxBus.send(EventNewBasalProfile()) repository.createEffectiveProfileSwitch( EffectiveProfileSwitch( timestamp = dateUtil.now(), @@ -104,6 +103,7 @@ open class CommandQueue @Inject constructor( insulinConfiguration = it.insulinConfiguration ) ) + rxBus.send(EventNewBasalProfile()) } } }) @@ -566,11 +566,12 @@ open class CommandQueue @Inject constructor( return HtmlHelper.fromHtml(s) } - override fun isThisProfileSet(profile: Profile): Boolean { - val result = activePlugin.activePump.isThisProfileSet(profile) + override fun isThisProfileSet(requestedProfile: Profile): Boolean { + val runningProfile = profileFunction.getProfile() ?: return false + val result = activePlugin.activePump.isThisProfileSet(requestedProfile) && requestedProfile.isEqual(runningProfile) if (!result) { aapsLogger.debug(LTag.PUMPQUEUE, "Current profile: ${profileFunction.getProfile()}") - aapsLogger.debug(LTag.PUMPQUEUE, "New profile: $profile") + aapsLogger.debug(LTag.PUMPQUEUE, "New profile: $requestedProfile") } return result } diff --git a/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandSetProfile.kt b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandSetProfile.kt index c795c988fb5..7759448f6f7 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandSetProfile.kt +++ b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandSetProfile.kt @@ -6,6 +6,7 @@ import info.nightscout.androidaps.data.PumpEnactResult import info.nightscout.androidaps.database.ValueWrapper import info.nightscout.androidaps.interfaces.ActivePlugin import info.nightscout.androidaps.interfaces.CommandQueueProvider +import info.nightscout.androidaps.interfaces.Config import info.nightscout.androidaps.interfaces.PluginType import info.nightscout.androidaps.interfaces.Profile import info.nightscout.androidaps.logging.LTag @@ -25,9 +26,10 @@ class CommandSetProfile constructor( @Inject lateinit var activePlugin: ActivePlugin @Inject lateinit var dateUtil: DateUtil @Inject lateinit var commandQueue: CommandQueueProvider + @Inject lateinit var config: Config override fun execute() { - if (commandQueue.isThisProfileSet(profile)) { + if (commandQueue.isThisProfileSet(profile) && repository.getEffectiveProfileSwitchActiveAt(dateUtil.now()).blockingGet() is ValueWrapper.Existing) { aapsLogger.debug(LTag.PUMPQUEUE, "Correct profile already set. profile: $profile") callback?.result(PumpEnactResult(injector).success(true).enacted(false))?.run() return @@ -37,7 +39,7 @@ class CommandSetProfile constructor( callback?.result(r)?.run() // Send SMS notification if ProfileSwitch is coming from NS val profileSwitch = repository.getEffectiveProfileSwitchActiveAt(dateUtil.now()).blockingGet() - if (profileSwitch is ValueWrapper.Existing && r.enacted && hasNsId) { + if (profileSwitch is ValueWrapper.Existing && r.enacted && hasNsId && !config.NSCLIENT) { if (smsCommunicatorPlugin.isEnabled(PluginType.GENERAL)) smsCommunicatorPlugin.sendNotificationToAllNumbers(resourceHelper.gs(R.string.profile_set_ok)) } diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveReceiver.kt b/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveReceiver.kt index afc160dd539..48f073e8044 100644 --- a/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveReceiver.kt +++ b/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveReceiver.kt @@ -119,17 +119,18 @@ class KeepAliveReceiver : DaggerBroadcastReceiver() { private fun checkPump() { val pump = activePlugin.activePump val ps = profileFunction.getRequestedProfile() ?: return - val profile = ProfileSealed.PS(ps) + val requestedProfile = ProfileSealed.PS(ps) + val runningProfile = profileFunction.getProfile() val lastConnection = pump.lastDataTime() val isStatusOutdated = lastConnection + STATUS_UPDATE_FREQUENCY < System.currentTimeMillis() - val isBasalOutdated = abs(profile.getBasal() - pump.baseBasalRate) > pump.pumpDescription.basalStep + 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, loopPlugin.isDisconnected) } - if (!pump.isThisProfileSet(profile) && !commandQueue.isRunning(Command.CommandType.BASAL_PROFILE)) { + 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() diff --git a/app/src/main/java/info/nightscout/androidaps/setupwizard/SWDefinition.kt b/app/src/main/java/info/nightscout/androidaps/setupwizard/SWDefinition.kt index 469b30269d6..b65a2841499 100644 --- a/app/src/main/java/info/nightscout/androidaps/setupwizard/SWDefinition.kt +++ b/app/src/main/java/info/nightscout/androidaps/setupwizard/SWDefinition.kt @@ -19,11 +19,8 @@ import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesFragm import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesPlugin import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientStatus -import info.nightscout.androidaps.plugins.general.nsclient.services.NSClientService import info.nightscout.androidaps.plugins.profile.local.LocalProfileFragment import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin -import info.nightscout.androidaps.plugins.profile.ns.NSProfileFragment -import info.nightscout.androidaps.plugins.profile.ns.NSProfilePlugin import info.nightscout.androidaps.plugins.pump.common.events.EventRileyLinkDeviceStatusChange import info.nightscout.androidaps.plugins.pump.omnipod.dash.OmnipodDashPumpPlugin import info.nightscout.androidaps.plugins.pump.omnipod.eros.OmnipodErosPumpPlugin @@ -53,7 +50,6 @@ class SWDefinition @Inject constructor( private val configBuilder: ConfigBuilder, private val loopPlugin: LoopPlugin, private val nsClientPlugin: NSClientPlugin, - private val nsProfilePlugin: NSProfilePlugin, private val importExportPrefs: ImportExportPrefs, private val androidPermission: AndroidPermission, private val cryptoUtil: CryptoUtil, @@ -189,8 +185,8 @@ class SWDefinition @Inject constructor( .label(R.string.status) .initialStatus(nsClientPlugin.status) ) - .validator { nsClientPlugin.nsClientService != null && NSClientService.isConnected && NSClientService.hasWriteAuth } - .visibility { !(nsClientPlugin.nsClientService != null && NSClientService.isConnected && NSClientService.hasWriteAuth) } + .validator { nsClientPlugin.nsClientService?.isConnected == true && nsClientPlugin.nsClientService?.hasWriteAuth == true } + .visibility { !(nsClientPlugin.nsClientService?.isConnected == true && nsClientPlugin.nsClientService?.hasWriteAuth == true) } private val screenPatientName = SWScreen(injector, R.string.patient_name) .skippable(true) .add(SWInfoText(injector) @@ -254,27 +250,14 @@ class SWDefinition @Inject constructor( .option(PluginType.BGSOURCE, R.string.configbuilder_bgsource_description) .label(R.string.configbuilder_bgsource)) .add(SWBreak(injector)) - private val screenProfile = SWScreen(injector, R.string.configbuilder_profile) - .skippable(false) - .add(SWInfoText(injector) - .label(R.string.setupwizard_profile_description)) - .add(SWBreak(injector)) - .add(SWPlugin(injector, this) - .option(PluginType.PROFILE, R.string.configbuilder_profile_description) - .label(R.string.configbuilder_profile)) - private val screenNsProfile = SWScreen(injector, R.string.nsprofile) - .skippable(false) - .add(SWInfoText(injector) - .label(R.string.adjustprofileinns)) - .add(SWFragment(injector, this) - .add(NSProfileFragment())) - .validator { nsProfilePlugin.profile?.getDefaultProfile()?.let { ProfileSealed.Pure(it).isValid("StartupWizard", activePlugin.activePump, config, resourceHelper, rxBus) } ?: false } - .visibility { nsProfilePlugin.isEnabled() } private val screenLocalProfile = SWScreen(injector, R.string.localprofile) .skippable(false) .add(SWFragment(injector, this) .add(LocalProfileFragment())) - .validator { localProfilePlugin.profile?.getDefaultProfile()?.let { ProfileSealed.Pure(it).isValid("StartupWizard", activePlugin.activePump, config, resourceHelper, rxBus) } ?: false } + .validator { + localProfilePlugin.profile?.getDefaultProfile()?.let { ProfileSealed.Pure(it).isValid("StartupWizard", activePlugin.activePump, config, resourceHelper, rxBus) } + ?: false + } .visibility { localProfilePlugin.isEnabled() } private val screenProfileSwitch = SWScreen(injector, R.string.careportal_profileswitch) .skippable(false) @@ -399,8 +382,6 @@ class SWDefinition @Inject constructor( .add(screenAge) .add(screenInsulin) .add(screenBgSource) - .add(screenProfile) - .add(screenNsProfile) .add(screenLocalProfile) .add(screenProfileSwitch) .add(screenPump) @@ -428,8 +409,6 @@ class SWDefinition @Inject constructor( .add(screenAge) .add(screenInsulin) .add(screenBgSource) - .add(screenProfile) - .add(screenNsProfile) .add(screenLocalProfile) .add(screenProfileSwitch) .add(screenPump) diff --git a/app/src/main/java/info/nightscout/androidaps/utils/JSONFormatter.kt b/app/src/main/java/info/nightscout/androidaps/utils/JSONFormatter.kt index 7d5247c8857..adda9f9cc29 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/JSONFormatter.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/JSONFormatter.kt @@ -17,14 +17,15 @@ class JSONFormatter @Inject constructor( private val aapsLogger: AAPSLogger ) { + @kotlin.ExperimentalStdlibApi fun format(jsonString: String?): Spanned { jsonString ?: return fromHtml("") val visitor = JsonVisitor(1, '\t') return try { when { - jsonString == "undefined" -> fromHtml("undefined") - jsonString.toByteArray()[0] == '['.toByte() -> fromHtml(visitor.visit(JSONArray(jsonString), 0)) - else -> fromHtml(visitor.visit(JSONObject(jsonString), 0)) + jsonString == "undefined" -> fromHtml("undefined") + jsonString.toByteArray()[0] == '['.code.toByte() -> fromHtml(visitor.visit(JSONArray(jsonString), 0)) + else -> fromHtml(visitor.visit(JSONObject(jsonString), 0)) } } catch (e: JSONException) { aapsLogger.error("Unhandled exception", e) diff --git a/app/src/main/java/info/nightscout/androidaps/utils/TrendCalculator.kt b/app/src/main/java/info/nightscout/androidaps/utils/TrendCalculator.kt index ac7738043d8..a5f2dc2dcb9 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/TrendCalculator.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/TrendCalculator.kt @@ -10,9 +10,12 @@ class TrendCalculator @Inject constructor( private val repository: AppRepository ) { - fun getTrendArrow(glucoseValue: GlucoseValue): GlucoseValue.TrendArrow = - if (glucoseValue.trendArrow != GlucoseValue.TrendArrow.NONE) glucoseValue.trendArrow - else calculateDirection(glucoseValue) + fun getTrendArrow(glucoseValue: GlucoseValue?): GlucoseValue.TrendArrow = + when { + glucoseValue?.trendArrow == null -> GlucoseValue.TrendArrow.NONE + glucoseValue.trendArrow != GlucoseValue.TrendArrow.NONE -> glucoseValue.trendArrow + else -> calculateDirection(glucoseValue) + } private fun calculateDirection(glucoseValue: GlucoseValue): GlucoseValue.TrendArrow { diff --git a/app/src/main/java/info/nightscout/androidaps/utils/wizard/BolusWizard.kt b/app/src/main/java/info/nightscout/androidaps/utils/wizard/BolusWizard.kt index a9f2462bc67..cce8d1b195a 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 @@ -12,6 +12,7 @@ import info.nightscout.androidaps.data.DetailedBolusInfo import info.nightscout.androidaps.interfaces.Profile import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.entities.BolusCalculatorResult +import info.nightscout.androidaps.database.entities.OfflineEvent import info.nightscout.androidaps.database.entities.ValueWithUnit import info.nightscout.androidaps.database.entities.TemporaryTarget import info.nightscout.androidaps.database.entities.UserEntry.Action @@ -374,7 +375,7 @@ class BolusWizard @Inject constructor( if (useSuperBolus) { uel.log(Action.SUPERBOLUS_TBR, Sources.WizardDialog) if (loopPlugin.isEnabled(PluginType.LOOP)) { - loopPlugin.superBolusTo(System.currentTimeMillis() + 2 * 60L * 60 * 1000) + loopPlugin.goToZeroTemp(2 * 60, profile, OfflineEvent.Reason.SUPER_BOLUS) rxBus.send(EventRefreshOverview("WizardDialog")) } @@ -408,7 +409,7 @@ class BolusWizard @Inject constructor( context = ctx mgdlGlucose = Profile.toMgdl(bg, profile.units) glucoseType = DetailedBolusInfo.MeterType.MANUAL - carbTime = this@BolusWizard.carbTime + carbsTimestamp = dateUtil.now() + T.mins(this@BolusWizard.carbTime.toLong()).msecs() bolusCalculatorResult = createBolusCalculatorResult() notes = this@BolusWizard.notes if (insulin > 0 || carbs > 0) { diff --git a/app/src/main/res/layout/activity_historybrowse.xml b/app/src/main/res/layout/activity_historybrowse.xml index 98ce50f0c47..65bd506f7e2 100644 --- a/app/src/main/res/layout/activity_historybrowse.xml +++ b/app/src/main/res/layout/activity_historybrowse.xml @@ -4,7 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context="info.nightscout.androidaps.historyBrowser.HistoryBrowseActivity"> + tools:context="info.nightscout.androidaps.activities.HistoryBrowseActivity"> diff --git a/app/src/main/res/layout/dialog_loop.xml b/app/src/main/res/layout/dialog_loop.xml index b0658e56e13..594d1855e33 100644 --- a/app/src/main/res/layout/dialog_loop.xml +++ b/app/src/main/res/layout/dialog_loop.xml @@ -51,6 +51,7 @@ diff --git a/app/src/main/res/layout/localprofile_fragment.xml b/app/src/main/res/layout/localprofile_fragment.xml index 5893faa7b6c..e66a0c9c62c 100644 --- a/app/src/main/res/layout/localprofile_fragment.xml +++ b/app/src/main/res/layout/localprofile_fragment.xml @@ -92,8 +92,8 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" - android:weightSum="5" - android:paddingBottom="10dp"> + android:paddingBottom="10dp" + android:weightSum="5"> + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical"> + + + + + + + android:text="@string/activate_profile" /> - - - - - - - - - - -