Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

contact trick #595

Closed

Conversation

yurique
Copy link
Contributor

@yurique yurique commented Mar 20, 2024

This PR introduces a new feature for those of us who are using an Apple Watch.

I don't know who to give credit for the original idea, I first heard of it in the xdripswift project: JohanDegraeve/xdripswift#481 and before that, it was implemented in OpenGlück: https://github.com/open-gluck#the-contact-trick


Here, though, the implementation and offered features are much more flexible and configurable:

Multiple contacts

We can configure multiple contacts, and then add add them all to the watch face - so we can see more information. One contact provides too little space to reliably display more than a single number and maybe an indicator. Multiple contacts help with this.

Layouts

There are two layouts that can be configured (this is debatable - we can leave just one, to make configuration simpler).

  • single - the main layout, where we can configure a main piece of information to display (it will take the central, largest part of the image), and - optionally - two secondary pieces: top and bottom (which will take less space and will be smaller)
  • split - this layout will display two pieces of information in two equally sized parts of the image - top and bottom

Rings

In addition to the above, we can configure up to two optional "large" rings that will be drawn around the image (similar to what Apple offers in its weather-related complications, or in the activity tracker complication)

A ring can be one of the following:

  • loop status - the well known green/yellow/red ring
  • iob - the fraction of current IOB relative to the max IOB
  • cob - the fraction of current COB relative to the max COB
  • iobcob - a "double" ring; left side - IOB, right side - COB

"Pieces"

Every area can be configured to display one of the following values:

  • current BG
  • eventual BG
  • latest delta
  • current trend
  • time of the latest BG measurement
  • time of the latest loop cycle
  • COB
  • IOB
  • ring - a small green/yellow/red ring

Extra customizations

In addition to the above, the following is configurable:

  • font name - either the system default or one of the fonts available on your phone
  • font size - the size for the primary area; secondary areas will be smaller; for all areas the font will be automatically reduced so that the displayed value fits in the designated "rectangle"
  • font tracking - how tight or wide the text should be
  • ring width - the width of the ring (in the percents of the image size)
  • ring gap - how much gap to leave between rings and/or the rest of the displayed elements
  • dark mode - this affects the color of the text, when dark mode is enabled - the text will be closed to white, when it's disabled, the text will be darker

Downsides / important to know

Unlike calendar events, the contact picture doesn't have an expiration time: so if something happens (iAPS crashes or something else) - the contact picture will stay there "forever" and will become misleading. Therefore I would recommend having one "contact" to display the time of the latest loop (that's what I'm doing myself).

Watch battery life? I haven't noticed any significant differences over the past couple of days. But I would guess constant contact updating might drain the battery a little bit faster than normal.

The white ring around the contact picture - unfortunately, that's part of the Apple's Contact complication design. It's always there - eating up precious screen estate - and no way to change it ¯_(ツ)_/¯


Screenshots image image image image image image image image image image image image image image

image

@yurique yurique mentioned this pull request Mar 20, 2024
@yurique
Copy link
Contributor Author

yurique commented Mar 20, 2024

I'm going to add some details to the PR description (didn't have much time today). Also it still needs a pass or two of cleanup (for example, I suspect there are things that I put into the ContactTrickState that are not needed there, etc).

As of now, my biggest question would be this: am I doing things the right way in BaseContactTrickManager? (I copy-pasted the code from WatchManager initially, and then updated it).

Another thing is the list of things that can be useful on a watch face. I added a bunch, but I've been using iAPS for just a couple of days, so I'm just guessing. Also need to double-check that the "stale" state is rendered correctly - with -- instead of numbers, etc.

@yurique
Copy link
Contributor Author

yurique commented Mar 20, 2024

Another thing - I called the thing "Contact Trick" everywhere, which might be not the best name/label :)

@Jon-b-m
Copy link
Member

Jon-b-m commented Mar 22, 2024

  • I think the naming is good, it's descriptive and intuitive.

  • The most important element to display is really the current glucose, and the reason for why the contact trick is even needed.

After that I think green/orange/red loop. Perhaps even IOB as on option.

The loop and the IOB and the COB and all of the other loop status can all be retrieved from just the suggestion.

