Repository navigation
WIP: AAPS v4 - #4903
Draft
MilosKozak wants to merge 3616 commits into
Draft
WIP: AAPS v4#4903MilosKozak wants to merge 3616 commits into
MilosKozak wants to merge 3616 commits into
Conversation
MilosKozak
marked this pull request as draft
June 16, 2026 16:31
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #4903 +/- ##
=============================================
+ Coverage 34.49% 60.68% +26.19%
- Complexity 9670 23732 +14062
=============================================
Files 2416 3633 +1217
Lines 126587 198990 +72403
Branches 16412 25444 +9032
=============================================
+ Hits 43661 120764 +77103
+ Misses 79691 68957 -10734
- Partials 3235 9269 +6034
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
❌ The last analysis has failed. |
MilosKozak
force-pushed
the
dev
branch
2 times, most recently
from
September 16, 2026 13:14
6b48aaa to
f3e9c5d
Compare
… a restart no longer sets the basal again
…s-codec-commons-codec-1.23.0 Bump commons-codec:commons-codec from 1.22.1 to 1.23.0
…e, so the widget no longer goes empty 0c1a45c draws the widgets in the app process with compose(), on Dispatchers.Default. To make a frame, Glance copies the widget tree while the composition may still be building it. With many threads both ran at the same time: the first frame sometimes held a half built tree (and once the copy threw a ConcurrentModificationException). compose() sent only that first frame and closed the session, so the real frame was lost and the widget stayed empty until a later update. Changing the opacity drew it again, which is why that brought it back. The session now runs on Dispatchers.Default.limitedParallelism(1), like Glance's own worker runs it on the main thread. Every frame goes to the launcher, and the session is closed only after one second without a new frame. That also gives Glance time to save which root layout it gave to the widget shape; when that save was lost, two shapes could get the same root layout.
…frame, without time labels and closer together (replaces #5293)
# Conflicts: # ui/src/commonMain/kotlin/app/aaps/ui/compose/overview/graphs/GraphsSection.kt
BG graph pop-up: press and hold for more data
# Conflicts: # pump/carelevo/src/main/kotlin/app/aaps/pump/carelevo/command/CarelevoActivationExecutor.kt # pump/carelevo/src/test/kotlin/app/aaps/pump/carelevo/command/CarelevoActivationExecutorTest.kt
…ver reports as failed
…in, not refused Since 58833e6 the phone wizard goes through WizardBolusExecutorImpl.prepareWizard(), which refused a dose above the max bolus ("Insulin constraint violation! Cannot deliver X U"). That rule came from the watch. In v3 the phone wizard capped the dose to the limit and showed "Bolus constraint applied: X U to Y U". prepareWizard now caps on every surface (phone, AAPSClient, watch): the confirmation shows the capped dose and the warning. The capped dose is also the base the watch adds its +/- steps to, so the total the watch shows is the dose that is given. QuickWizard still refuses: it is a one-tap preset. When the limit leaves no insulin and there are no carbs, the wizard still refuses.
Dynamic Scale for Activity Curve
Overview: Add edit button to header of scenes bottom sheet
…olus) from the patch total
Two follow-ups to #5251. Nothing to draw: zeroFloorNiceRange(0.0, 0.0) returns 0..1, because niceScale widens a zero-width range by 1 so that it always has a usable tick step. The scale was therefore positive for a window with no insulin on board at all, and a flat line was drawn on the zero anchor - on the BG graph that is the low mark, the bottom edge of the in-range belt, where it reads as a second target line. Catch the all-zero case before the nice-ification and return a zero scale. A window where activity is constant but not zero is a real curve and still draws. Room for the IOB overlay: the windowed nice range of the primary layer moves into its own value, primaryDataNiceRange, which no longer depends on hasBasalLayer. The IOB+basal axis is built from it, and so is the activity overlay, which needs the room the axis leaves on each side of zero whether or not basal is drawn. Reading that room from the IOB+basal axis made the overlay vanish with no other symptom whenever that axis was absent. In that case the real axis is the zero-aligned dual-axis range, which always contains this nice range, so the curve is sized a little conservatively and still sits in frame with its zero on the axis zero.
…overview again, and the widget works (#5301) A scene without a duration stores its temporary target and therapy event with duration Long.MAX_VALUE until the scene ends. In Kotlin, timestamp + duration then wraps to a negative number, so the entry looked like it ended long ago: - the BG graph drew the profile target instead of the temp target - the treatment belt drew no bar for the event - the home screen widget and Android Auto failed on every update, because untilString made an infinite Duration and infinite minus infinite throws - the temp target editor copied Long.MAX_VALUE as the duration, so activating from it started a target that never ends and no scene closes - lists showed a wrong end time and a huge duration Dosing was not affected: the database query does not overflow. endOf() gives the end of an entry and stops at Long.MAX_VALUE instead of wrapping. TT.end uses it, and TT.hasEnd / TE.hasEnd tell a scene entry without an end. Text that shows a remaining time or a duration now leaves it out for such an entry, like the overview chip already did. timeFrameString returns "∞" for an infinite Duration instead of throwing. Also removed remainingTimeMs from the temp target screen: it was never shown.
…r_anchor Profile editor: the hour wheel opens over the time chip that was tapped
… temp basal is not ended by the wrong entry (#3378)
…so a 723 is no longer shown as suspended by a record dated 2060 (#1398)
…es forward (#449) When the clock goes forward, the local hour from 02:00 to 03:00 does not exist. The pump clock was read with Joda DateTime(y, m, d, h, m, s) in the phone time zone, which throws for such a time. The reply then counted as not received, the pump time was 0, readPumpStatus reset the pump and lastConnection became 0, so "pump unreachable" came at once, without the set limit. - Pumps with local time (Dana RS): right after the change the pump clock still shows the missing hour. It is read as the next hour, which is the real time, the same rule the history already used (dateTimeSecFromBuff). One helper, DanaRSPacket.localTimeToMillis, for the pump clock and the history. - Pumps with UTC (Dana-i): the clock is read as UTC, the way AAPS sets it. The old code read it as local time and added the offset of now. That threw when the UTC time fell in the local gap (04:00 to 05:00 in central Europe), was an hour off in the hour after the change, and was 30 minutes off in zones with half hours, because the offset was cut to whole hours. - A date without a time (shipping date, daily total) starts at the first time that exists, for zones that change the clock at midnight. Outside these cases the values are the same as before.
The prepare pass wrote endTime = toTime into the cached TimeRange and threw away the horizon the post phase had put there. The IOB and activity series are built in that same pass, so they stopped at the next full hour while the axis went on to the newest prediction. Regressed in 426178f, which dropped the max() that 8903cdb had put there on purpose. Two phases write endTime and only one of them knows the horizon, so give them one rule: the post phase owns endTime, the prepare pass may widen it but never narrow it. keptEndTime() carries that rule, with an upper guard for a clock or time zone change that moves toTime back by hours. The post phase now sets endTime to the newest prediction instead of the window share clamped to two hours. The axis is already max(newest point, endTime), so this widens nothing - it only makes endTime agree with the edge the series are measured against. The two hour clamp stays where it belongs, deciding how much history to fetch. BGI does not change: bgiPrediction is gated on autosensData, which is null for any future time, so it has always been empty.
… gets its own answer, so the fill step no longer times out, and CI waits longer for a slow shard
… and for a failed read that is read again (#2512)
The method has had this guard since 36040b7 and no test covered it, although something visible depends on it: the future part of the BGI series on the overview is built only where this method answers, so it is empty. That is not obvious from reading PrepareGraphDataRunner alone, see #5304. The test asserts the past case as well, or a null that only came from "no bucketed data at all" would look like a pass. It builds its times from the real clock, because the method reads Clock.System.now() itself instead of the clock it is given, so the test fixture time would never reach the guard.
The age was built in two forms, and the row showed the long one
("3 days 4 hours") wherever it fit. So the same row read differently on a
phone and on a tablet, and on a narrow screen the long words pushed the
row's own name out of its place. There is one form now, the short one, on
every screen size.
While there is no full day yet, the days part is left out: a sensor
inserted this morning reads "4h", not "0d4h". It has its own format
string, status_age_short_hours, so nothing is cut off a string in code.
LongOrShort was only used here and is removed with it.
"Bluetooth Low Energy not supported or device not paired" was shown where the code already knew which of the two it was, and in one place where it was neither. Medtrum sent it whenever connect() was refused. That happens when a Bluetooth permission is missing, which the transport already reports on its own; when there is no Bluetooth adapter, which now says "Bluetooth Low Energy not supported"; and when the service is already connecting, which is normal and is now only a log line. Medtrum never pairs, it looks for the serial number, so "device not paired" could not be true there at all. The loop calls connect() over and over, so the third case was the one users saw, turned into a notification when no screen was open. Dana RS showed it in the error banner when the pump has an address but is not bonded. That is only about pairing, and BLE support is not tested there, so it now says "Please pair your pump with your phone!", the same string the notification from BLEComm uses. The combined string has no callers left and is removed.
The client's COB calculation takes the ISF for carbs from the master's device status. The first calculation after a start can run before Nightscout has delivered one, and then every bucket uses the profile ISF: with dynamic ISF on the master that is about twice the value, so carbs decay at half speed and COB stays too high until something else resets the calculation. The device status handler now says once when the master's value has arrived, and the calculation starts over on it.
The master's ISF for carbs is the average dynamic ISF of the 24 hours before each bucket, so it moves through the day. The client used the newest value for every bucket, and a full calculation after a restart absorbed the early buckets slower than the master whenever the ISF had risen since: COB 19 g where the master had 14 g. The client stores every APS result it gets with a device status; the calculation now reads the carbs ISF of those results, time and value only, and takes the newest one at or before each bucket. Before the first stored result the newest value still serves.
On top of the two commits before this one, for issue #5404. The reset on EventMasterCarbsIsfArrived ran at every client start. The client keeps that value only in memory, so it is always absent when the calculation at start runs, and the event always came. But the calculation now reads the master's ISF for carbs of each bucket's own time from the stored results, so a client that has results stored is right from the first run and the reset only calculated the same numbers again, about four seconds of a full recalculation per start. The calculation says instead whether it had to use the profile ISF, which happens when the client has stored nothing for the calculated time - a new install, or after a database reset. Only then does the arriving value change anything, and only then does the calculation start over. The flag is only set, never cleared by a later run, because a run that does find stored values does not make the buckets already in the cache right; it is cleared with the cache. Also: - Before the first stored result the oldest stored one is used instead of the master's newest value. The master's value of a few hours ago is much nearer to what it had then, and the newest value is what made COB too high in the first place. - A note in ProfileSealed.getIsfMgdlForCarbs that it answers a client with the newest value and ignores the timestamp, so the calculation must not ask there for a past bucket. - The missing newline at the end of APSResultDao.kt.
…tored loop results (#5404)
The bar showed the length that was set and never changed it, while the countdown notification showed the time from the start until the ring, which did grow with every +5 min. The same timer showed two different numbers. extend() now moves the end and the whole wait together, so both read the same, and the text says "Total wait: 25 min" instead of "Set: 25 min": after +5 min it is no longer the length that was set. The end itself does not change: 5 minutes on top of the end while the timer runs, 5 minutes from now once it has rung. On the alarm screen "Mute" and "OK" did not say what they do. They are now "Silence alarm" (the screen stays open) and "Silence and close". This is shown for every alarm, not only the eat reminder.
The timer rang with AlarmSound.ALARM, the same sound as a BG alarm, so a reminder to eat sounded like something was wrong. It now has its own sound: a small steel dome struck twice, synthesized rather than sampled, so no licence is attached to it. The file sits in :core:ui/res/raw with the other four. The Xcode build script copies them into the iOS bundle by name, so it and the bundled count in ShellInfo are updated too - without that the sound would simply be missing on iOS and the alarm would stay silent there.
…he wizard The timer the phone starts after a bolus never reached the watch: nothing was sent, and its notifications are ongoing ones, which Wear OS does not bridge. Now the phone sends the timer's start and end with every status, also from a client's copy. The watch draws a dashed line at the end in its BG graph, on the graph screen and the tile, with the minutes left at it. The watch sets its own alarm at that time and rings by itself, so it rings with the phone out of reach too: a vibrating notification with Silence and +5 min, and a screen with the same two buttons over it. Both go to the phone, which stops or moves the timer; a client relays to its master. The next status closes the screen. The watch wizard gets a carb time page behind a new watch setting, off by default, next to the meal type one: minutes until the carbs are eaten, with a "Set alarm" bell that is on by default once the time is positive. The phone starts the timer after the bolus and moves the meal type's eCarbs with the meal, as its own wizard does. The custom watchface graph and the V4 face do not show the timer yet.
…ation
Ending a scene asked for two confirmations: the banner, and a notification
card next to it saying the same thing. Only the banner cleared both, so
anyone who reached for the card first - the natural thing when it sits in
the Android shade - was left with the banner still waiting.
A plain end and a follow-up that started now post nothing at all: the
banner of the ended scene, and the banner of the scene that started, say
it already. A follow-up that did NOT start is the one case with something
to add, so its reason goes on the same banner ("Follow-up did not start:
target scene was deleted") instead of into a card of its own. Only a chain
that partly failed still posts, because there the new scene's banner looks
healthy while it is half applied.
The reason rides on the active scene state, so it survives a restart and
reaches a client with the running configuration, as the rest of the scene
state does. Both places that posted the success card are covered: the
expiry runner and stopActiveSceneAndChain, which the End-Scene dialog and
a client's scene.stop(triggerChain) share.
The three ids that nothing posts any more stay in NotificationId: their
ordinals are the live system-notification ids, so removing them would
renumber every id after them.
…ge fits The ring screen is gone: Android does not start an activity from an alarm in the background, so it only showed up behind the notification's "Open app". The vibrating notification with Silence and +5 min is the ring now. The carb time page says "Carb time [min]" on two lines and keeps the bell row on one line, so the - and + buttons sit still from "Now" to "+60". The face's graph complication writes the minutes left at the timer's line too.
Between the start and the ring the watch only showed the line in the graph, so the timer could not be stopped from the watch before it rang. While it runs, a silent notification now sits in the stream: "Time to eat, Rings at 19:25", with Stop and +5 min. It follows every new end time, goes away when the timer ends, and times out by itself at the ring, when the ring's own notification takes over.
…ed elsewhere Silence from the watch, or Stop from a client or from the notification, ended the timer and muted the sound, but the alarm screen on the phone stayed open with nothing left to offer. It now closes when the timer is gone. The phone's own "Silence alarm" keeps it open, as for every alarm. Also on the watch wizard's request page: the carb time and the percentage share one line when both are there, since a third line under the check fell off the screen. And a note for translators that the carb time label must stay short.
…tion and silences the ring Wear OS lets the user swipe an ongoing notification away. For the running timer that notification is the only Stop and +5 min on the watch, so it comes back at once: its delete intent posts it again while the timer runs. Our own cancel does not fire the delete intent, so Stop, the ring and a timer ended on the phone still clear it. A swipe on the ringing notification silences the timer, as on a clock's alarm. Before, the phone kept ringing with nothing left on the watch to stop it. On the phone, the alarm screen now also closes when the timer is moved +5 min from the watch or a client, not only when it is stopped there. The alarm has rung when that screen opens, so a timer that has not rung is one that was extended.
A watch vibrates once per notification, while the phone rings until it is answered. So the ring is posted again every 30 s until 5 minutes after the end, with the same alarm intent: Silence, +5 min and a swipe on the watch, and a timer stopped or moved on the phone, cancel or replace that alarm and the repeats with it. The window for a valid alarm grows from 2 minutes around the end to 2 minutes before and 7 minutes after, so the repeats pass and an old timer is still ignored.
…t left behind Adding a client sometimes made two Nightscout tokens with the same name, and the second one was the only one the client got. Nightscout answers the create without the token: it builds the token from the subject id while it reloads its own list, and GET /authorization/subjects then serves that list from memory, never from the database. The list is reloaded at start and by a create, a change or a delete that the same Nightscout process handled - there is no timer. So the create can come back fine while the read right after it knows nothing about the new token, and the list stays that way until the next write. In the log from the report the create was answered 200, the read 12 ms later did not list it, the check two minutes later still did not, and the second try then made the second token. Reading again cannot mend that, because nothing refreshes the list without a write. Writing again does, so a new token that is not listed is now deleted again and made once more: at most two tries, each a create, up to three short reads, and a delete when it was never listed. The delete is the part that matters - without it the first token stays behind, invisible to the next try, which is how the two tokens of the same name appeared. If even the delete fails the token really is in Nightscout and only the user can clear it, so the dialog names it instead of showing a QR code for a token the client would not be able to use. The plain failure text says that nothing was left behind, because now that is true. Every branch logs what it did, so the next report of this says which one was taken. Worth fixing in Nightscout too, later: return the token from the create, or serve the subject list from the database. Old Nightscout versions stay as they are, so AAPS has to deal with this either way.
… the app language
…ter Silence starts a new timer (#5408)
…ugh the system, Android 12 follows the phone
…tored, on the master or a client (#5413)
…art, and one warning per batch instead of a stack trace per document (#5414)
…newest calculation wins, and no bell for a past carb time (#5415)
…client loses nothing, and a client shows the master's answer once (#5416)
…raphs below BG fill once the time range is known (#5418)
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.