From 59fb27eea2a2c82c1740a5a8606dc97fb387ccf6 Mon Sep 17 00:00:00 2001 From: Afrezza Feature Date: Tue, 2 Jun 2026 02:26:00 +0000 Subject: [PATCH 01/11] feat: Add Afrezza inhaled insulin string resources Add string resources for Afrezza (Technosphere) inhaled insulin support: - core/interfaces: insulin type label and description - core/ui: navigation label and treatment description - ui: Afrezza quick-log dialog strings --- core/interfaces/src/main/res/values/strings.xml | 2 ++ core/ui/src/main/res/values/strings.xml | 2 ++ ui/src/main/res/values/strings.xml | 7 +++++++ 3 files changed, 11 insertions(+) diff --git a/core/interfaces/src/main/res/values/strings.xml b/core/interfaces/src/main/res/values/strings.xml index f307b7a52071..7532d0dc3968 100644 --- a/core/interfaces/src/main/res/values/strings.xml +++ b/core/interfaces/src/main/res/values/strings.xml @@ -114,6 +114,8 @@ Fiasp Novorapid, Novolog, Humalog Fiasp + Afrezza (Inhaled) + Technosphere inhaled insulin — peak ~40 min, duration ~2.5 h Peak Time [min] diff --git a/core/ui/src/main/res/values/strings.xml b/core/ui/src/main/res/values/strings.xml index b902a6e68f1f..1977bf3c73f6 100644 --- a/core/ui/src/main/res/values/strings.xml +++ b/core/ui/src/main/res/values/strings.xml @@ -211,6 +211,7 @@ Select site location Site location Insulin + Afrezza Stop temp target Closed Loop Open Loop @@ -403,6 +404,7 @@ Bolus wizard Record insulin and carbs together Deliver bolus manually + Log inhaled Afrezza dose Record carbs without insulin Calculate bolus Insulin diff --git a/ui/src/main/res/values/strings.xml b/ui/src/main/res/values/strings.xml index f27f6113c037..76465b1fa2d1 100644 --- a/ui/src/main/res/values/strings.xml +++ b/ui/src/main/res/values/strings.xml @@ -341,4 +341,11 @@ Glucose is changing too fast (Δ %1$s %3$s per 5 min, limit %2$s). Wait for stability. Mark sensor change + + Log Afrezza Dose + Log %1$dU Afrezza? + %1$dU Afrezza logged + Afrezza inhaled + Add Afrezza insulin in Insulin Management first + From 657635b7378564e016a179abba6a403ae0406476 Mon Sep 17 00:00:00 2001 From: Afrezza Feature Date: Tue, 2 Jun 2026 02:26:10 +0000 Subject: [PATCH 02/11] feat: Add InsulinType.OREF_INHALED_AFREZZA and inhaled DIA limits - Add OREF_INHALED_AFREZZA enum entry (peak=40min, DIA=2.5h, value=6) - Add isInhaled property to InsulinType for delivery route identification - Add MIN_DIA_INHALED/MAX_DIA_INHALED to HardLimits (1.5h-4.0h range) - Add minDiaInhaled()/maxDiaInhaled() to HardLimits interface, impl, and test mock Afrezza (Technosphere) pharmacokinetics: onset ~12min, peak ~40min, clinical duration 1.5-3h depending on cartridge size (4U/8U/12U). Conservative DIA of 2.5h covers the majority of insulin activity. --- .../kotlin/app/aaps/core/interfaces/insulin/InsulinType.kt | 5 +++-- .../kotlin/app/aaps/core/interfaces/utils/HardLimits.kt | 7 +++++++ .../kotlin/app/aaps/implementation/utils/HardLimitsImpl.kt | 2 ++ .../main/kotlin/app/aaps/shared/tests/HardLimitsMock.kt | 2 ++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/insulin/InsulinType.kt b/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/insulin/InsulinType.kt index c65bbe25fbbf..529a0012c86b 100644 --- a/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/insulin/InsulinType.kt +++ b/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/insulin/InsulinType.kt @@ -5,7 +5,7 @@ import app.aaps.core.data.model.ICfg import app.aaps.core.interfaces.R import app.aaps.core.interfaces.resources.ResourceHelper -enum class InsulinType(val value: Int, val insulinEndTime: Long, val insulinPeakTime: Long, @StringRes val label: Int, @StringRes val comment: Int) { +enum class InsulinType(val value: Int, val insulinEndTime: Long, val insulinPeakTime: Long, @StringRes val label: Int, @StringRes val comment: Int, val isInhaled: Boolean = false) { UNKNOWN(-1, 0, 0, R.string.unknown, R.string.unknown), // int FAST_ACTING_INSULIN = 0; // old model no longer available @@ -13,7 +13,8 @@ enum class InsulinType(val value: Int, val insulinEndTime: Long, val insulinPeak OREF_RAPID_ACTING(2, 8 * 3600 * 1000, 75 * 60000, R.string.rapid_acting_oref, R.string.fast_acting_insulin_comment), OREF_ULTRA_RAPID_ACTING(3, 8 * 3600 * 1000, 55 * 60000, R.string.ultra_rapid_oref, R.string.ultra_fast_acting_insulin_comment), OREF_FREE_PEAK(4, 8 * 3600 * 1000, 50 * 60000, R.string.free_peak_oref, R.string.insulin_peak_time), - OREF_LYUMJEV(5, 8 * 3600 * 1000, 45 * 60000, R.string.lyumjev, R.string.lyumjev); + OREF_LYUMJEV(5, 8 * 3600 * 1000, 45 * 60000, R.string.lyumjev, R.string.lyumjev), + OREF_INHALED_AFREZZA(6, (2.5 * 3600 * 1000).toLong(), 40 * 60000L, R.string.inhaled_afrezza, R.string.inhaled_afrezza_comment, isInhaled = true); val iCfg: ICfg get() = ICfg(this.name, insulinEndTime, insulinPeakTime, 1.0) diff --git a/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/utils/HardLimits.kt b/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/utils/HardLimits.kt index 0789a131d804..a746ae69f7c2 100644 --- a/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/utils/HardLimits.kt +++ b/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/utils/HardLimits.kt @@ -17,6 +17,11 @@ interface HardLimits { val LIMIT_TEMP_TARGET_BG = doubleArrayOf(72.0, 200.0) val MIN_DIA = doubleArrayOf(5.0, 5.0, 5.0, 5.0, 5.0) val MAX_DIA = doubleArrayOf(9.0, 9.0, 9.0, 9.0, 10.0) + val MAX_DIA = doubleArrayOf(10.0, 10.0, 10.0, 10.0, 10.0) + val MAX_DIA = doubleArrayOf(9.0, 9.0, 9.0, 9.0, 10.0) + val MIN_DIA_INHALED = doubleArrayOf(1.5, 1.5, 1.5, 1.5, 1.5) // Inhaled insulin (e.g. Afrezza) has shorter DIA + val MAX_DIA_INHALED = doubleArrayOf(4.0, 4.0, 4.0, 4.0, 4.0) +>>>>>>> feat: Add InsulinType.OREF_INHALED_AFREZZA and inhaled DIA limits const val MIN_PEAK = 35 // mgdl const val MAX_PEAK = 120 // mgdl val MIN_IC = doubleArrayOf(2.0, 2.0, 2.0, 2.0, 0.3) @@ -41,6 +46,8 @@ interface HardLimits { fun maxBasal(): Double fun minDia(): Double fun maxDia(): Double + fun minDiaInhaled(): Double + fun maxDiaInhaled(): Double fun minPeak(): Int fun maxPeak(): Int fun minIC(): Double diff --git a/implementation/src/main/kotlin/app/aaps/implementation/utils/HardLimitsImpl.kt b/implementation/src/main/kotlin/app/aaps/implementation/utils/HardLimitsImpl.kt index 0f2d4e602f4c..36d9b6ad85d6 100644 --- a/implementation/src/main/kotlin/app/aaps/implementation/utils/HardLimitsImpl.kt +++ b/implementation/src/main/kotlin/app/aaps/implementation/utils/HardLimitsImpl.kt @@ -51,6 +51,8 @@ class HardLimitsImpl @Inject constructor( override fun maxBasal(): Double = HardLimits.MAX_BASAL[loadAge()] override fun minDia(): Double = HardLimits.MIN_DIA[loadAge()] override fun maxDia(): Double = HardLimits.MAX_DIA[loadAge()] + override fun minDiaInhaled(): Double = HardLimits.MIN_DIA_INHALED[loadAge()] + override fun maxDiaInhaled(): Double = HardLimits.MAX_DIA_INHALED[loadAge()] override fun minPeak(): Int = HardLimits.MIN_PEAK override fun maxPeak(): Int = HardLimits.MAX_PEAK override fun minIC(): Double = HardLimits.MIN_IC[loadAge()] diff --git a/shared/tests/src/main/kotlin/app/aaps/shared/tests/HardLimitsMock.kt b/shared/tests/src/main/kotlin/app/aaps/shared/tests/HardLimitsMock.kt index 57dee30111ab..d3e8f066c500 100644 --- a/shared/tests/src/main/kotlin/app/aaps/shared/tests/HardLimitsMock.kt +++ b/shared/tests/src/main/kotlin/app/aaps/shared/tests/HardLimitsMock.kt @@ -66,6 +66,8 @@ class HardLimitsMock @Inject constructor( override fun maxBasal(): Double = MAX_BASAL[loadAge()] override fun minDia(): Double = MIN_DIA[loadAge()] override fun maxDia(): Double = MAX_DIA[loadAge()] + override fun minDiaInhaled(): Double = HardLimits.MIN_DIA_INHALED[loadAge()] + override fun maxDiaInhaled(): Double = HardLimits.MAX_DIA_INHALED[loadAge()] override fun minPeak(): Int = MIN_PEAK override fun maxPeak(): Int = MAX_PEAK override fun minIC(): Double = MIN_IC[loadAge()] From c11713c5a78e8942607af8ff150615c7e58e21dc Mon Sep 17 00:00:00 2001 From: Afrezza Feature Date: Tue, 2 Jun 2026 02:26:16 +0000 Subject: [PATCH 03/11] feat: Add Afrezza to insulin template list, fix editor DIA validation - Add OREF_INHALED_AFREZZA to insulinTemplateList() in InsulinImpl - Update InsulinManagementViewModel to use inhaled DIA limits (1.5-4.0h) when the editor template is an inhaled insulin type - Make diaRange() template-aware so the UI slider shows correct bounds - Subcutaneous insulin DIA limits (5.0-9.0h) remain unchanged --- .../aaps/implementation/insulin/InsulinImpl.kt | 3 ++- .../InsulinManagementViewModel.kt | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/implementation/src/main/kotlin/app/aaps/implementation/insulin/InsulinImpl.kt b/implementation/src/main/kotlin/app/aaps/implementation/insulin/InsulinImpl.kt index fdc459d4f561..ca14ab8d5b34 100644 --- a/implementation/src/main/kotlin/app/aaps/implementation/insulin/InsulinImpl.kt +++ b/implementation/src/main/kotlin/app/aaps/implementation/insulin/InsulinImpl.kt @@ -79,7 +79,8 @@ class InsulinImpl @Inject constructor( InsulinType.OREF_RAPID_ACTING, InsulinType.OREF_ULTRA_RAPID_ACTING, InsulinType.OREF_LYUMJEV, - InsulinType.OREF_FREE_PEAK + InsulinType.OREF_FREE_PEAK, + InsulinType.OREF_INHALED_AFREZZA ) override fun concentrationList(): List = listOf( diff --git a/ui/src/main/kotlin/app/aaps/ui/compose/insulinManagement/InsulinManagementViewModel.kt b/ui/src/main/kotlin/app/aaps/ui/compose/insulinManagement/InsulinManagementViewModel.kt index c35d93848ae2..7334647fc5c3 100644 --- a/ui/src/main/kotlin/app/aaps/ui/compose/insulinManagement/InsulinManagementViewModel.kt +++ b/ui/src/main/kotlin/app/aaps/ui/compose/insulinManagement/InsulinManagementViewModel.kt @@ -313,8 +313,11 @@ class InsulinManagementViewModel @Inject constructor( editedICfg.setDia(state.editorDiaHours) editedICfg.setPeak(state.editorPeakMinutes) - // Validation - if (editedICfg.dia < hardLimits.minDia() || editedICfg.dia > hardLimits.maxDia()) { + // Validation — use inhaled DIA limits when template is an inhaled insulin + val isInhaled = state.editorTemplate?.isInhaled == true + val minDia = if (isInhaled) hardLimits.minDiaInhaled() else hardLimits.minDia() + val maxDia = if (isInhaled) hardLimits.maxDiaInhaled() else hardLimits.maxDia() + if (editedICfg.dia < minDia || editedICfg.dia > maxDia) { showSnackbar(rh.gs(CoreUiR.string.value_out_of_hard_limits, rh.gs(CoreUiR.string.insulin_dia), editedICfg.dia)) return false } @@ -446,6 +449,12 @@ class InsulinManagementViewModel @Inject constructor( val concentrationEnabled: Boolean get() = preferences.get(BooleanKey.GeneralInsulinConcentration) - fun diaRange(): ClosedFloatingPointRange = hardLimits.minDia()..hardLimits.maxDia() + fun diaRange(): ClosedFloatingPointRange { + val isInhaled = uiState.value.editorTemplate?.isInhaled == true + val min = if (isInhaled) hardLimits.minDiaInhaled() else hardLimits.minDia() + val max = if (isInhaled) hardLimits.maxDiaInhaled() else hardLimits.maxDia() + return min..max + } + fun peakRange(): ClosedFloatingPointRange = hardLimits.minPeak().toDouble()..hardLimits.maxPeak().toDouble() } From ba8e548ea27637463c45cff9ea02f6d4d231e9b0 Mon Sep 17 00:00:00 2001 From: Afrezza Feature Date: Tue, 2 Jun 2026 02:26:30 +0000 Subject: [PATCH 04/11] test: Add IOB curve validation tests for Afrezza parameters Comprehensive test suite validating the oref bilinear model at Afrezza's short DIA (2.5h) and early peak (40min): - IOB starts near 1.0 at t=0 and reaches 0 at t=DIA - No NaN or negative values across entire curve - Peak activity occurs near configured peak time (40min) - Monotonic IOB decrease after peak - Afrezza IOB decays faster than Fiasp (comparative) - Dose scaling correct for cartridge sizes (4U/8U/12U) - Combined Afrezza+Fiasp IOB sums correctly, with Afrezza dropping to zero while Fiasp remains active --- .../core/data/model/ICfgAfrezzaIobTest.kt | 179 ++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 core/data/src/test/kotlin/app/aaps/core/data/model/ICfgAfrezzaIobTest.kt diff --git a/core/data/src/test/kotlin/app/aaps/core/data/model/ICfgAfrezzaIobTest.kt b/core/data/src/test/kotlin/app/aaps/core/data/model/ICfgAfrezzaIobTest.kt new file mode 100644 index 000000000000..8103cbee9d60 --- /dev/null +++ b/core/data/src/test/kotlin/app/aaps/core/data/model/ICfgAfrezzaIobTest.kt @@ -0,0 +1,179 @@ +package app.aaps.core.data.model + +import app.aaps.core.data.iob.Iob +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +/** + * Validate the oref bilinear IOB model with Afrezza (Technosphere inhaled insulin) parameters. + * + * Afrezza pharmacokinetics (published): + * - Onset: ~12 minutes + * - Peak (Tmax): 35-45 minutes + * - Clinical duration: 1.5-3 hours (dose-dependent) + * + * Model parameters used: + * - Peak: 40 minutes + * - DIA: 2.5 hours (150 minutes) + * - Concentration: 1.0 (U100 equivalent) + */ +class ICfgAfrezzaIobTest { + + private lateinit var afrezzaCfg: ICfg + private lateinit var fiaspCfg: ICfg + private lateinit var bolus1U: BS + + @BeforeEach + fun setup() { + afrezzaCfg = ICfg( + insulinLabel = "Afrezza (Inhaled)", + peak = 40, // minutes + dia = 2.5, // hours + concentration = 1.0 + ) + fiaspCfg = ICfg( + insulinLabel = "Fiasp", + peak = 55, // minutes + dia = 5.0, // hours + concentration = 1.0 + ) + bolus1U = BS( + timestamp = 0L, + amount = 1.0, + type = BS.Type.NORMAL, + iCfg = afrezzaCfg + ) + } + + @Test + fun `IOB starts near 1_0 at time zero`() { + // At t=1 minute after bolus, IOB should be very close to 1.0 + val iob = afrezzaCfg.iobCalcForTreatment(bolus1U, 60_000L) // 1 min in ms + assertTrue(iob.iobContrib > 0.95, "IOB at t=1min should be >0.95, was ${iob.iobContrib}") + assertTrue(iob.iobContrib <= 1.0, "IOB at t=1min should be <=1.0, was ${iob.iobContrib}") + } + + @Test + fun `IOB is zero at DIA`() { + // At t=DIA (150 minutes), IOB should be exactly 0 + val iob = afrezzaCfg.iobCalcForTreatment(bolus1U, 150L * 60_000L) + assertEquals(0.0, iob.iobContrib, 0.001, "IOB should be 0 at t=DIA") + assertEquals(0.0, iob.activityContrib, 0.001, "Activity should be 0 at t=DIA") + } + + @Test + fun `IOB is zero after DIA`() { + // At t=DIA+30min, IOB should still be 0 + val iob = afrezzaCfg.iobCalcForTreatment(bolus1U, 180L * 60_000L) + assertEquals(0.0, iob.iobContrib, 0.001, "IOB should be 0 after DIA") + } + + @Test + fun `IOB decreases monotonically after peak`() { + var prevIob = 1.0 + // Check from t=50min to t=150min (after peak, IOB should only decrease) + for (t in 50..150 step 5) { + val iob = afrezzaCfg.iobCalcForTreatment(bolus1U, t.toLong() * 60_000L) + assertTrue( + iob.iobContrib <= prevIob + 0.001, + "IOB should decrease monotonically after peak. At t=${t}min: ${iob.iobContrib} > prev: $prevIob" + ) + prevIob = iob.iobContrib + } + } + + @Test + fun `no NaN or negative values across entire curve`() { + for (t in 0..200 step 1) { + val iob = afrezzaCfg.iobCalcForTreatment(bolus1U, t.toLong() * 60_000L) + assertTrue(!iob.iobContrib.isNaN(), "IOB should not be NaN at t=${t}min") + assertTrue(!iob.activityContrib.isNaN(), "Activity should not be NaN at t=${t}min") + assertTrue(iob.iobContrib >= 0.0, "IOB should not be negative at t=${t}min, was ${iob.iobContrib}") + assertTrue(iob.activityContrib >= 0.0, "Activity should not be negative at t=${t}min, was ${iob.activityContrib}") + } + } + + @Test + fun `peak activity occurs near configured peak time`() { + var maxActivity = 0.0 + var maxActivityTime = 0 + for (t in 1..150) { + val iob = afrezzaCfg.iobCalcForTreatment(bolus1U, t.toLong() * 60_000L) + if (iob.activityContrib > maxActivity) { + maxActivity = iob.activityContrib + maxActivityTime = t + } + } + // Peak should be within +/- 10 minutes of configured peak (40 min) + assertTrue( + maxActivityTime in 30..50, + "Peak activity should occur near t=40min, was at t=${maxActivityTime}min" + ) + } + + @Test + fun `Afrezza IOB decays faster than Fiasp`() { + val fiaspBolus = BS(timestamp = 0L, amount = 1.0, type = BS.Type.NORMAL, iCfg = fiaspCfg) + + // At 90 minutes, Afrezza should have substantially less IOB than Fiasp + val afrezzaIob90 = afrezzaCfg.iobCalcForTreatment(bolus1U, 90L * 60_000L) + val fiaspIob90 = fiaspCfg.iobCalcForTreatment(fiaspBolus, 90L * 60_000L) + + assertTrue( + afrezzaIob90.iobContrib < fiaspIob90.iobContrib, + "At t=90min, Afrezza IOB (${afrezzaIob90.iobContrib}) should be less than Fiasp IOB (${fiaspIob90.iobContrib})" + ) + + // At 150 minutes (Afrezza DIA), Afrezza IOB should be ~0, Fiasp still significant + val afrezzaIob150 = afrezzaCfg.iobCalcForTreatment(bolus1U, 150L * 60_000L) + val fiaspIob150 = fiaspCfg.iobCalcForTreatment(fiaspBolus, 150L * 60_000L) + + assertEquals(0.0, afrezzaIob150.iobContrib, 0.001, "Afrezza IOB should be ~0 at t=150min") + assertTrue( + fiaspIob150.iobContrib > 0.05, + "Fiasp IOB should still be significant at t=150min, was ${fiaspIob150.iobContrib}" + ) + } + + @Test + fun `dose scaling works correctly for cartridge sizes`() { + // 4U, 8U, 12U should produce proportional IOB + val bolus4U = BS(timestamp = 0L, amount = 4.0, type = BS.Type.NORMAL, iCfg = afrezzaCfg) + val bolus8U = BS(timestamp = 0L, amount = 8.0, type = BS.Type.NORMAL, iCfg = afrezzaCfg) + val bolus12U = BS(timestamp = 0L, amount = 12.0, type = BS.Type.NORMAL, iCfg = afrezzaCfg) + + val t = 60L * 60_000L // 60 minutes + val iob4 = afrezzaCfg.iobCalcForTreatment(bolus4U, t) + val iob8 = afrezzaCfg.iobCalcForTreatment(bolus8U, t) + val iob12 = afrezzaCfg.iobCalcForTreatment(bolus12U, t) + + assertEquals(iob4.iobContrib * 2.0, iob8.iobContrib, 0.001, "8U IOB should be 2x 4U IOB") + assertEquals(iob4.iobContrib * 3.0, iob12.iobContrib, 0.001, "12U IOB should be 3x 4U IOB") + } + + @Test + fun `combined Afrezza plus Fiasp IOB sums correctly`() { + // Simulate: Afrezza 8U at t=0, Fiasp pump bolus 3U at t=0 + val afrezzaBolus = BS(timestamp = 0L, amount = 8.0, type = BS.Type.NORMAL, iCfg = afrezzaCfg) + val fiaspBolus = BS(timestamp = 0L, amount = 3.0, type = BS.Type.NORMAL, iCfg = fiaspCfg) + + val t = 60L * 60_000L // 60 minutes + val afrezzaIob = afrezzaCfg.iobCalcForTreatment(afrezzaBolus, t) + val fiaspIob = fiaspCfg.iobCalcForTreatment(fiaspBolus, t) + val combinedIob = afrezzaIob.iobContrib + fiaspIob.iobContrib + + // Combined should equal sum of individual IOBs + assertTrue(combinedIob > 0, "Combined IOB should be positive") + assertTrue(combinedIob < 11.0, "Combined IOB should be less than total dose (11U)") + + // At t=180min (well past Afrezza DIA), only Fiasp should remain + val t180 = 180L * 60_000L + val afrezzaIob180 = afrezzaCfg.iobCalcForTreatment(afrezzaBolus, t180) + val fiaspIob180 = fiaspCfg.iobCalcForTreatment(fiaspBolus, t180) + + assertEquals(0.0, afrezzaIob180.iobContrib, 0.001, "Afrezza IOB should be 0 at t=180min") + assertTrue(fiaspIob180.iobContrib > 0.1, "Fiasp IOB should still be active at t=180min") + } +} From 4784e0551d40e535cbe8a3cb8a60f512ac270a84 Mon Sep 17 00:00:00 2001 From: Afrezza Feature Date: Tue, 2 Jun 2026 02:26:39 +0000 Subject: [PATCH 05/11] feat: Add Afrezza quick-log dialog for one-tap dose logging New Compose bottom sheet dialog with three large cartridge-size buttons (4U, 8U, 12U) for rapid Afrezza dose logging: - AfrezzaDialogScreen: Bottom sheet with cartridge selection UI - AfrezzaDialogViewModel: Resolves Afrezza ICfg from InsulinManager, creates record-only bolus with correct per-bolus ICfg - AfrezzaDialogUiState: Minimal state for cartridge selection flow User flow: tap cartridge -> confirm -> done (2 taps total). Bolus is created as record-only with Afrezza ICfg, ensuring the IOB calculator uses the correct short-DIA curve. --- .../afrezzaDialog/AfrezzaDialogScreen.kt | 192 ++++++++++++++++++ .../afrezzaDialog/AfrezzaDialogUiState.kt | 13 ++ .../afrezzaDialog/AfrezzaDialogViewModel.kt | 136 +++++++++++++ 3 files changed, 341 insertions(+) create mode 100644 ui/src/main/kotlin/app/aaps/ui/compose/afrezzaDialog/AfrezzaDialogScreen.kt create mode 100644 ui/src/main/kotlin/app/aaps/ui/compose/afrezzaDialog/AfrezzaDialogUiState.kt create mode 100644 ui/src/main/kotlin/app/aaps/ui/compose/afrezzaDialog/AfrezzaDialogViewModel.kt diff --git a/ui/src/main/kotlin/app/aaps/ui/compose/afrezzaDialog/AfrezzaDialogScreen.kt b/ui/src/main/kotlin/app/aaps/ui/compose/afrezzaDialog/AfrezzaDialogScreen.kt new file mode 100644 index 000000000000..e9b316548ebe --- /dev/null +++ b/ui/src/main/kotlin/app/aaps/ui/compose/afrezzaDialog/AfrezzaDialogScreen.kt @@ -0,0 +1,192 @@ +package app.aaps.ui.compose.afrezzaDialog + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import app.aaps.core.ui.compose.dialogs.OkCancelDialog +import app.aaps.core.ui.compose.navigation.ElementType +import app.aaps.core.ui.compose.navigation.color +import app.aaps.core.ui.compose.navigation.icon +import app.aaps.ui.R +import app.aaps.core.ui.R as CoreUiR + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AfrezzaDialogScreen( + viewModel: AfrezzaDialogViewModel = hiltViewModel(), + onNavigateBack: () -> Unit, + onShowMessage: (String) -> Unit +) { + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) + + // Observe side effects + LaunchedEffect(Unit) { + viewModel.sideEffect.collect { effect -> + when (effect) { + is AfrezzaDialogViewModel.SideEffect.ShowMessage -> onShowMessage(effect.message) + is AfrezzaDialogViewModel.SideEffect.DoseLogged -> onNavigateBack() + } + } + } + + // Confirmation dialog + if (uiState.showConfirmation && uiState.selectedCartridge != null) { + OkCancelDialog( + title = stringResource(R.string.log_afrezza_dose), + message = stringResource(R.string.afrezza_confirm_log, uiState.selectedCartridge!!), + icon = ElementType.INSULIN.icon(), + iconTint = ElementType.INSULIN.color(), + onConfirm = { viewModel.confirmAndLog() }, + onDismiss = { viewModel.dismissConfirmation() } + ) + } + + ModalBottomSheet( + onDismissRequest = onNavigateBack, + sheetState = sheetState, + containerColor = MaterialTheme.colorScheme.surface + ) { + if (!uiState.isConfigured) { + // Afrezza not yet added in Insulin Management + AfrezzaNotConfiguredContent() + } else { + AfrezzaCartridgeSelector( + onCartridgeSelected = { units -> viewModel.selectCartridge(units) }, + isLogging = uiState.isLogging, + insulinLabel = uiState.afrezzaIcfg?.insulinLabel ?: "" + ) + } + } +} + +@Composable +private fun AfrezzaCartridgeSelector( + onCartridgeSelected: (Int) -> Unit, + isLogging: Boolean, + insulinLabel: String +) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(start = 24.dp, end = 24.dp, bottom = 32.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = stringResource(R.string.log_afrezza_dose), + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold + ) + + Spacer(modifier = Modifier.height(4.dp)) + + Text( + text = insulinLabel, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + + Spacer(modifier = Modifier.height(24.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(12.dp, Alignment.CenterHorizontally) + ) { + CartridgeButton(units = 4, onClick = { onCartridgeSelected(4) }, enabled = !isLogging) + CartridgeButton(units = 8, onClick = { onCartridgeSelected(8) }, enabled = !isLogging) + CartridgeButton(units = 12, onClick = { onCartridgeSelected(12) }, enabled = !isLogging) + } + + Spacer(modifier = Modifier.height(16.dp)) + } +} + +@Composable +private fun CartridgeButton( + units: Int, + onClick: () -> Unit, + enabled: Boolean = true +) { + Button( + onClick = onClick, + enabled = enabled, + modifier = Modifier.size(100.dp), + shape = RoundedCornerShape(16.dp), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.primaryContainer, + contentColor = MaterialTheme.colorScheme.onPrimaryContainer + ) + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Text( + text = "$units", + fontSize = 28.sp, + fontWeight = FontWeight.Bold + ) + Text( + text = "units", + fontSize = 12.sp + ) + } + } +} + +@Composable +private fun AfrezzaNotConfiguredContent() { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(24.dp), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.errorContainer + ) + ) { + Text( + text = stringResource(R.string.afrezza_not_configured), + modifier = Modifier.padding(16.dp), + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onErrorContainer, + textAlign = TextAlign.Center + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun AfrezzaCartridgeSelectorPreview() { + MaterialTheme { + AfrezzaCartridgeSelector( + onCartridgeSelected = {}, + isLogging = false, + insulinLabel = "Afrezza (Inhaled) 40m 2.5h U100" + ) + } +} diff --git a/ui/src/main/kotlin/app/aaps/ui/compose/afrezzaDialog/AfrezzaDialogUiState.kt b/ui/src/main/kotlin/app/aaps/ui/compose/afrezzaDialog/AfrezzaDialogUiState.kt new file mode 100644 index 000000000000..9fe4cc88c780 --- /dev/null +++ b/ui/src/main/kotlin/app/aaps/ui/compose/afrezzaDialog/AfrezzaDialogUiState.kt @@ -0,0 +1,13 @@ +package app.aaps.ui.compose.afrezzaDialog + +import androidx.compose.runtime.Immutable +import app.aaps.core.data.model.ICfg + +@Immutable +data class AfrezzaDialogUiState( + val selectedCartridge: Int? = null, // 4, 8, or 12 + val afrezzaIcfg: ICfg? = null, // Resolved Afrezza ICfg from InsulinManager + val isConfigured: Boolean = false, // Whether Afrezza insulin exists in InsulinManager + val showConfirmation: Boolean = false, + val isLogging: Boolean = false +) diff --git a/ui/src/main/kotlin/app/aaps/ui/compose/afrezzaDialog/AfrezzaDialogViewModel.kt b/ui/src/main/kotlin/app/aaps/ui/compose/afrezzaDialog/AfrezzaDialogViewModel.kt new file mode 100644 index 000000000000..5acbfc8a4a71 --- /dev/null +++ b/ui/src/main/kotlin/app/aaps/ui/compose/afrezzaDialog/AfrezzaDialogViewModel.kt @@ -0,0 +1,136 @@ +package app.aaps.ui.compose.afrezzaDialog + +import androidx.compose.runtime.Stable +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import app.aaps.core.data.model.BS +import app.aaps.core.data.model.ICfg +import app.aaps.core.data.model.IDs +import app.aaps.core.data.model.TE +import app.aaps.core.data.ue.Action +import app.aaps.core.data.ue.Sources +import app.aaps.core.data.ue.ValueWithUnit +import app.aaps.core.interfaces.db.PersistenceLayer +import app.aaps.core.interfaces.insulin.InsulinManager +import app.aaps.core.interfaces.insulin.InsulinType +import app.aaps.core.interfaces.logging.AAPSLogger +import app.aaps.core.interfaces.logging.LTag +import app.aaps.core.interfaces.logging.UserEntryLogger +import app.aaps.core.interfaces.resources.ResourceHelper +import app.aaps.core.interfaces.utils.DateUtil +import app.aaps.ui.R +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +@Stable +class AfrezzaDialogViewModel @Inject constructor( + private val insulinManager: InsulinManager, + private val persistenceLayer: PersistenceLayer, + private val uel: UserEntryLogger, + private val dateUtil: DateUtil, + private val rh: ResourceHelper, + private val aapsLogger: AAPSLogger +) : ViewModel() { + + private val _uiState = MutableStateFlow(AfrezzaDialogUiState()) + val uiState: StateFlow = _uiState.asStateFlow() + + sealed class SideEffect { + data class ShowMessage(val message: String) : SideEffect() + data object DoseLogged : SideEffect() + } + + private val _sideEffect = MutableSharedFlow( + replay = 0, + extraBufferCapacity = 1, + onBufferOverflow = BufferOverflow.DROP_OLDEST + ) + val sideEffect: SharedFlow = _sideEffect.asSharedFlow() + + init { + val afrezzaIcfg = findAfrezzaIcfg() + _uiState.update { + AfrezzaDialogUiState( + afrezzaIcfg = afrezzaIcfg, + isConfigured = afrezzaIcfg != null + ) + } + } + + /** + * Find the Afrezza ICfg from InsulinManager's configured insulins. + * Matches by peak time against the OREF_INHALED_AFREZZA template. + */ + private fun findAfrezzaIcfg(): ICfg? { + val afrezzaPeak = InsulinType.OREF_INHALED_AFREZZA.insulinPeakTime + return insulinManager.insulins.firstOrNull { it.insulinPeakTime == afrezzaPeak } + ?: insulinManager.insulins.firstOrNull { + // Fallback: match any insulin with DIA <= 4h (likely inhaled) + val template = InsulinType.fromPeak(it.insulinPeakTime) + template.isInhaled + } + } + + fun selectCartridge(units: Int) { + _uiState.update { it.copy(selectedCartridge = units, showConfirmation = true) } + } + + fun dismissConfirmation() { + _uiState.update { it.copy(showConfirmation = false, selectedCartridge = null) } + } + + fun confirmAndLog() { + val state = _uiState.value + val units = state.selectedCartridge ?: return + val iCfg = state.afrezzaIcfg ?: return + + _uiState.update { it.copy(isLogging = true) } + + viewModelScope.launch { + try { + val now = dateUtil.now() + val bolus = BS( + timestamp = now, + amount = units.toDouble(), + type = BS.Type.NORMAL, + notes = rh.gs(R.string.afrezza_inhaled), + iCfg = iCfg, + ids = IDs(pumpId = now) + ) + + persistenceLayer.insertOrUpdateBolus( + bolus = bolus, + action = Action.BOLUS, + source = Sources.AfrezzaDialog, + note = rh.gs(R.string.afrezza_inhaled) + ) + + uel.log( + Action.BOLUS, + Sources.InsulinDialog, + rh.gs(R.string.afrezza_inhaled), + ValueWithUnit.Insulin(units.toDouble()) + ) + + aapsLogger.info(LTag.UI, "Afrezza ${units}U logged with ICfg: ${iCfg.insulinLabel}") + + _sideEffect.tryEmit(SideEffect.ShowMessage(rh.gs(R.string.afrezza_logged, units))) + _sideEffect.tryEmit(SideEffect.DoseLogged) + } catch (e: Exception) { + aapsLogger.error(LTag.UI, "Failed to log Afrezza dose", e) + } finally { + _uiState.update { it.copy(isLogging = false, showConfirmation = false, selectedCartridge = null) } + } + } + } +} From 1cb1ed79c571f812be6bdcf9b137a0fda1b295e4 Mon Sep 17 00:00:00 2001 From: Afrezza Feature Date: Tue, 2 Jun 2026 09:40:21 +0000 Subject: [PATCH 06/11] feat: Add Afrezza to navigation system (ElementType, icon, label, color) - Add ElementType.AFREZZA with TREATMENT category and BOLUS protection - Map Afrezza icon (IcBolus), color (insulin), label, and description - Afrezza appears in global search and treatment action sheets --- .../kotlin/app/aaps/core/ui/compose/navigation/ElementType.kt | 1 + .../app/aaps/core/ui/compose/navigation/ElementTypeStyle.kt | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/core/ui/src/main/kotlin/app/aaps/core/ui/compose/navigation/ElementType.kt b/core/ui/src/main/kotlin/app/aaps/core/ui/compose/navigation/ElementType.kt index 4f890e70f554..92172d2aeb60 100644 --- a/core/ui/src/main/kotlin/app/aaps/core/ui/compose/navigation/ElementType.kt +++ b/core/ui/src/main/kotlin/app/aaps/core/ui/compose/navigation/ElementType.kt @@ -23,6 +23,7 @@ enum class ElementType( BOLUS_WIZARD(category = ElementCategory.TREATMENT, searchable = true, protection = ProtectionCheck.Protection.BOLUS), QUICK_WIZARD(protection = ProtectionCheck.Protection.BOLUS), TREATMENT(category = ElementCategory.TREATMENT, searchable = true, protection = ProtectionCheck.Protection.BOLUS), + AFREZZA(category = ElementCategory.TREATMENT, searchable = true, protection = ProtectionCheck.Protection.BOLUS), // CGM CGM_XDRIP(category = ElementCategory.CGM, searchable = true), diff --git a/core/ui/src/main/kotlin/app/aaps/core/ui/compose/navigation/ElementTypeStyle.kt b/core/ui/src/main/kotlin/app/aaps/core/ui/compose/navigation/ElementTypeStyle.kt index a5348762d458..fc99d5643809 100644 --- a/core/ui/src/main/kotlin/app/aaps/core/ui/compose/navigation/ElementTypeStyle.kt +++ b/core/ui/src/main/kotlin/app/aaps/core/ui/compose/navigation/ElementTypeStyle.kt @@ -56,6 +56,7 @@ import app.aaps.core.ui.compose.icons.Pump fun ElementType.color(): Color = when (this) { ElementType.INSULIN, ElementType.TREATMENT, + ElementType.AFREZZA, ElementType.FILL -> AapsTheme.elementColors.insulin ElementType.CARBS -> AapsTheme.elementColors.carbs @@ -116,6 +117,7 @@ fun ElementType.color(): Color = when (this) { fun ElementType.icon(): ImageVector = when (this) { ElementType.INSULIN -> IcBolus + ElementType.AFREZZA -> IcBolus ElementType.CARBS -> IcCarbs ElementType.BOLUS_WIZARD -> IcCalculator ElementType.QUICK_WIZARD, @@ -171,6 +173,7 @@ fun ElementType.icon(): ImageVector = when (this) { fun ElementType.labelResId(): Int = when (this) { ElementType.INSULIN -> R.string.overview_insulin_label + ElementType.AFREZZA -> R.string.overview_afrezza_label ElementType.CARBS -> R.string.carbs ElementType.BOLUS_WIZARD -> R.string.boluswizard ElementType.QUICK_WIZARD -> 0 // dynamic label @@ -221,6 +224,7 @@ fun ElementType.labelResId(): Int = when (this) { fun ElementType.descriptionResId(): Int = when (this) { ElementType.INSULIN -> R.string.treatment_insulin_desc + ElementType.AFREZZA -> R.string.treatment_afrezza_desc ElementType.CARBS -> R.string.treatment_carbs_desc ElementType.BOLUS_WIZARD -> R.string.treatment_calculator_desc ElementType.TREATMENT -> R.string.treatment_desc From 7435ec7a460489f8d0a3963dfc41175cb051210f Mon Sep 17 00:00:00 2001 From: Afrezza Feature Date: Tue, 2 Jun 2026 09:40:32 +0000 Subject: [PATCH 07/11] feat: Wire Afrezza dialog into navigation, quick-launch, and routing - Add AppRoute.AfrezzaDialog route definition - Register AfrezzaDialogScreen composable in AppNavGraph - Add ElementType.AFREZZA -> AfrezzaDialog navigation handler - Add QuickLaunchAction.Afrezza static action and to staticActions list - Users can add Afrezza to their quick-launch bar via configuration --- app/src/main/kotlin/app/aaps/ComposeMainActivity.kt | 1 + .../kotlin/app/aaps/compose/navigation/AppNavGraph.kt | 10 ++++++++++ .../kotlin/app/aaps/compose/navigation/AppRoute.kt | 1 + .../aaps/ui/compose/quickLaunch/QuickLaunchAction.kt | 3 ++- 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/app/aaps/ComposeMainActivity.kt b/app/src/main/kotlin/app/aaps/ComposeMainActivity.kt index e53d6a8172dd..bba55a39826f 100644 --- a/app/src/main/kotlin/app/aaps/ComposeMainActivity.kt +++ b/app/src/main/kotlin/app/aaps/ComposeMainActivity.kt @@ -1055,6 +1055,7 @@ class ComposeMainActivity : AppCompatActivity() { // Treatment dialogs ElementType.CARBS -> navController.navigate(AppRoute.CarbsDialog.route) ElementType.INSULIN -> navController.navigate(AppRoute.InsulinDialog.route) + ElementType.AFREZZA -> navController.navigate(AppRoute.AfrezzaDialog.route) ElementType.TREATMENT -> navController.navigate(AppRoute.TreatmentDialog.route) ElementType.FILL -> navController.navigate(AppRoute.FillDialog.createRoute(FillPreselect.CARTRIDGE_CHANGE.ordinal)) ElementType.CANNULA_CHANGE -> navController.navigate(AppRoute.FillDialog.createRoute(FillPreselect.SITE_CHANGE.ordinal)) diff --git a/app/src/main/kotlin/app/aaps/compose/navigation/AppNavGraph.kt b/app/src/main/kotlin/app/aaps/compose/navigation/AppNavGraph.kt index e1d7e5db1b41..850a21af11bb 100644 --- a/app/src/main/kotlin/app/aaps/compose/navigation/AppNavGraph.kt +++ b/app/src/main/kotlin/app/aaps/compose/navigation/AppNavGraph.kt @@ -68,6 +68,7 @@ import app.aaps.ui.compose.configuration.ConfigurationViewModel import app.aaps.ui.compose.extendedBolusDialog.ExtendedBolusDialogScreen import app.aaps.ui.compose.fillDialog.FillDialogScreen import app.aaps.ui.compose.history.HistoryScreen +import app.aaps.ui.compose.afrezzaDialog.AfrezzaDialogScreen import app.aaps.ui.compose.insulinDialog.InsulinDialogScreen import app.aaps.ui.compose.insulinManagement.InsulinManagementScreen import app.aaps.ui.compose.insulinManagement.InsulinManagementViewModel @@ -308,6 +309,15 @@ fun NavGraphBuilder.appNavGraph( ) } + composable(route = AppRoute.AfrezzaDialog.route) { + AfrezzaDialogScreen( + onNavigateBack = { navController.safePopBackStack() }, + onShowMessage = { message -> + // Toast or Snackbar handled by caller + } + ) + } + composable(route = AppRoute.TreatmentDialog.route) { TreatmentDialogScreen( bgInfoState = graphViewModel.bgInfoState, diff --git a/app/src/main/kotlin/app/aaps/compose/navigation/AppRoute.kt b/app/src/main/kotlin/app/aaps/compose/navigation/AppRoute.kt index ccd213770aef..cd00d61c8398 100644 --- a/app/src/main/kotlin/app/aaps/compose/navigation/AppRoute.kt +++ b/app/src/main/kotlin/app/aaps/compose/navigation/AppRoute.kt @@ -73,6 +73,7 @@ sealed class AppRoute(val route: String) { data object CalibrationDialog : AppRoute("calibration_dialog") data object CarbsDialog : AppRoute("carbs_dialog") data object InsulinDialog : AppRoute("insulin_dialog") + data object AfrezzaDialog : AppRoute("afrezza_dialog") data object TreatmentDialog : AppRoute("treatment_dialog") data object TempBasalDialog : AppRoute("temp_basal_dialog") data object ExtendedBolusDialog : AppRoute("extended_bolus_dialog") diff --git a/ui/src/main/kotlin/app/aaps/ui/compose/quickLaunch/QuickLaunchAction.kt b/ui/src/main/kotlin/app/aaps/ui/compose/quickLaunch/QuickLaunchAction.kt index 6c9223bcb0f0..53917e8c62dc 100644 --- a/ui/src/main/kotlin/app/aaps/ui/compose/quickLaunch/QuickLaunchAction.kt +++ b/ui/src/main/kotlin/app/aaps/ui/compose/quickLaunch/QuickLaunchAction.kt @@ -81,6 +81,7 @@ sealed class QuickLaunchAction { val Carbs = StaticAction(ElementType.CARBS) val Wizard = StaticAction(ElementType.BOLUS_WIZARD) val Treatment = StaticAction(ElementType.TREATMENT) + val Afrezza = StaticAction(ElementType.AFREZZA) val Cgm = StaticAction(ElementType.CGM_XDRIP) val Calibration = StaticAction(ElementType.CALIBRATION) val InsulinManagement = StaticAction(ElementType.INSULIN_MANAGEMENT) @@ -99,7 +100,7 @@ sealed class QuickLaunchAction { /** All static actions available for the configuration screen (excluding QuickLaunchConfig) */ val staticActions: List = listOf( - Insulin, InsulinManagement, Carbs, Wizard, Treatment, Cgm, Calibration, + Insulin, InsulinManagement, Afrezza, Carbs, Wizard, Treatment, Cgm, Calibration, BgCheck, Note, Exercise, Question, Announcement, SensorInsert, BatteryChange, CannulaChange, Fill, SiteRotation ) From 80996ac92b65c480d3094f13d4c1c03aa191b77c Mon Sep 17 00:00:00 2001 From: Afrezza Feature Date: Tue, 2 Jun 2026 09:40:40 +0000 Subject: [PATCH 08/11] feat: Add Afrezza button to Treatment Bottom Sheet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add showAfrezza to TreatmentUiState - Inject InsulinManager into TreatmentViewModel to detect configured inhaled insulin — Afrezza button appears automatically when an inhaled insulin type exists in the user's insulin list - Thread showAfrezza through TreatmentBottomSheet -> TreatmentSelectionContent - Afrezza item appears between Insulin and Carbs in the treatment sheet - Include showAfrezza in Preview composable - No user preference toggle needed — presence is driven by insulin config --- .../app/aaps/ui/compose/main/MainScreen.kt | 17 +++++++++++++++++ .../treatmentsSheet/TreatmentBottomSheet.kt | 15 +++++++++++++++ .../compose/treatmentsSheet/TreatmentUiState.kt | 1 + .../treatmentsSheet/TreatmentViewModel.kt | 10 +++++++++- 4 files changed, 42 insertions(+), 1 deletion(-) diff --git a/ui/src/main/kotlin/app/aaps/ui/compose/main/MainScreen.kt b/ui/src/main/kotlin/app/aaps/ui/compose/main/MainScreen.kt index db08741365fb..6c5ad406784f 100644 --- a/ui/src/main/kotlin/app/aaps/ui/compose/main/MainScreen.kt +++ b/ui/src/main/kotlin/app/aaps/ui/compose/main/MainScreen.kt @@ -423,6 +423,22 @@ fun MainScreen( } } + // Treatment bottom sheet + if (showTreatmentSheet) { + val treatmentState by treatmentViewModel.uiState.collectAsStateWithLifecycle() + TreatmentBottomSheet( + onDismiss = { showTreatmentSheet = false }, + showCgm = treatmentState.showCgm, + showCalibration = treatmentState.showCalibration, + showTreatment = treatmentState.showTreatment, + showInsulin = treatmentState.showInsulin, + showCarbs = treatmentState.showCarbs, + showCalculator = treatmentState.showCalculator, + isDexcomSource = treatmentState.isDexcomSource, + showSettingsIcon = treatmentState.showSettingsIcon, + quickWizardItems = treatmentState.quickWizardItems, + onNavigate = onNavigate, + treatmentButtonsDef = treatmentButtonsDef, // Treatment bottom sheet if (showTreatmentSheet) { val treatmentState by treatmentViewModel.uiState.collectAsStateWithLifecycle() @@ -432,6 +448,7 @@ fun MainScreen( showCalibration = treatmentState.showCalibration, showTreatment = treatmentState.showTreatment, showInsulin = treatmentState.showInsulin, + showAfrezza = treatmentState.showAfrezza, showCarbs = treatmentState.showCarbs, showCalculator = treatmentState.showCalculator, isDexcomSource = treatmentState.isDexcomSource, diff --git a/ui/src/main/kotlin/app/aaps/ui/compose/treatmentsSheet/TreatmentBottomSheet.kt b/ui/src/main/kotlin/app/aaps/ui/compose/treatmentsSheet/TreatmentBottomSheet.kt index 72b711953aa6..a55352737c9a 100644 --- a/ui/src/main/kotlin/app/aaps/ui/compose/treatmentsSheet/TreatmentBottomSheet.kt +++ b/ui/src/main/kotlin/app/aaps/ui/compose/treatmentsSheet/TreatmentBottomSheet.kt @@ -56,6 +56,7 @@ fun TreatmentBottomSheet( showCalibration: Boolean, showTreatment: Boolean, showInsulin: Boolean, + showAfrezza: Boolean, showCarbs: Boolean, showCalculator: Boolean, isDexcomSource: Boolean, @@ -89,6 +90,7 @@ fun TreatmentBottomSheet( showCalibration = showCalibration, showTreatment = showTreatment, showInsulin = showInsulin, + showAfrezza = showAfrezza, showCarbs = showCarbs, showCalculator = showCalculator, isDexcomSource = isDexcomSource, @@ -108,6 +110,7 @@ private fun TreatmentSelectionContent( showCalibration: Boolean, showTreatment: Boolean, showInsulin: Boolean, + showAfrezza: Boolean, showCarbs: Boolean, showCalculator: Boolean, isDexcomSource: Boolean, @@ -249,6 +252,17 @@ private fun TreatmentSelectionContent( ) } + // Afrezza (inhaled insulin) + if (showAfrezza) { + TreatmentItem( + elementType = ElementType.AFREZZA, + enabled = true, + disabledAlpha = disabledAlpha, + onDismiss = onDismiss, + onClick = { onNavigate(NavigationRequest.Element(ElementType.AFREZZA)) } + ) + } + // Carbs if (showCarbs) { TreatmentItem( @@ -368,6 +382,7 @@ private fun TreatmentBottomSheetPreview() { showCalibration = true, showTreatment = true, showInsulin = true, + showAfrezza = true, showCarbs = true, showCalculator = true, isDexcomSource = false, diff --git a/ui/src/main/kotlin/app/aaps/ui/compose/treatmentsSheet/TreatmentUiState.kt b/ui/src/main/kotlin/app/aaps/ui/compose/treatmentsSheet/TreatmentUiState.kt index b516bfbfec70..77957b423ac7 100644 --- a/ui/src/main/kotlin/app/aaps/ui/compose/treatmentsSheet/TreatmentUiState.kt +++ b/ui/src/main/kotlin/app/aaps/ui/compose/treatmentsSheet/TreatmentUiState.kt @@ -13,6 +13,7 @@ data class TreatmentUiState( val showCalibration: Boolean = false, val showTreatment: Boolean = true, val showInsulin: Boolean = true, + val showAfrezza: Boolean = false, val showCarbs: Boolean = true, val showCalculator: Boolean = true, diff --git a/ui/src/main/kotlin/app/aaps/ui/compose/treatmentsSheet/TreatmentViewModel.kt b/ui/src/main/kotlin/app/aaps/ui/compose/treatmentsSheet/TreatmentViewModel.kt index 0dab4f3b09a9..557a9f3ec19f 100644 --- a/ui/src/main/kotlin/app/aaps/ui/compose/treatmentsSheet/TreatmentViewModel.kt +++ b/ui/src/main/kotlin/app/aaps/ui/compose/treatmentsSheet/TreatmentViewModel.kt @@ -8,6 +8,8 @@ import app.aaps.core.data.model.RM import app.aaps.core.interfaces.aps.Loop import app.aaps.core.interfaces.constraints.ConstraintsChecker import app.aaps.core.interfaces.iob.IobCobCalculator +import app.aaps.core.interfaces.insulin.InsulinManager +import app.aaps.core.interfaces.insulin.InsulinType import app.aaps.core.interfaces.logging.AAPSLogger import app.aaps.core.interfaces.plugin.ActivePlugin import app.aaps.core.interfaces.profile.Profile @@ -56,7 +58,8 @@ class TreatmentViewModel @Inject constructor( private val rxBus: RxBus, private val aapsLogger: AAPSLogger, private val dexcomBoyda: DexcomBoyda, - private val elementAvailability: ElementAvailability + private val elementAvailability: ElementAvailability, + private val insulinManager: InsulinManager ) : ViewModel() { private val _uiState = MutableStateFlow(TreatmentUiState()) @@ -96,6 +99,10 @@ class TreatmentViewModel @Inject constructor( val showInsulin = preferences.get(BooleanKey.OverviewShowInsulinButton) val showCarbs = preferences.get(BooleanKey.OverviewShowCarbsButton) val showCalculator = preferences.get(BooleanKey.OverviewShowWizardButton) + // Show Afrezza button if user has configured an inhaled insulin + val showAfrezza = insulinManager.insulins.any { + InsulinType.fromPeak(it.insulinPeakTime).isInhaled + } val showSettingsIcon = !preferences.simpleMode @@ -105,6 +112,7 @@ class TreatmentViewModel @Inject constructor( showCalibration = showCalibration, showTreatment = showTreatment, showInsulin = showInsulin, + showAfrezza = showAfrezza, showCarbs = showCarbs, showCalculator = showCalculator, isDexcomSource = isDexcomSource, From 6e390af48ea07c535bb387e2362b0e9e9c07d0aa Mon Sep 17 00:00:00 2001 From: Afrezza Feature Date: Tue, 2 Jun 2026 09:40:48 +0000 Subject: [PATCH 09/11] feat: Add Sources.AfrezzaDialog for treatment history tracking - Add AfrezzaDialog to Sources enum (core/data) - Add AfrezzaDialog to UserEntry.Sources DB enum (database/impl) - Add bidirectional mapping in SourcesExtension (database/persistence) - Add icon and color mappings in UserEntryPresentationHelperImpl - Afrezza doses are now distinguishable from pump boluses in treatment history and UserEntry logs --- .../kotlin/app/aaps/core/data/ue/Sources.kt | 1 + .../app/aaps/database/entities/UserEntry.kt | 1 + .../converters/SourcesExtension.kt | 2 + .../UserEntryPresentationHelperImpl.kt | 104 ++++++++++++++++++ 4 files changed, 108 insertions(+) diff --git a/core/data/src/main/kotlin/app/aaps/core/data/ue/Sources.kt b/core/data/src/main/kotlin/app/aaps/core/data/ue/Sources.kt index e0e79baf5805..63d824403e11 100644 --- a/core/data/src/main/kotlin/app/aaps/core/data/ue/Sources.kt +++ b/core/data/src/main/kotlin/app/aaps/core/data/ue/Sources.kt @@ -3,6 +3,7 @@ package app.aaps.core.data.ue enum class Sources { TreatmentDialog, InsulinDialog, + AfrezzaDialog, CarbDialog, WizardDialog, QuickWizard, diff --git a/database/impl/src/main/kotlin/app/aaps/database/entities/UserEntry.kt b/database/impl/src/main/kotlin/app/aaps/database/entities/UserEntry.kt index b94412e0a152..88826feaf9f7 100644 --- a/database/impl/src/main/kotlin/app/aaps/database/entities/UserEntry.kt +++ b/database/impl/src/main/kotlin/app/aaps/database/entities/UserEntry.kt @@ -134,6 +134,7 @@ data class UserEntry( enum class Sources { TreatmentDialog, InsulinDialog, + AfrezzaDialog, CarbDialog, WizardDialog, QuickWizard, diff --git a/database/persistence/src/main/kotlin/app/aaps/database/persistence/converters/SourcesExtension.kt b/database/persistence/src/main/kotlin/app/aaps/database/persistence/converters/SourcesExtension.kt index c84dedfd19e7..7e69143e9734 100644 --- a/database/persistence/src/main/kotlin/app/aaps/database/persistence/converters/SourcesExtension.kt +++ b/database/persistence/src/main/kotlin/app/aaps/database/persistence/converters/SourcesExtension.kt @@ -7,6 +7,7 @@ fun UserEntry.Sources.fromDb(): Sources = when (this) { UserEntry.Sources.TreatmentDialog -> Sources.TreatmentDialog UserEntry.Sources.InsulinDialog -> Sources.InsulinDialog + UserEntry.Sources.AfrezzaDialog -> Sources.AfrezzaDialog UserEntry.Sources.CarbDialog -> Sources.CarbDialog UserEntry.Sources.WizardDialog -> Sources.WizardDialog UserEntry.Sources.QuickWizard -> Sources.QuickWizard @@ -94,6 +95,7 @@ fun Sources.toDb(): UserEntry.Sources = when (this) { Sources.TreatmentDialog -> UserEntry.Sources.TreatmentDialog Sources.InsulinDialog -> UserEntry.Sources.InsulinDialog + Sources.AfrezzaDialog -> UserEntry.Sources.AfrezzaDialog Sources.CarbDialog -> UserEntry.Sources.CarbDialog Sources.WizardDialog -> UserEntry.Sources.WizardDialog Sources.QuickWizard -> UserEntry.Sources.QuickWizard diff --git a/implementation/src/main/kotlin/app/aaps/implementation/userEntry/UserEntryPresentationHelperImpl.kt b/implementation/src/main/kotlin/app/aaps/implementation/userEntry/UserEntryPresentationHelperImpl.kt index 1d8d79eb253f..7a10c5c7196f 100644 --- a/implementation/src/main/kotlin/app/aaps/implementation/userEntry/UserEntryPresentationHelperImpl.kt +++ b/implementation/src/main/kotlin/app/aaps/implementation/userEntry/UserEntryPresentationHelperImpl.kt @@ -102,6 +102,41 @@ class UserEntryPresentationHelperImpl @Inject constructor( Sources.Aidex -> IcXDrip Sources.Announcement -> IcAnnouncement Sources.Automation -> IcPluginAutomation + Sources.BatteryChange -> IcPumpBattery + Sources.BgCheck -> IcBgCheck + Sources.CalibrationDialog -> IcCalibration + Sources.CarbDialog -> IcCarbs + Sources.Exercise -> IcActivity + Sources.FillDialog -> IcCannulaChange + Sources.InsulinDialog -> IcBolus + Sources.AfrezzaDialog -> IcBolus + Sources.Intelligo -> IcPluginIntelligo + Sources.LocalProfile -> IcProfile + Sources.Loop -> IcLoopClosed + Sources.LoopDialog -> IcLoopClosed + Sources.MDI -> IcMdi + Sources.MM640g -> IcPluginMM640G + Sources.Maintenance -> IcPluginMaintenance + Sources.Medtronic -> IcPluginMedtronic + Sources.Medtrum -> IcPluginMedtrum + Sources.NSClient -> IcPluginNsClient + Sources.NSClientSource -> IcPluginNsClientBg + Sources.NSProfile -> IcPluginNsClient + Sources.Note -> IcNote + Sources.Question -> IcQuestion + Sources.QuickWizard -> IcQuickwizard + Sources.SensorInsert -> IcCgmInsert + Sources.SiteRotationDialog -> IcSiteRotation + Sources.Treatments -> IcClinicalNotes + Sources.WizardDialog -> IcCalculator + Sources.ExtendedBolusDialog -> IcExtendedBolus + Sources.TTDialog -> IcTtHigh + Sources.ProfileSwitchDialog -> IcProfile + Sources.LoopDialog -> IcLoopClosed + Sources.TempBasalDialog -> IcTbrHigh + Sources.ConcentrationDialog -> IcPluginInsulin + Sources.SettingsExport -> IcAutomation + Sources.Automation -> IcAutomation Sources.Autotune -> IcPluginAutotune Sources.BG -> IcGenericCgm Sources.BatteryChange -> IcPumpBattery @@ -182,6 +217,74 @@ class UserEntryPresentationHelperImpl @Inject constructor( Sources.Xdrip -> IcXDrip } + @Deprecated("use fun icon(source: Sources): ImageVector?") + override fun iconId(source: Sources): Int = when (source) { + //Sources.TreatmentDialog -> R.drawable.icon_insulin_carbs + //Sources.ExtendedBolusDialog -> R.drawable.ic_actions_start_extended_bolus + //Sources.TTDialog -> R.drawable.ic_temptarget_high + //Sources.ProfileSwitchDialog -> app.aaps.core.ui.R.drawable.ic_actions_profileswitch + //Sources.LoopDialog -> R.drawable.ic_loop_closed + //Sources.TempBasalDialog -> R.drawable.ic_actions_start_temp_basal + //Sources.ConcentrationDialog -> R.drawable.ic_insulin + //Sources.SettingsExport -> R.drawable.ic_automation + Sources.Actions -> R.drawable.ic_action + //Sources.Automation -> R.drawable.ic_automation + //Sources.Autotune -> R.drawable.ic_autotune + //Sources.BG -> R.drawable.ic_generic_cgm + //Sources.Aidex -> R.drawable.ic_blooddrop_48 + //Sources.Dexcom -> R.drawable.ic_dexcom_g6 + //Sources.Eversense -> R.drawable.ic_eversense + //Sources.Glimp -> R.drawable.ic_glimp + //Sources.MM640g -> R.drawable.ic_generic_cgm + //Sources.NSClientSource -> R.drawable.ic_nsclient_bg + //Sources.PocTech -> R.drawable.ic_poctech + //Sources.Tomato -> R.drawable.ic_sensor + //Sources.Glunovo -> R.drawable.ic_glunovo + //Sources.Intelligo -> app.aaps.core.ui.R.drawable.ic_intelligo + //Sources.Xdrip -> R.drawable.ic_blooddrop_48 + //Sources.Ottai -> R.drawable.ic_syai_tag + //Sources.SyaiTag -> R.drawable.ic_syai_tag + //Sources.SiBionic -> R.drawable.ic_generic_cgm + //Sources.Sino -> R.drawable.ic_generic_cgm + //Sources.LocalProfile -> R.drawable.ic_local_profile + //Sources.Loop -> R.drawable.ic_loop_closed_white + //Sources.Maintenance -> app.aaps.core.ui.R.drawable.ic_maintenance + //Sources.NSClient -> app.aaps.core.ui.R.drawable.ic_nightscout_syncs + //Sources.NSProfile -> R.drawable.ic_nightscout_profile + //Sources.Objectives -> app.aaps.core.ui.R.drawable.ic_graduation + Sources.Pump -> app.aaps.core.ui.R.drawable.ic_generic_icon + //Sources.Dana -> app.aaps.core.ui.R.drawable.ic_danars_128 + //Sources.DanaR -> app.aaps.core.ui.R.drawable.ic_danars_128 + //Sources.DanaRC -> app.aaps.core.ui.R.drawable.ic_danars_128 + //Sources.DanaRv2 -> app.aaps.core.ui.R.drawable.ic_danars_128 + //Sources.DanaRS -> app.aaps.core.ui.R.drawable.ic_danars_128 + //Sources.DanaI -> app.aaps.core.ui.R.drawable.ic_danai_128 + //Sources.DiaconnG8 -> app.aaps.core.ui.R.drawable.ic_diaconn_g8 + //Sources.Insight -> app.aaps.core.ui.R.drawable.ic_insight_128 + //Sources.Combo -> app.aaps.core.ui.R.drawable.ic_combo_128 + //Sources.Medtronic -> app.aaps.core.ui.R.drawable.ic_veo_128 + //Sources.Omnipod -> R.drawable.ic_patch_pump_outline + //Sources.OmnipodEros -> R.drawable.ic_patch_pump_outline + //Sources.OmnipodDash -> R.drawable.ic_patch_pump_outline + Sources.EOPatch2 -> app.aaps.core.ui.R.drawable.ic_eopatch2_128 + Sources.Equil -> app.aaps.core.ui.R.drawable.ic_equil_128 + //Sources.Medtrum -> app.aaps.core.ui.R.drawable.ic_medtrum_128 + Sources.MDI -> R.drawable.ic_ict + //Sources.VirtualPump -> R.drawable.ic_virtual_pump + //Sources.SMS -> R.drawable.ic_sms + //Sources.Wear -> R.drawable.ic_watch + //Sources.Food -> R.drawable.ic_food + //Sources.Stats -> R.drawable.ic_cp_stats + //Sources.ConfigBuilder -> app.aaps.core.ui.R.drawable.ic_cogs + Sources.Overview -> app.aaps.core.ui.R.drawable.ic_home + Sources.Aaps -> R.drawable.ic_aaps + //Sources.Garmin -> app.aaps.core.ui.R.drawable.ic_generic_icon + Sources.Database -> app.aaps.core.ui.R.drawable.ic_database_cleanup + //Sources.Unknown -> app.aaps.core.ui.R.drawable.ic_generic_icon + //Sources.Random -> R.drawable.ic_aaps + Sources.BgFragment -> R.drawable.ic_aaps + //Sources.Insulin -> R.drawable.ic_insulin + else -> error("Missing resource") @Composable override fun iconColor(source: Sources): Color = when (source) { Sources.Aaps -> ElementType.AAPS.color() @@ -222,6 +325,7 @@ class UserEntryPresentationHelperImpl @Inject constructor( Sources.Instara -> ElementType.CGM_DEX.color() Sources.Insulin -> ElementType.INSULIN_MANAGEMENT.color() Sources.InsulinDialog -> ElementType.INSULIN.color() + Sources.AfrezzaDialog -> ElementType.AFREZZA.color() Sources.Intelligo -> ElementType.CGM_DEX.color() Sources.LocalProfile -> ElementType.PROFILE_MANAGEMENT.color() Sources.Loop -> ElementType.LOOP.color() From 3bd06ef9d849dfd6cc29c2e2f9d338f31d0bce0d Mon Sep 17 00:00:00 2001 From: Afrezza Feature Date: Tue, 2 Jun 2026 15:51:01 +0000 Subject: [PATCH 10/11] feat: Add Afrezza 4U/8U/12U logging to Wear OS Actions tile - Add AfrezzaActivity with cartridge dose selection (4U, 8U, 12U) and confirmation screen for Wear OS - Add ActionAfrezzaPreCheck and ActionAfrezzaConfirmed events to EventData sealed class for watch-phone communication - Register Afrezza as selectable action in ActionSource tile config - Add phone-side handler in DataHandlerMobile that logs Afrezza bolus directly via PersistenceLayer (bypasses pump queue for inhaled insulin) - Inject InsulinManager to look up Afrezza ICfg by peak time - Add ic_afrezza.xml inhaler icon drawable for wear tile - Add wear string resources and AndroidManifest activity registration - Add afrezza_not_configured error string to core/ui --- .../core/interfaces/rx/weardata/EventData.kt | 6 + core/ui/src/main/res/values/strings.xml | 2 + .../wear/wearintegration/DataHandlerMobile.kt | 85 ++++++++ wear/src/main/AndroidManifest.xml | 5 + .../app/aaps/wear/di/WearActivitiesModule.kt | 2 + .../interaction/actions/AfrezzaActivity.kt | 197 ++++++++++++++++++ .../app/aaps/wear/tile/source/ActionSource.kt | 8 + wear/src/main/res/drawable/ic_afrezza.xml | 27 +++ wear/src/main/res/values/strings.xml | 5 + 9 files changed, 337 insertions(+) create mode 100644 wear/src/main/kotlin/app/aaps/wear/interaction/actions/AfrezzaActivity.kt create mode 100644 wear/src/main/res/drawable/ic_afrezza.xml diff --git a/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/rx/weardata/EventData.kt b/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/rx/weardata/EventData.kt index 6f637866eab2..36a283f5f9a7 100644 --- a/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/rx/weardata/EventData.kt +++ b/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/rx/weardata/EventData.kt @@ -96,6 +96,9 @@ sealed class EventData : Event() { @Serializable data class ActionBolusPreCheck(val insulin: Double, val carbs: Int) : EventData() + @Serializable + data class ActionAfrezzaPreCheck(val units: Int) : EventData() + @Serializable data class ActionFillPreCheck(val insulin: Double) : EventData() @@ -215,6 +218,9 @@ sealed class EventData : Event() { @Serializable data class ActionBolusConfirmed(val insulin: Double, val carbs: Int) : EventData() + @Serializable + data class ActionAfrezzaConfirmed(val units: Int) : EventData() + @Serializable data class ActionECarbsConfirmed(val carbs: Int, val carbsTime: Long, val duration: Int) : EventData() diff --git a/core/ui/src/main/res/values/strings.xml b/core/ui/src/main/res/values/strings.xml index 1977bf3c73f6..0d92067d2e19 100644 --- a/core/ui/src/main/res/values/strings.xml +++ b/core/ui/src/main/res/values/strings.xml @@ -745,6 +745,8 @@ TempT: %1$s %1$s to %2$s No pump available! + QuickWizard + Add Afrezza insulin in Insulin Management first QuickWizard diff --git a/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/wear/wearintegration/DataHandlerMobile.kt b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/wear/wearintegration/DataHandlerMobile.kt index 9d25697eb4a5..56d850b8609d 100644 --- a/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/wear/wearintegration/DataHandlerMobile.kt +++ b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/wear/wearintegration/DataHandlerMobile.kt @@ -11,6 +11,7 @@ import app.aaps.core.data.model.BS import app.aaps.core.data.model.GV import app.aaps.core.data.model.GlucoseUnit import app.aaps.core.data.model.HR +import app.aaps.core.data.model.IDs import app.aaps.core.data.model.RM import app.aaps.core.data.model.SC import app.aaps.core.data.model.Scene @@ -34,6 +35,8 @@ import app.aaps.core.interfaces.db.PersistenceLayer import app.aaps.core.interfaces.db.ProcessedTbrEbData import app.aaps.core.interfaces.insulin.ConcentrationHelper import app.aaps.core.interfaces.insulin.Insulin +import app.aaps.core.interfaces.insulin.InsulinManager +import app.aaps.core.interfaces.insulin.InsulinType import app.aaps.core.interfaces.iob.GlucoseStatusProvider import app.aaps.core.interfaces.iob.IobCobCalculator import app.aaps.core.interfaces.logging.AAPSLogger @@ -142,6 +145,7 @@ class DataHandlerMobile @Inject constructor( private val uel: UserEntryLogger, private val activePlugin: ActivePlugin, private val insulin: Insulin, + private val insulinManager: InsulinManager, private val commandQueue: CommandQueue, private val fabricPrivacy: FabricPrivacy, private val uiInteraction: UiInteraction, @@ -388,6 +392,30 @@ class DataHandlerMobile @Inject constructor( .onErrorComplete() } .subscribe() + disposable += rxBus + .toObservable(EventData.ActionAfrezzaPreCheck::class.java) + .observeOn(aapsSchedulers.io) + .concatMapCompletable { + rxCompletable { + aapsLogger.debug(LTag.WEAR, "ActionAfrezzaPreCheck received $it from ${it.sourceNodeId}") + handleAfrezzaPreCheck(it) + } + .doOnError(fabricPrivacy::logException) + .onErrorComplete() + } + .subscribe() + disposable += rxBus + .toObservable(EventData.ActionAfrezzaConfirmed::class.java) + .observeOn(aapsSchedulers.io) + .concatMapCompletable { + rxCompletable { + aapsLogger.debug(LTag.WEAR, "ActionAfrezzaConfirmed received $it from ${it.sourceNodeId}") + doAfrezzaBolus(it.units) + } + .doOnError(fabricPrivacy::logException) + .onErrorComplete() + } + .subscribe() disposable += rxBus .toObservable(EventData.ActionECarbsPreCheck::class.java) .observeOn(aapsSchedulers.io) @@ -1144,6 +1172,63 @@ class DataHandlerMobile @Inject constructor( ) } + private suspend fun handleAfrezzaPreCheck(command: EventData.ActionAfrezzaPreCheck) { + val units = command.units + if (units !in listOf(4, 8, 12)) { + sendError("Invalid Afrezza cartridge: ${units}U") + return + } + // Find the Afrezza ICfg from InsulinManager + val afrezzaPeak = InsulinType.OREF_INHALED_AFREZZA.insulinPeakTime + val afrezzaIcfg = insulinManager.insulins.firstOrNull { it.insulinPeakTime == afrezzaPeak } + ?: insulinManager.insulins.firstOrNull { InsulinType.fromPeak(it.insulinPeakTime).isInhaled } + if (afrezzaIcfg == null) { + sendError(rh.gs(app.aaps.core.ui.R.string.afrezza_not_configured)) + return + } + val message = "Afrezza: ${units}U" + rxBus.send( + EventMobileToWear( + EventData.ConfirmAction( + rh.gs(app.aaps.core.ui.R.string.confirm).uppercase(), message, + returnCommand = EventData.ActionAfrezzaConfirmed(units), + insulin = units.toDouble(), + ) + ) + ) + } + + private suspend fun doAfrezzaBolus(units: Int) { + val afrezzaPeak = InsulinType.OREF_INHALED_AFREZZA.insulinPeakTime + val afrezzaIcfg = insulinManager.insulins.firstOrNull { it.insulinPeakTime == afrezzaPeak } + ?: insulinManager.insulins.firstOrNull { InsulinType.fromPeak(it.insulinPeakTime).isInhaled } + if (afrezzaIcfg == null) { + sendError(rh.gs(app.aaps.core.ui.R.string.afrezza_not_configured)) + return + } + val now = dateUtil.now() + val bolus = BS( + timestamp = now, + amount = units.toDouble(), + type = BS.Type.NORMAL, + notes = "Afrezza inhaled", + iCfg = afrezzaIcfg, + ids = IDs(pumpId = now) + ) + persistenceLayer.insertOrUpdateBolus( + bolus = bolus, + action = Action.BOLUS, + source = Sources.Wear, + note = "Afrezza inhaled" + ) + uel.log( + action = Action.BOLUS, source = Sources.Wear, + "Afrezza inhaled", + ValueWithUnit.Insulin(units.toDouble()) + ) + aapsLogger.info(LTag.WEAR, "Afrezza ${units}U logged via Wear with ICfg: ${afrezzaIcfg.insulinLabel}") + } + private fun handleECarbsPreCheck(command: EventData.ActionECarbsPreCheck) { val startTimeStamp = System.currentTimeMillis() + T.mins(command.carbsTimeShift.toLong()).msecs() val cob = iobCobCalculator.ads.getLastAutosensData("carbsDialog", aapsLogger, dateUtil)?.cob ?: 0.0 diff --git a/wear/src/main/AndroidManifest.xml b/wear/src/main/AndroidManifest.xml index adf570ecb474..1043c6fee0b6 100644 --- a/wear/src/main/AndroidManifest.xml +++ b/wear/src/main/AndroidManifest.xml @@ -715,6 +715,11 @@ android:exported="true" android:label="@string/action_insulin" android:theme="@style/WearAppCompatDark" /> + + when (page) { + 0 -> AfrezzaDoseSelectScreen( + onDoseSelected = { selectedUnits = it }, + selectedUnits = selectedUnits, + ) + else -> AfrezzaConfirmScreen( + units = selectedUnits, + onConfirm = { confirmAfrezza(selectedUnits) }, + ) + } + } + HorizontalPageIndicator( + pagerState = pagerState, + modifier = Modifier.align(Alignment.BottomCenter).padding(bottom = 4.dp) + ) + } + } + } + } + + private fun confirmAfrezza(units: Int) { + rxBus.send(EventWearToMobile(ActionAfrezzaPreCheck(units))) + startActivity( + Intent(this, ConfirmationActivity::class.java).apply { + putExtra(ConfirmationActivity.EXTRA_ANIMATION_TYPE, ConfirmationActivity.SUCCESS_ANIMATION) + putExtra(ConfirmationActivity.EXTRA_MESSAGE, getString(R.string.action_afrezza_confirmation)) + } + ) + finish() + } +} + +@Composable +private fun AfrezzaDoseSelectScreen( + onDoseSelected: (Int) -> Unit, + selectedUnits: Int, +) { + val haptic = LocalHapticFeedback.current + + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 12.dp, vertical = 8.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Text( + text = stringResource(R.string.action_afrezza_select_dose), + color = Color.White, + fontSize = 14.sp, + fontWeight = FontWeight.Bold, + ) + Spacer(Modifier.height(10.dp)) + Row( + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically, + ) { + for (dose in listOf(4, 8, 12)) { + val isSelected = selectedUnits == dose + Box( + modifier = Modifier + .size(52.dp) + .clip(CircleShape) + .background(if (isSelected) AfrezzaTeal else AfrezzaTealDark) + .clickable { + haptic.performHapticFeedback(HapticFeedbackType.LongPress) + onDoseSelected(dose) + }, + contentAlignment = Alignment.Center + ) { + Text( + text = "${dose}${stringResource(R.string.insulin_unit_short)}", + color = Color.White, + fontSize = 16.sp, + fontWeight = FontWeight.Bold, + ) + } + if (dose != 12) Spacer(Modifier.width(8.dp)) + } + } + } +} + +@Composable +private fun AfrezzaConfirmScreen(units: Int, onConfirm: () -> Unit) { + val haptic = LocalHapticFeedback.current + var confirmationSent by remember { mutableStateOf(false) } + + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 24.dp, vertical = 8.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Text( + text = stringResource(R.string.request), + color = Color.White, + fontSize = 18.sp, + fontWeight = FontWeight.Bold, + ) + Spacer(Modifier.height(8.dp)) + Box( + modifier = Modifier + .size(90.dp) + .clip(CircleShape) + .clickable(enabled = units > 0 && !confirmationSent) { + confirmationSent = true + haptic.performHapticFeedback(HapticFeedbackType.LongPress) + onConfirm() + }, + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource(R.drawable.ic_confirm), + contentDescription = stringResource(R.string.confirm), + tint = ConfirmGreen, + modifier = Modifier.fillMaxSize() + ) + } + Spacer(Modifier.height(4.dp)) + Text( + text = "$units ${stringResource(R.string.insulin_unit_short)} ${stringResource(R.string.action_afrezza)}", + color = AfrezzaTeal, + fontSize = 24.sp, + fontWeight = FontWeight.Bold, + ) + } +} diff --git a/wear/src/main/kotlin/app/aaps/wear/tile/source/ActionSource.kt b/wear/src/main/kotlin/app/aaps/wear/tile/source/ActionSource.kt index d155e3fe242f..9c32081c5df9 100644 --- a/wear/src/main/kotlin/app/aaps/wear/tile/source/ActionSource.kt +++ b/wear/src/main/kotlin/app/aaps/wear/tile/source/ActionSource.kt @@ -10,6 +10,7 @@ import app.aaps.wear.interaction.actions.BackgroundActionActivity import app.aaps.wear.interaction.actions.BolusActivity import app.aaps.wear.interaction.actions.CarbActivity import app.aaps.wear.interaction.actions.ECarbActivity +import app.aaps.wear.interaction.actions.AfrezzaActivity import app.aaps.wear.interaction.actions.TempTargetActivity import app.aaps.wear.interaction.actions.TreatmentActivity import app.aaps.wear.interaction.actions.WizardActivity @@ -43,6 +44,13 @@ class ActionSource @Inject constructor(context: Context, sp: SP, aapsLogger: AAP iconRes = R.drawable.ic_bolus, activityClass = BolusActivity::class.java.name, ), + StaticAction( + settingName = "afrezza", + buttonText = resources.getString(R.string.action_afrezza_short), + settingLabel = resources.getString(R.string.action_afrezza), + iconRes = R.drawable.ic_afrezza, + activityClass = AfrezzaActivity::class.java.name, + ), StaticAction( settingName = "carbs", buttonText = resources.getString(R.string.action_carbs), diff --git a/wear/src/main/res/drawable/ic_afrezza.xml b/wear/src/main/res/drawable/ic_afrezza.xml new file mode 100644 index 000000000000..a5eb01b9f169 --- /dev/null +++ b/wear/src/main/res/drawable/ic_afrezza.xml @@ -0,0 +1,27 @@ + + + + + + + + + + diff --git a/wear/src/main/res/values/strings.xml b/wear/src/main/res/values/strings.xml index e35902de26f6..7d06555b26c3 100644 --- a/wear/src/main/res/values/strings.xml +++ b/wear/src/main/res/values/strings.xml @@ -89,6 +89,11 @@ STOP Treatment Requested Bolus Requested + Afrezza + Afrz + Afrezza Requested + Select Cartridge + Log %1$dU? Calculation Requested Fill Requested Carbs Requested From 0bb94b85f5135d96e8d8f0d05c5146dd52e0bc7d Mon Sep 17 00:00:00 2001 From: Craig Gordon Date: Tue, 9 Jun 2026 16:46:16 -0400 Subject: [PATCH 11/11] fix: Resolve merge conflicts, remove Wear OS (separate PR), minimize UserEntry diff - Fix merge conflict markers and duplicate MAX_DIA in HardLimits.kt - Fix unused lambda parameter (message -> _) in AppNavGraph.kt - Reset UserEntryPresentationHelperImpl.kt to dev, re-add only 2 Afrezza lines - Remove Wear OS changes (AfrezzaActivity, EventData, DataHandlerMobile, etc.) - Merge latest dev to resolve all conflicts --- .gitignore | 1 + .../aaps/compose/navigation/AppNavGraph.kt | 2 +- .../core/interfaces/rx/weardata/EventData.kt | 6 - .../aaps/core/interfaces/utils/HardLimits.kt | 5 +- .../UserEntryPresentationHelperImpl.kt | 106 +--------- .../wear/wearintegration/DataHandlerMobile.kt | 85 -------- .../app/aaps/ui/compose/main/MainScreen.kt | 16 -- wear/src/main/AndroidManifest.xml | 5 - .../app/aaps/wear/di/WearActivitiesModule.kt | 2 - .../interaction/actions/AfrezzaActivity.kt | 197 ------------------ .../app/aaps/wear/tile/source/ActionSource.kt | 8 - wear/src/main/res/drawable/ic_afrezza.xml | 27 --- wear/src/main/res/values/strings.xml | 5 - 13 files changed, 5 insertions(+), 460 deletions(-) delete mode 100644 wear/src/main/kotlin/app/aaps/wear/interaction/actions/AfrezzaActivity.kt delete mode 100644 wear/src/main/res/drawable/ic_afrezza.xml diff --git a/.gitignore b/.gitignore index 3f58bd13e198..21494c242ef9 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,4 @@ wear/pumpcontrol/* .claude/settings.local.json .claude/CLAUDE_COMMANDS.md tmpclaude-* +build_output*.txt diff --git a/app/src/main/kotlin/app/aaps/compose/navigation/AppNavGraph.kt b/app/src/main/kotlin/app/aaps/compose/navigation/AppNavGraph.kt index 30fe0c80c9c3..fe2e245a1249 100644 --- a/app/src/main/kotlin/app/aaps/compose/navigation/AppNavGraph.kt +++ b/app/src/main/kotlin/app/aaps/compose/navigation/AppNavGraph.kt @@ -313,7 +313,7 @@ fun NavGraphBuilder.appNavGraph( composable(route = AppRoute.AfrezzaDialog.route) { AfrezzaDialogScreen( onNavigateBack = { navController.safePopBackStack() }, - onShowMessage = { message -> + onShowMessage = { _ -> // Toast or Snackbar handled by caller } ) diff --git a/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/rx/weardata/EventData.kt b/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/rx/weardata/EventData.kt index 36a283f5f9a7..6f637866eab2 100644 --- a/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/rx/weardata/EventData.kt +++ b/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/rx/weardata/EventData.kt @@ -96,9 +96,6 @@ sealed class EventData : Event() { @Serializable data class ActionBolusPreCheck(val insulin: Double, val carbs: Int) : EventData() - @Serializable - data class ActionAfrezzaPreCheck(val units: Int) : EventData() - @Serializable data class ActionFillPreCheck(val insulin: Double) : EventData() @@ -218,9 +215,6 @@ sealed class EventData : Event() { @Serializable data class ActionBolusConfirmed(val insulin: Double, val carbs: Int) : EventData() - @Serializable - data class ActionAfrezzaConfirmed(val units: Int) : EventData() - @Serializable data class ActionECarbsConfirmed(val carbs: Int, val carbsTime: Long, val duration: Int) : EventData() diff --git a/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/utils/HardLimits.kt b/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/utils/HardLimits.kt index a746ae69f7c2..51f84ac3aca7 100644 --- a/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/utils/HardLimits.kt +++ b/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/utils/HardLimits.kt @@ -17,11 +17,8 @@ interface HardLimits { val LIMIT_TEMP_TARGET_BG = doubleArrayOf(72.0, 200.0) val MIN_DIA = doubleArrayOf(5.0, 5.0, 5.0, 5.0, 5.0) val MAX_DIA = doubleArrayOf(9.0, 9.0, 9.0, 9.0, 10.0) - val MAX_DIA = doubleArrayOf(10.0, 10.0, 10.0, 10.0, 10.0) - val MAX_DIA = doubleArrayOf(9.0, 9.0, 9.0, 9.0, 10.0) val MIN_DIA_INHALED = doubleArrayOf(1.5, 1.5, 1.5, 1.5, 1.5) // Inhaled insulin (e.g. Afrezza) has shorter DIA val MAX_DIA_INHALED = doubleArrayOf(4.0, 4.0, 4.0, 4.0, 4.0) ->>>>>>> feat: Add InsulinType.OREF_INHALED_AFREZZA and inhaled DIA limits const val MIN_PEAK = 35 // mgdl const val MAX_PEAK = 120 // mgdl val MIN_IC = doubleArrayOf(2.0, 2.0, 2.0, 2.0, 0.3) @@ -64,4 +61,4 @@ interface HardLimits { fun ageEntryValues(): Array enum class AgeType { CHILD, TEENAGE, ADULT, RESISTANT_ADULT, PREGNANT } -} \ No newline at end of file +} diff --git a/implementation/src/main/kotlin/app/aaps/implementation/userEntry/UserEntryPresentationHelperImpl.kt b/implementation/src/main/kotlin/app/aaps/implementation/userEntry/UserEntryPresentationHelperImpl.kt index 7a10c5c7196f..535c15a97e50 100644 --- a/implementation/src/main/kotlin/app/aaps/implementation/userEntry/UserEntryPresentationHelperImpl.kt +++ b/implementation/src/main/kotlin/app/aaps/implementation/userEntry/UserEntryPresentationHelperImpl.kt @@ -102,41 +102,6 @@ class UserEntryPresentationHelperImpl @Inject constructor( Sources.Aidex -> IcXDrip Sources.Announcement -> IcAnnouncement Sources.Automation -> IcPluginAutomation - Sources.BatteryChange -> IcPumpBattery - Sources.BgCheck -> IcBgCheck - Sources.CalibrationDialog -> IcCalibration - Sources.CarbDialog -> IcCarbs - Sources.Exercise -> IcActivity - Sources.FillDialog -> IcCannulaChange - Sources.InsulinDialog -> IcBolus - Sources.AfrezzaDialog -> IcBolus - Sources.Intelligo -> IcPluginIntelligo - Sources.LocalProfile -> IcProfile - Sources.Loop -> IcLoopClosed - Sources.LoopDialog -> IcLoopClosed - Sources.MDI -> IcMdi - Sources.MM640g -> IcPluginMM640G - Sources.Maintenance -> IcPluginMaintenance - Sources.Medtronic -> IcPluginMedtronic - Sources.Medtrum -> IcPluginMedtrum - Sources.NSClient -> IcPluginNsClient - Sources.NSClientSource -> IcPluginNsClientBg - Sources.NSProfile -> IcPluginNsClient - Sources.Note -> IcNote - Sources.Question -> IcQuestion - Sources.QuickWizard -> IcQuickwizard - Sources.SensorInsert -> IcCgmInsert - Sources.SiteRotationDialog -> IcSiteRotation - Sources.Treatments -> IcClinicalNotes - Sources.WizardDialog -> IcCalculator - Sources.ExtendedBolusDialog -> IcExtendedBolus - Sources.TTDialog -> IcTtHigh - Sources.ProfileSwitchDialog -> IcProfile - Sources.LoopDialog -> IcLoopClosed - Sources.TempBasalDialog -> IcTbrHigh - Sources.ConcentrationDialog -> IcPluginInsulin - Sources.SettingsExport -> IcAutomation - Sources.Automation -> IcAutomation Sources.Autotune -> IcPluginAutotune Sources.BG -> IcGenericCgm Sources.BatteryChange -> IcPumpBattery @@ -170,6 +135,7 @@ class UserEntryPresentationHelperImpl @Inject constructor( Sources.Instara -> IcGenericCgm Sources.Insulin -> IcPluginInsulin Sources.InsulinDialog -> IcBolus + Sources.AfrezzaDialog -> IcBolus Sources.Intelligo -> IcPluginIntelligo Sources.LocalProfile -> IcProfile Sources.Loop -> IcLoopClosed @@ -217,74 +183,6 @@ class UserEntryPresentationHelperImpl @Inject constructor( Sources.Xdrip -> IcXDrip } - @Deprecated("use fun icon(source: Sources): ImageVector?") - override fun iconId(source: Sources): Int = when (source) { - //Sources.TreatmentDialog -> R.drawable.icon_insulin_carbs - //Sources.ExtendedBolusDialog -> R.drawable.ic_actions_start_extended_bolus - //Sources.TTDialog -> R.drawable.ic_temptarget_high - //Sources.ProfileSwitchDialog -> app.aaps.core.ui.R.drawable.ic_actions_profileswitch - //Sources.LoopDialog -> R.drawable.ic_loop_closed - //Sources.TempBasalDialog -> R.drawable.ic_actions_start_temp_basal - //Sources.ConcentrationDialog -> R.drawable.ic_insulin - //Sources.SettingsExport -> R.drawable.ic_automation - Sources.Actions -> R.drawable.ic_action - //Sources.Automation -> R.drawable.ic_automation - //Sources.Autotune -> R.drawable.ic_autotune - //Sources.BG -> R.drawable.ic_generic_cgm - //Sources.Aidex -> R.drawable.ic_blooddrop_48 - //Sources.Dexcom -> R.drawable.ic_dexcom_g6 - //Sources.Eversense -> R.drawable.ic_eversense - //Sources.Glimp -> R.drawable.ic_glimp - //Sources.MM640g -> R.drawable.ic_generic_cgm - //Sources.NSClientSource -> R.drawable.ic_nsclient_bg - //Sources.PocTech -> R.drawable.ic_poctech - //Sources.Tomato -> R.drawable.ic_sensor - //Sources.Glunovo -> R.drawable.ic_glunovo - //Sources.Intelligo -> app.aaps.core.ui.R.drawable.ic_intelligo - //Sources.Xdrip -> R.drawable.ic_blooddrop_48 - //Sources.Ottai -> R.drawable.ic_syai_tag - //Sources.SyaiTag -> R.drawable.ic_syai_tag - //Sources.SiBionic -> R.drawable.ic_generic_cgm - //Sources.Sino -> R.drawable.ic_generic_cgm - //Sources.LocalProfile -> R.drawable.ic_local_profile - //Sources.Loop -> R.drawable.ic_loop_closed_white - //Sources.Maintenance -> app.aaps.core.ui.R.drawable.ic_maintenance - //Sources.NSClient -> app.aaps.core.ui.R.drawable.ic_nightscout_syncs - //Sources.NSProfile -> R.drawable.ic_nightscout_profile - //Sources.Objectives -> app.aaps.core.ui.R.drawable.ic_graduation - Sources.Pump -> app.aaps.core.ui.R.drawable.ic_generic_icon - //Sources.Dana -> app.aaps.core.ui.R.drawable.ic_danars_128 - //Sources.DanaR -> app.aaps.core.ui.R.drawable.ic_danars_128 - //Sources.DanaRC -> app.aaps.core.ui.R.drawable.ic_danars_128 - //Sources.DanaRv2 -> app.aaps.core.ui.R.drawable.ic_danars_128 - //Sources.DanaRS -> app.aaps.core.ui.R.drawable.ic_danars_128 - //Sources.DanaI -> app.aaps.core.ui.R.drawable.ic_danai_128 - //Sources.DiaconnG8 -> app.aaps.core.ui.R.drawable.ic_diaconn_g8 - //Sources.Insight -> app.aaps.core.ui.R.drawable.ic_insight_128 - //Sources.Combo -> app.aaps.core.ui.R.drawable.ic_combo_128 - //Sources.Medtronic -> app.aaps.core.ui.R.drawable.ic_veo_128 - //Sources.Omnipod -> R.drawable.ic_patch_pump_outline - //Sources.OmnipodEros -> R.drawable.ic_patch_pump_outline - //Sources.OmnipodDash -> R.drawable.ic_patch_pump_outline - Sources.EOPatch2 -> app.aaps.core.ui.R.drawable.ic_eopatch2_128 - Sources.Equil -> app.aaps.core.ui.R.drawable.ic_equil_128 - //Sources.Medtrum -> app.aaps.core.ui.R.drawable.ic_medtrum_128 - Sources.MDI -> R.drawable.ic_ict - //Sources.VirtualPump -> R.drawable.ic_virtual_pump - //Sources.SMS -> R.drawable.ic_sms - //Sources.Wear -> R.drawable.ic_watch - //Sources.Food -> R.drawable.ic_food - //Sources.Stats -> R.drawable.ic_cp_stats - //Sources.ConfigBuilder -> app.aaps.core.ui.R.drawable.ic_cogs - Sources.Overview -> app.aaps.core.ui.R.drawable.ic_home - Sources.Aaps -> R.drawable.ic_aaps - //Sources.Garmin -> app.aaps.core.ui.R.drawable.ic_generic_icon - Sources.Database -> app.aaps.core.ui.R.drawable.ic_database_cleanup - //Sources.Unknown -> app.aaps.core.ui.R.drawable.ic_generic_icon - //Sources.Random -> R.drawable.ic_aaps - Sources.BgFragment -> R.drawable.ic_aaps - //Sources.Insulin -> R.drawable.ic_insulin - else -> error("Missing resource") @Composable override fun iconColor(source: Sources): Color = when (source) { Sources.Aaps -> ElementType.AAPS.color() @@ -325,7 +223,7 @@ class UserEntryPresentationHelperImpl @Inject constructor( Sources.Instara -> ElementType.CGM_DEX.color() Sources.Insulin -> ElementType.INSULIN_MANAGEMENT.color() Sources.InsulinDialog -> ElementType.INSULIN.color() - Sources.AfrezzaDialog -> ElementType.AFREZZA.color() + Sources.AfrezzaDialog -> ElementType.INSULIN.color() Sources.Intelligo -> ElementType.CGM_DEX.color() Sources.LocalProfile -> ElementType.PROFILE_MANAGEMENT.color() Sources.Loop -> ElementType.LOOP.color() diff --git a/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/wear/wearintegration/DataHandlerMobile.kt b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/wear/wearintegration/DataHandlerMobile.kt index 56d850b8609d..9d25697eb4a5 100644 --- a/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/wear/wearintegration/DataHandlerMobile.kt +++ b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/wear/wearintegration/DataHandlerMobile.kt @@ -11,7 +11,6 @@ import app.aaps.core.data.model.BS import app.aaps.core.data.model.GV import app.aaps.core.data.model.GlucoseUnit import app.aaps.core.data.model.HR -import app.aaps.core.data.model.IDs import app.aaps.core.data.model.RM import app.aaps.core.data.model.SC import app.aaps.core.data.model.Scene @@ -35,8 +34,6 @@ import app.aaps.core.interfaces.db.PersistenceLayer import app.aaps.core.interfaces.db.ProcessedTbrEbData import app.aaps.core.interfaces.insulin.ConcentrationHelper import app.aaps.core.interfaces.insulin.Insulin -import app.aaps.core.interfaces.insulin.InsulinManager -import app.aaps.core.interfaces.insulin.InsulinType import app.aaps.core.interfaces.iob.GlucoseStatusProvider import app.aaps.core.interfaces.iob.IobCobCalculator import app.aaps.core.interfaces.logging.AAPSLogger @@ -145,7 +142,6 @@ class DataHandlerMobile @Inject constructor( private val uel: UserEntryLogger, private val activePlugin: ActivePlugin, private val insulin: Insulin, - private val insulinManager: InsulinManager, private val commandQueue: CommandQueue, private val fabricPrivacy: FabricPrivacy, private val uiInteraction: UiInteraction, @@ -392,30 +388,6 @@ class DataHandlerMobile @Inject constructor( .onErrorComplete() } .subscribe() - disposable += rxBus - .toObservable(EventData.ActionAfrezzaPreCheck::class.java) - .observeOn(aapsSchedulers.io) - .concatMapCompletable { - rxCompletable { - aapsLogger.debug(LTag.WEAR, "ActionAfrezzaPreCheck received $it from ${it.sourceNodeId}") - handleAfrezzaPreCheck(it) - } - .doOnError(fabricPrivacy::logException) - .onErrorComplete() - } - .subscribe() - disposable += rxBus - .toObservable(EventData.ActionAfrezzaConfirmed::class.java) - .observeOn(aapsSchedulers.io) - .concatMapCompletable { - rxCompletable { - aapsLogger.debug(LTag.WEAR, "ActionAfrezzaConfirmed received $it from ${it.sourceNodeId}") - doAfrezzaBolus(it.units) - } - .doOnError(fabricPrivacy::logException) - .onErrorComplete() - } - .subscribe() disposable += rxBus .toObservable(EventData.ActionECarbsPreCheck::class.java) .observeOn(aapsSchedulers.io) @@ -1172,63 +1144,6 @@ class DataHandlerMobile @Inject constructor( ) } - private suspend fun handleAfrezzaPreCheck(command: EventData.ActionAfrezzaPreCheck) { - val units = command.units - if (units !in listOf(4, 8, 12)) { - sendError("Invalid Afrezza cartridge: ${units}U") - return - } - // Find the Afrezza ICfg from InsulinManager - val afrezzaPeak = InsulinType.OREF_INHALED_AFREZZA.insulinPeakTime - val afrezzaIcfg = insulinManager.insulins.firstOrNull { it.insulinPeakTime == afrezzaPeak } - ?: insulinManager.insulins.firstOrNull { InsulinType.fromPeak(it.insulinPeakTime).isInhaled } - if (afrezzaIcfg == null) { - sendError(rh.gs(app.aaps.core.ui.R.string.afrezza_not_configured)) - return - } - val message = "Afrezza: ${units}U" - rxBus.send( - EventMobileToWear( - EventData.ConfirmAction( - rh.gs(app.aaps.core.ui.R.string.confirm).uppercase(), message, - returnCommand = EventData.ActionAfrezzaConfirmed(units), - insulin = units.toDouble(), - ) - ) - ) - } - - private suspend fun doAfrezzaBolus(units: Int) { - val afrezzaPeak = InsulinType.OREF_INHALED_AFREZZA.insulinPeakTime - val afrezzaIcfg = insulinManager.insulins.firstOrNull { it.insulinPeakTime == afrezzaPeak } - ?: insulinManager.insulins.firstOrNull { InsulinType.fromPeak(it.insulinPeakTime).isInhaled } - if (afrezzaIcfg == null) { - sendError(rh.gs(app.aaps.core.ui.R.string.afrezza_not_configured)) - return - } - val now = dateUtil.now() - val bolus = BS( - timestamp = now, - amount = units.toDouble(), - type = BS.Type.NORMAL, - notes = "Afrezza inhaled", - iCfg = afrezzaIcfg, - ids = IDs(pumpId = now) - ) - persistenceLayer.insertOrUpdateBolus( - bolus = bolus, - action = Action.BOLUS, - source = Sources.Wear, - note = "Afrezza inhaled" - ) - uel.log( - action = Action.BOLUS, source = Sources.Wear, - "Afrezza inhaled", - ValueWithUnit.Insulin(units.toDouble()) - ) - aapsLogger.info(LTag.WEAR, "Afrezza ${units}U logged via Wear with ICfg: ${afrezzaIcfg.insulinLabel}") - } - private fun handleECarbsPreCheck(command: EventData.ActionECarbsPreCheck) { val startTimeStamp = System.currentTimeMillis() + T.mins(command.carbsTimeShift.toLong()).msecs() val cob = iobCobCalculator.ads.getLastAutosensData("carbsDialog", aapsLogger, dateUtil)?.cob ?: 0.0 diff --git a/ui/src/main/kotlin/app/aaps/ui/compose/main/MainScreen.kt b/ui/src/main/kotlin/app/aaps/ui/compose/main/MainScreen.kt index 347b557238fd..a05da1f01ccd 100644 --- a/ui/src/main/kotlin/app/aaps/ui/compose/main/MainScreen.kt +++ b/ui/src/main/kotlin/app/aaps/ui/compose/main/MainScreen.kt @@ -425,22 +425,6 @@ fun MainScreen( } } - // Treatment bottom sheet - if (showTreatmentSheet) { - val treatmentState by treatmentViewModel.uiState.collectAsStateWithLifecycle() - TreatmentBottomSheet( - onDismiss = { showTreatmentSheet = false }, - showCgm = treatmentState.showCgm, - showCalibration = treatmentState.showCalibration, - showTreatment = treatmentState.showTreatment, - showInsulin = treatmentState.showInsulin, - showCarbs = treatmentState.showCarbs, - showCalculator = treatmentState.showCalculator, - isDexcomSource = treatmentState.isDexcomSource, - showSettingsIcon = treatmentState.showSettingsIcon, - quickWizardItems = treatmentState.quickWizardItems, - onNavigate = onNavigate, - treatmentButtonsDef = treatmentButtonsDef, // Treatment bottom sheet if (showTreatmentSheet) { val treatmentState by treatmentViewModel.uiState.collectAsStateWithLifecycle() diff --git a/wear/src/main/AndroidManifest.xml b/wear/src/main/AndroidManifest.xml index 1043c6fee0b6..adf570ecb474 100644 --- a/wear/src/main/AndroidManifest.xml +++ b/wear/src/main/AndroidManifest.xml @@ -715,11 +715,6 @@ android:exported="true" android:label="@string/action_insulin" android:theme="@style/WearAppCompatDark" /> - - when (page) { - 0 -> AfrezzaDoseSelectScreen( - onDoseSelected = { selectedUnits = it }, - selectedUnits = selectedUnits, - ) - else -> AfrezzaConfirmScreen( - units = selectedUnits, - onConfirm = { confirmAfrezza(selectedUnits) }, - ) - } - } - HorizontalPageIndicator( - pagerState = pagerState, - modifier = Modifier.align(Alignment.BottomCenter).padding(bottom = 4.dp) - ) - } - } - } - } - - private fun confirmAfrezza(units: Int) { - rxBus.send(EventWearToMobile(ActionAfrezzaPreCheck(units))) - startActivity( - Intent(this, ConfirmationActivity::class.java).apply { - putExtra(ConfirmationActivity.EXTRA_ANIMATION_TYPE, ConfirmationActivity.SUCCESS_ANIMATION) - putExtra(ConfirmationActivity.EXTRA_MESSAGE, getString(R.string.action_afrezza_confirmation)) - } - ) - finish() - } -} - -@Composable -private fun AfrezzaDoseSelectScreen( - onDoseSelected: (Int) -> Unit, - selectedUnits: Int, -) { - val haptic = LocalHapticFeedback.current - - Column( - modifier = Modifier - .fillMaxSize() - .padding(horizontal = 12.dp, vertical = 8.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center - ) { - Text( - text = stringResource(R.string.action_afrezza_select_dose), - color = Color.White, - fontSize = 14.sp, - fontWeight = FontWeight.Bold, - ) - Spacer(Modifier.height(10.dp)) - Row( - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically, - ) { - for (dose in listOf(4, 8, 12)) { - val isSelected = selectedUnits == dose - Box( - modifier = Modifier - .size(52.dp) - .clip(CircleShape) - .background(if (isSelected) AfrezzaTeal else AfrezzaTealDark) - .clickable { - haptic.performHapticFeedback(HapticFeedbackType.LongPress) - onDoseSelected(dose) - }, - contentAlignment = Alignment.Center - ) { - Text( - text = "${dose}${stringResource(R.string.insulin_unit_short)}", - color = Color.White, - fontSize = 16.sp, - fontWeight = FontWeight.Bold, - ) - } - if (dose != 12) Spacer(Modifier.width(8.dp)) - } - } - } -} - -@Composable -private fun AfrezzaConfirmScreen(units: Int, onConfirm: () -> Unit) { - val haptic = LocalHapticFeedback.current - var confirmationSent by remember { mutableStateOf(false) } - - Column( - modifier = Modifier - .fillMaxSize() - .padding(horizontal = 24.dp, vertical = 8.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center - ) { - Text( - text = stringResource(R.string.request), - color = Color.White, - fontSize = 18.sp, - fontWeight = FontWeight.Bold, - ) - Spacer(Modifier.height(8.dp)) - Box( - modifier = Modifier - .size(90.dp) - .clip(CircleShape) - .clickable(enabled = units > 0 && !confirmationSent) { - confirmationSent = true - haptic.performHapticFeedback(HapticFeedbackType.LongPress) - onConfirm() - }, - contentAlignment = Alignment.Center - ) { - Icon( - painter = painterResource(R.drawable.ic_confirm), - contentDescription = stringResource(R.string.confirm), - tint = ConfirmGreen, - modifier = Modifier.fillMaxSize() - ) - } - Spacer(Modifier.height(4.dp)) - Text( - text = "$units ${stringResource(R.string.insulin_unit_short)} ${stringResource(R.string.action_afrezza)}", - color = AfrezzaTeal, - fontSize = 24.sp, - fontWeight = FontWeight.Bold, - ) - } -} diff --git a/wear/src/main/kotlin/app/aaps/wear/tile/source/ActionSource.kt b/wear/src/main/kotlin/app/aaps/wear/tile/source/ActionSource.kt index 9c32081c5df9..d155e3fe242f 100644 --- a/wear/src/main/kotlin/app/aaps/wear/tile/source/ActionSource.kt +++ b/wear/src/main/kotlin/app/aaps/wear/tile/source/ActionSource.kt @@ -10,7 +10,6 @@ import app.aaps.wear.interaction.actions.BackgroundActionActivity import app.aaps.wear.interaction.actions.BolusActivity import app.aaps.wear.interaction.actions.CarbActivity import app.aaps.wear.interaction.actions.ECarbActivity -import app.aaps.wear.interaction.actions.AfrezzaActivity import app.aaps.wear.interaction.actions.TempTargetActivity import app.aaps.wear.interaction.actions.TreatmentActivity import app.aaps.wear.interaction.actions.WizardActivity @@ -44,13 +43,6 @@ class ActionSource @Inject constructor(context: Context, sp: SP, aapsLogger: AAP iconRes = R.drawable.ic_bolus, activityClass = BolusActivity::class.java.name, ), - StaticAction( - settingName = "afrezza", - buttonText = resources.getString(R.string.action_afrezza_short), - settingLabel = resources.getString(R.string.action_afrezza), - iconRes = R.drawable.ic_afrezza, - activityClass = AfrezzaActivity::class.java.name, - ), StaticAction( settingName = "carbs", buttonText = resources.getString(R.string.action_carbs), diff --git a/wear/src/main/res/drawable/ic_afrezza.xml b/wear/src/main/res/drawable/ic_afrezza.xml deleted file mode 100644 index a5eb01b9f169..000000000000 --- a/wear/src/main/res/drawable/ic_afrezza.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - diff --git a/wear/src/main/res/values/strings.xml b/wear/src/main/res/values/strings.xml index 7d06555b26c3..e35902de26f6 100644 --- a/wear/src/main/res/values/strings.xml +++ b/wear/src/main/res/values/strings.xml @@ -89,11 +89,6 @@ STOP Treatment Requested Bolus Requested - Afrezza - Afrz - Afrezza Requested - Select Cartridge - Log %1$dU? Calculation Requested Fill Requested Carbs Requested