I therefore don't think we need all of the other stuff or imported modules here, as we don't need to enact anything (don't need to communicate with pump), just retrieve the last suggestion.

@Jon-b-m
Copy link
Member

Jon-b-m commented Mar 22, 2024

I suggest to start small, then we/you eventually can add stuff later.

@yurique
Copy link
Contributor Author

yurique commented Mar 22, 2024

Thank you for your thoughts on this, Jon!

I ended up setting up BG+trend+IOB on one contact and green/orange/red loop+last loop time on the second one - and it does seem to work well:

IMG_3281

I suggest to start small, then we/you eventually can add stuff later.

Sounds good! I'll be removing things/imports (later today, hopefully).

I also need to tweak the rendering a little bit, some things end up too small under certain circumstances - too hard to see inside a small circle.

@yurique
Copy link
Contributor Author

yurique commented Mar 22, 2024

I think last loop time is important because the contact picture doesn't have an expiration time, unlike calendar events - so if something happens (iAPS crashes or something else) - the contact picture will stay there "forever" and will become misleading.

In fact, that's the biggest downside of this approach (and a risk), even though it hasn't happened a single time over the past two days of me running it. I think I should add some warning/description about this in the settings view.

@yurique
Copy link
Contributor Author

yurique commented Mar 23, 2024

@Jon-b-m I pushed some tweaks-and-cleanups - removed most of the imports and subscriptions in the manager, dropped part of the "state" and tweaked the rendering a little bit.

I will need to try it "live" to see if it looks good/is readable on the watch, but otherwise it all should be in a more or less decent state.

@yurique yurique marked this pull request as ready for review March 23, 2024 00:23
@@ -11,6 +11,7 @@ class BaseProvider: Provider, Injectable {
@Injected() var deviceManager: DeviceDataManager!
@Injected() var storage: FileStorage!
@Injected() var bluetoothProvider: BluetoothStateManager!
@Injected() var contactTrickManager: ContactTrickManager!
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure if I should have added this here - it's referenced from the ContactTrick.Provider when contacts are saved

private func renderContacts() {
contacts.forEach { renderContact($0) }
workItem = DispatchWorkItem(block: {
print("in updateContact, no updates received for more than 5 minutes")
Copy link
Contributor Author

@yurique yurique Mar 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

haven't looked yet into how to log things like this correctly

SuggestionObserver,
SettingsObserver
{
func glucoseDidUpdate(_: [BloodGlucose]) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure if it's necessary to subscribe to glucose updates, or if subscribing to suggestions is enough

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess BG can also be retrieved from the suggestion, instead of asking self.coreDataStorage.fetchGlucose(...)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but not trend or delta 🤔

private var glucoseFormatter: NumberFormatter {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.locale = Locale(identifier: "en_US")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the locale explicitly here in order to make it print decimal separators as points (.) not commas (,), because It looked weird-ish on the complication, but it doesn't seem right to override the locale like this 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed it.

@@ -61,6 +61,9 @@ extension Settings {
Text("Dynamic ISF").navigationLink(to: .dynamicISF, from: self)
} header: { Text("Extra Features") }

Section {
Text("Contact trick").navigationLink(to: .contactTrick, from: self)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right now the Contact Trick section is located in the main settings view, right above the Debug options - not sure what's the right place for it

DispatchQueue.main.asyncAfter(deadline: .now() + 5 * 60 + 15, execute: workItem!)
}

private func renderContact(_ entry: ContactTrickEntry) {
Copy link
Contributor Author

@yurique yurique Mar 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these larger functions are straight copy-pastes from the WatchManager, it probably deserves a refactoring but I don't feel qualified for that :)

fontTracking: FontTracking,
color: Color
) {
// guard let context = UIGraphicsGetCurrentContext() else {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

commented out - just some stuff to help debugging, to be deleted

processQueue.async {
let readings = self.coreDataStorage.fetchGlucose(interval: DateFilter().twoHours)
let glucoseValues = self.glucoseText(readings)
self.state.glucose = glucoseValues.glucose
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self.state doesn't have to be an instance field in this class - I could create a new one every time and pass it along into renderContacts() - but it was done like this in WatchManager so I kept it the same

@yurique
Copy link
Contributor Author

yurique commented Mar 23, 2024

I was observing something just now - the ring was green (in the app), last update was 3 min ago, but on the contact picture it was yellow (and 8 minutes ago) - I did "force" the refresh of the contact, so it seems like my code was getting a wrong time of the latest cycle somehow?

self.state.lastLoopDate = self.apsManager.lastLoopDate

(it got fixed with the next cycle, so I didn't have a chance to investigate more)

@Jon-b-m
Copy link
Member

Jon-b-m commented Mar 23, 2024

I was observing something just now - the ring was green (in the app), last update was 3 min ago, but on the contact picture it was yellow (and 8 minutes ago) - I did "force" the refresh of the contact, so it seems like my code was getting a wrong time of the latest cycle somehow?

self.state.lastLoopDate = self.apsManager.lastLoopDate

(it got fixed with the next cycle, so I didn't have a chance to investigate more)

You don’t need APSmanager for this. Everything can be retrieved from suggestion. suggestion.timestamp

@Jon-b-m
Copy link
Member

Jon-b-m commented Mar 23, 2024

I will take a closer look at this PR next week, I hope. As soon as I’m done with some other iAPS work. Thank you!

@yurique
Copy link
Contributor Author

yurique commented Mar 23, 2024

You don’t need APSmanager for this. Everything can be retrieved from suggestion. suggestion.timestamp

Aha! Very good! :)

@yurique
Copy link
Contributor Author

yurique commented Mar 23, 2024

Another thing I'm observing today - the contact pictures on the watch face are displaying the pictures from 5 hours ago, even though the contact manager was updating the contacts without problems. Tapping on the complication opens the contact - the picture is up-to-date there, as well as when looking at the contact on the phone. Not sure what has happened. Locking/unlocking the watch fixed it.

@yurique
Copy link
Contributor Author

yurique commented Mar 23, 2024

I had to restart the watch to fix it. The contact pictures on the watch were not updating, or updating but with a very long delay.

@yurique
Copy link
Contributor Author

yurique commented Mar 29, 2024

A quick update: I haven't seen any issues with contacts being out of date on the watch face since the last time I mentioned it here - works like a clock.
I suspect it might have been due to the bug I had in the code - there is a task that is scheduled to run 5+ minutes after the last update (if no other updates received), and somehow I managed to delete the line that would cancel the previously scheduled task when needed. So after a while, the contacts update would end up running a LOT.

@Jon-b-m
Copy link
Member

Jon-b-m commented May 7, 2024

Now merged into dev via another branch, as I had to make some small edits after testing. Thank you @yurique

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
No open projects
Status: Done
Development

Successfully merging this pull request may close these issues.

2 participants