Skip to content

Add tappable zoom buttons to main graph view - #651

Open
aug0211 wants to merge 182 commits into
loopandlearn:devfrom
aug0211:auggie-tappable-zoom-buttons
Open

Add tappable zoom buttons to main graph view#651
aug0211 wants to merge 182 commits into
loopandlearn:devfrom
aug0211:auggie-tappable-zoom-buttons

Conversation

@aug0211

Copy link
Copy Markdown
Contributor
  • Add tappable buttons to main view for zooming in/out/selecting pre-defined zoom level
  • Very nice to have on iOS, but also fantastic for users deploying on Mac, where pinch to zoom is not supported
  • Disable pinch to zoom on Mac to avoid force closes

aug0211and others added 30 commits May 8, 2026 00:33
Watch app with BG chart, remote controls (bolus, meal, override, temp target), and Nightscout/Dexcom Share data fetching. APNS push uses alert type with time-sensitive delivery for reliable command delivery.
Isolated watch-only changes on top of latest upstream dev — no unrelated iOS feature modifications.
- Complications for rectangle, circle, also bolus/meal/override/temp deep link shortcuts
- Dynamic BG color integrated throughout experiences
- Stats view on watch
- LoopStatus view on watch
- Cleaned up crown rotation requirements
- Watch was reading top-level `token` from /api/v1/status.json but Nightscout nests it under `authorized.token`
- POST treatments now actually authorize
- Cached APNS JWTs for 55 min (with 403-invalidation) instead of re-signing P256 on every send
- Forwarded LF APNS creds over WCSession so Trio return-notifications land on the iPhone
- Add vertical dotted orange lines to main graph for "this time" on prior days when scrolling back
- Add vertical dotted orange lines to the mini graph for "this time" on prior days
- Add tappable buttons to main view for zooming in/out/selecting pre-defined zoom level
- Useful on iOS but also fantastic for users deploying on Mac, where pinch to zoom is not supported
- Disable pinch to zoom on Mac to avoid force closes
- Apply SwiftFormat auto-fixes to resolve CI lint failures
- Changes include indentation, file headers, spacing around operators, unused argument markers, brace wrapping, trailing commas, sort imports, and redundant self removal
- Covers ~25 files in LoopFollowWatch, LoopFollowWidgets, and LoopFollow/Watch.
- Removed unneeded empty lines
- PhoneSessionManager.swift: added back in mealWithFatProtein, maxProtein, and maxFat to the buildConfig() dictionary so they're actually sent to the watch
- RemoteSettingsViewModel.swift: added PhoneSessionManager.shared.sendConfig() in $mealWithFatProtein so toggling it immediately pushes the new config to the watch
- BGFetcher.swift: add completion handler to fetchDeviceStatus so bolus screen can await fresh IOB/COB
- WatchBolusView.swift: fetch latest device status on appear with loading overlay to prevent stale COB double-counting
- Changed the max from 100g to 200g for all three: Max Carbs, Max Fat, and Max Protein in RemoteSettingsView.swift
- COB could under report by not including freshly entered COB in meal entry flow during bolus screen
- Adds Display Smoothed BG toggle in Settings → Advanced (off by default).
- Pulls smoothed values from Nightscout devicestatus openaps.enacted.bg / openaps.suggested.bg and stores an in-memory history bounded by the graph's day range.
- Renders smoothed values as a light-grey line on the main BG chart; CGM dots stay colorful, but the dot-connecting line is hidden while smoothing is on. Display Dots / Display Lines toggles are disabled in Graph Settings during this mode.
- Chart-tap popup shows the smoothed value (✨ … ✨) above the raw CGM value when a match is available within tolerance of the dot's timestamp.
- All work (fetches, parsing, polling) is gated on the toggle so users who don't enable it incur no extra overhead.
- Add the option for users to display the smoothed value in the information display table as well.
- When the latest Nightscout devicestatus record is "thin" (e.g. SMB-only notifications or partial loop runs that Trio sometimes writes between full-state runs), its enactedOrSuggested block lacks timestamp / bg / TDD, leaving the Updated, TDD, and Smoothed BG info-table rows empty
- Add .smoothedBg to the per-update clear list in updateDeviceStatusDisplay so it's blanked alongside Updated and TDD on every refresh: no stale value persists past a sparse fetch
- Detect a sparse record by snapshotting Observable.shared.enactedOrSuggested.value before processing and comparing after; the OpenAPS parser only updates that Observable when the timestamp parses, so an unchanged value signals a sparse record.
- Extend the devicestatus fast-poll override so it fires on sparse records too. Same backoff as the smoothed-line retry: 3s while the latest BG is under 60s old, 15s out to 5 minutes, then fall back to the normal cadence. No count > 1 fetch, no historical fallback, just keep polling for the next full record.
- The Smoothed BG feature added a chart dataset that contributes ~280 entries per downloadDays day, pushing the chart's total data.entryCount from ~600 to ~880 with smoothing on (1-day default).
- DGCharts gates value-label rendering on data.entryCount < maxVisibleCount × scaleX. With the library default of 100, the 3h zoom (scaleX = 8, cap 800) no longer cleared the new total, so bolus / carb / SMB value labels stopped appearing at 3h.
- Set BGChart.maxVisibleCount = 150 so the threshold accommodates the extra entries while keeping ≥6h zooms hiding values, matching the prior behavior in both smoothing-on and smoothing-off states.
- Fix a cold-launch race condition where the sparse-record fast-poll would silently skip
- White space complaints now resolved
- Bug: Updated, TDD, and Smoothed BG rows showed blank or stale values.
- Cause: parser picked just one of openaps.suggested / openaps.enacted (preferred suggested via ??), but Trio splits each loop's data across both blocks. suggested has fresh bg / IOB / COB / ISF / CR but no timestamp or TDD; enacted has timestamp and TDD but can linger at a previous loop's values.
- Fix: merge both blocks with suggested winning on conflicts (latest loop's values), fall back to the record's outer created_at for the Updated timestamp, and switch to NightscoutUtils.parseDate so trailing Z / fractional seconds parse cleanly.
loopandlearn#649)
* Move release flow to PR-based and add CODEOWNERS for branch protection
- Add .github/CODEOWNERS
- Cut a release branch in release.sh and open two PRs (sync to dev,
release to main) instead of merging dev into main locally
- Move tag creation into a workflow that fires on push to main
- Skip auto_version_dev when Config.xcconfig was changed in this push,
so merging the release sync-PR into dev does not double-bump
* Make workflow guards portable via fork check
Replace the hardcoded 'loopandlearn' owner check in workflows with a
fork check, so the workflows run on any non-fork repository (including
a test org) while still skipping on contributor forks.
* Allow skipping sister repo updates in release.sh
Set SKIP_SISTER_REPOS=1 to bypass the LoopFollow_Second / LoopFollow_Third
update_follower steps. Default behavior is unchanged: both sister repos
are updated as today and missing directories still cause a hard error,
so a forgotten clone in production fails fast.
* Revert "Allow skipping sister repo updates in release.sh"
This reverts commit c2792b8.
* Skip patch hunks for files missing from sister repos
Sister repos (LoopFollow_Second / LoopFollow_Third) are intentionally
stripped of dev-only files like release.sh, auto_version_dev.yml,
lint.yml, and warn_main_pr.yml. Any release patch that touches one of
those files used to abort the sister-repo update with 'No such file or
directory'. Now update_follower runs git apply --check first, parses
the missing-file errors, and re-applies with --exclude for each, so the
sister patch covers the files that actually exist.
* Revert "Skip patch hunks for files missing from sister repos"
This reverts commit 45b9871.
* Diagnose and skip rogue Nightscout profile records
Profile fetch now uses /api/v1/profiles?count=1 with find[startDate][$lte]=now,
so future-dated records can no longer block the active profile. Adds a
"Run diagnostics" button in the Remote Settings Debug section that fetches
14 days of profile history and surfaces three failure modes:
- Bundle ID mismatch when Loop and Trio share a Nightscout
- Alternating device tokens from multiple installations
- Future-dated profile records left over from a wrong-clock uploader
The bouncing-tokens check compresses consecutive same-token runs and only
warns on actual token alternation, not normal token rotation.
* Widen bouncing-token check and surface shift history
Three changes to the profile diagnostics:
- Drop the 14-day find[startDate][$gte] filter. A slow A→B→A pattern
spread across months only registers as one transition inside a 14-day
window, so the bouncing-tokens check would silently miss it on servers
that honor the filter. The existing 1000-record cap now defines the
scope, which goes back as far as upload frequency allows.
- Fall back to created_at when sorting profile records, so uploaders
that omit startDate don't cluster at .distantPast and corrupt the
run-length compression.
- Include the chronological list of token shifts in the bouncing-tokens
warning. Each row shows when the shift happened and the abbreviated
from→to tokens, so users can see at a glance which devices are
competing instead of just "3 tokens involved across N records".
…wed LA (loopandlearn#656)
pendingForegroundRestart could outlive the condition that triggered it:
a brief foreground entry while the renewal overlay was up latched the
intent, the user backgrounded before didBecomeActive ran, the background
renewal then replaced the LA, and the next foreground entry minutes later
fired the deferred restart against an already-fresh LA.
- adoptPushToStartActivity clears pendingForegroundRestart on every
adoption — a freshly-adopted LA resolves the renewal-window condition
that latched the intent.
- performForegroundRestart re-checks renewalFailed / overlayShowing /
pushToStartLooksStuck before tearing down; bails if none still hold.
- Deferred-foreground-restart push-to-start is tagged
reason="deferred-foreground-restart" via a single-shot
nextStartReasonOverride, so the stale-latch event is no longer
indistinguishable from a real user start in logs.
A 6.1.0 user reported the Live Activity vanishing and refusing to come
back without a manual Restart. Trace: APNs returned 410 on the per-
activity push token at 04:42; handleExpiredToken ended the activity but
the eventual iOS .dismissed (4 h later, under the default dismissal
policy) was classified as a user swipe and locked dismissedByUser=true.
Root cause is two cooperating bugs around an app-initiated end():
- end() nulls `current` and clears laRenewBy. handleExpiredToken's
comment said "Activity will restart on next BG refresh via
refreshFromCurrentState()", but renewIfNeeded short-circuits when
current is nil and performRefresh's bind-existing path rebinds to the
just-ended activity. bind() then clears endingForRestart, so the late
.dismissed reads as renewBy=0 / renewalFailed=false / endingForRestart=
false — branch (c) "USER" in the classifier.
- The classifier had no way to recognize a stale observer firing for an
activity the app no longer tracks.
Fixes:
- handleExpiredToken drives the restart synchronously on iOS 17.2+
(attemptPushToStartCreate "expired-token"), so the orphaned post-410
state is short-lived and adoption of the fresh activity cancels the
old observer.
- performRefresh / update bind-existing only to activities in
.active state. Binding to an .ended/.dismissed corpse would clear
endingForRestart and re-attach an observer that only ever delivers
.dismissed.
- .dismissed classifier gains branch (d): if the dismissed activity is
not the one we currently track, log and take no action — only the
foreground LA can be user-swiped, so a stale-observer delivery for an
already-replaced activity must not latch dismissedByUser=true.
When iOS reaches the Live Activity lifetime cap before renewal fires it
delivers .ended, not .dismissed. The state observer only ran restart
logic on .dismissed, so handleForeground saw renewalFailed=false and
renewBy still in the future and returned "no action needed", leaving
the LA dark until manual force-restart.
Mark laRenewalFailed=true on the .ended path (gated on wasCurrent and
!endingForRestart) so the next foreground entry triggers
performForegroundRestart, which sweeps the corpse activity and pushes a
fresh one.
…oopandlearn#661)
Dexcom Share returns each reading twice when both the iPhone Dexcom
app and the Apple Watch app upload to the same account (~9-10 s apart,
same SGV). Without deduplication the two most recent entries in bgData
were always identical, producing delta = 0.
The NS fetch path already had inline deduplication. Extract it into a
shared helper (deduplicateBGReadings) and apply it to the Dexcom-only
path as well.
* Remove Main.storyboard and migrate to SwiftUI app lifecycle
Replace UIKit storyboard/SceneDelegate architecture with SwiftUI App
entry point (LoopFollowApp.swift) and TabView (MainTabView.swift).
Convert MoreMenuViewController to SwiftUI (MoreMenuView.swift). Add
SwiftUI wrappers for Remote and Nightscout tabs. Remove 6 obsolete
UIKit wrapper view controllers and ~300 lines of tab management code
from MainViewController.
* Migrate info table from UITableView to SwiftUI List
Replace UITableView with SwiftUI InfoTableView hosted in
MainViewController. Make InfoManager an ObservableObject so data
updates trigger SwiftUI rebuilds automatically. Remove
UITableViewDataSource conformance and table delegate methods.
No changes needed to the 10 Nightscout controller files that
populate the table data.
* Migrate statistics and pie chart from UIKit to SwiftUI
Replace 7 UILabel properties and DGCharts PieChartView with a
StatsDisplayModel ObservableObject and hosted StatsDisplayView.
The pie chart uses a UIViewRepresentable wrapper for DGCharts
since the Charts pod name shadows Swift Charts. Remove ~60 lines
of UIKit stack layout code from MainViewController setupUI().
* Migrate BG display area from UIKit labels to SwiftUI
Replace BGText, DirectionText, DeltaText, MinAgoText, serverText,
LoopStatusLabel, and PredictionLabel with a SwiftUI BGDisplayView.
Add pull-to-refresh via .refreshable modifier. Move loop status and
prediction text updates to Observable values across DeviceStatus,
DeviceStatusLoop, DeviceStatusOpenAPS, and BGData. Remove
UIScrollView overlay and UIScrollViewDelegate conformance.
* Migrate main layout to SwiftUI with UIKit charts embedded
Replace UIStackView layout with MainHomeView SwiftUI view that composes
BGDisplayView, InfoTableView, LineChartWrapper (UIViewRepresentable for
DGCharts), and StatsDisplayView. MainViewController now hosts a single
UIHostingController instead of managing individual UIView containers.
Visibility of info table, small graph, and stats is now reactive via
Storage observables in SwiftUI, removing several Combine subscriptions.
BG text uses lineLimit + minimumScaleFactor instead of manual font sizing.
* Clean up migration artifacts and fix post-migration bugs
- Fix AVSpeechSynthesizer temporary in AppDelegate that would be
deallocated before speech completes; use stored property instead
- Fix appMovedToBackground tab switching to use Observable instead of
dead UIKit tabBarController reference
- Remove dead code: rebuildTabsIfNeeded(), updateNightscoutTabState(),
traitCollectionDidChange notification relay, UIViewExtension.addBorder
- Remove unused imports (Charts, UIKit, Combine) from migrated files
- Remove unused synthesizer from LoopFollowApp
- Remove redundant .appearanceDidChange subscription from NightscoutVC
- Add missing super calls in viewWillAppear/viewDidAppear
* Replace view hierarchy walking with MainViewController.shared
The getMainViewController() methods in TreatmentsView, SettingsMenuView,
and BackgroundRefreshManager tried to find MainViewController by casting
rootViewController as UITabBarController, which always fails with the
SwiftUI lifecycle. Add a weak static shared reference set during
viewDidLoad and use it everywhere instead.
* Fix MainViewController.shared references for stats and treatments
Pass MainViewController.shared instead of nil when creating
AggregatedStatsContentView in MainTabView and MoreMenuView. Replace
view-hierarchy-walking getMainViewController() in TreatmentsViewModel
with MainViewController.shared.
* Fix info table font size to match storyboard
The storyboard used system 17pt for both title and detail labels.
The SwiftUI migration used .subheadline (~15pt) making text smaller.
* Fix Share Logs sheet rendering blank
Present UIActivityViewController via UIApplication.topMost instead of
wrapping it in a SwiftUI .sheet, which rendered an empty view.
* Fix back navigation from Settings sub-pages
SettingsMenuView declared its own NavigationStack(path:) while already
being pushed onto the outer NavigationStack from MainTabView, so sub-page
back buttons popped the outer stack and jumped past Settings to Menu.
Drop the nested NavigationStack and route Settings entries through the
ambient stack: a single SettingsRoute enum drives a navigationDestination
attached at the MoreMenuView root. The Settings entry itself becomes a
NavigationLink(value:) so it doesn't compete with a navigationDestination
(isPresented:) modifier, which was re-asserting Settings as the top of
stack whenever a sub-page was pushed.
* Harden post-storyboard migration
* MainViewController is now a strong static singleton bootstrapped from
LoopFollowApp.init(). Lifecycle work in viewDidLoad (Combine sinks,
observers, scheduleAllTasks, migrations) runs at launch regardless of
whether the Home tab is rendered, and HomeContentView reuses the
singleton instead of instantiating a fresh VC each time.
* MoreMenuView's eight .navigationDestination(isPresented:) modifiers are
collapsed to a single MenuRoute enum routed through one
.navigationDestination(for:), preventing the same destination-slot
contention that previously caused Settings → Graph back navigation to
jump past Settings.
* MainTabView observes Storage.shared.appearanceMode so theme changes
propagate; the orphaned .appearanceDidChange notification name is
removed.
* OPEN_APP_ACTION notification taps now dismiss any presented modal
before switching to Home, matching prior SceneDelegate behavior.
* Drop the unused Core Data stack (NSPersistentCloudKitContainer,
saveContext) from AppDelegate, the dead AppDelegate.window property,
and the legacy UIRequiredDeviceCapabilities=armv7 /
UIStatusBarTintParameters keys from Info.plist. Switch
AlarmSound's keyWindow access to the connected-scenes API and
generalize UIApplication.topMost likewise so it works on Mac
Catalyst.
* Strip redundant inner NavigationView wrappers from settings sub-views
pushed onto the outer NavigationStack: Graph, General, Advanced,
Calendar, Contact, Dexcom, Nightscout, BackgroundRefresh,
InfoDisplay, ImportExport. Drop unused onBack parameters from
AlarmsContainerView and SettingsMenuView, the unused
isPresentedAsModal flag from MainViewController, and the leftover
debug print in ObservableValue.set.
* LineChartWrapper.updateUIView now flushes the chart on SwiftUI
re-render. MainViewController.deinit removes all observers, not
just the custom "refresh" one. MoreMenuView caches the app version
in @State instead of constructing AppVersionManager on every body
re-render. HomeModalView uses NavigationStack (not deprecated
NavigationView).
* MoreMenuView: render tab-switch buttons in primary color
Buttons in a List inherit the accent tint, so the Features rows that switch tabs appeared blue while the NavigationLink rows that push appeared white. Use .buttonStyle(.plain) to suppress the tint and drop the now-redundant .foregroundStyle(.primary) calls.
* Revert MainViewController singleton bootstrap
Constructing MainViewController.shared from LoopFollowApp.init() — and
reusing the same VC across HomeContentView re-creations — caused tapping
the BG chart to crash with `-[__NSArrayM insertObject:atIndex:]: object
cannot be nil`. Bisected to the singleton+bootstrap piece of the
post-storyboard hardening; the rest of that commit (programmatic UI,
MoreMenuView routing, NavigationView strip-out) is retained.
Restore the prior behavior: shared is a weak static set in viewDidLoad,
HomeContentView constructs a fresh MainViewController each time, and the
LoopFollowApp.init() bootstrap is removed.
Known follow-up: lifecycle work in viewDidLoad (Combine sinks,
scheduleAllTasks, migrations) again only runs when the Home view is
first rendered, so a user who has moved Home off the tab bar gets
degraded behavior until they navigate to it.
* MoreMenuView: make tab-switch rows full-row tappable
Wrap Button labels in an HStack with a trailing Spacer and contentShape
so the entire row is tappable, matching the hit area of NavigationLink
rows. Extract the pattern into a small FullRowButton helper, used for
both tab-switch rows and Share Logs.
* Align units-selection conflict resolution with integration branch
Move the Diagnostics section out of Section("Speak BG") (was nested at
the wrong indent), match StatsDisplayModel field order, and add the
spacing line in updateStats.
* MoreMenuView: fix cross-row tap routing in Features section
Mixing Button and NavigationLink rows in the same List ForEach caused
taps on a NavigationLink row to fire a sibling Button row's action —
e.g. tapping Alarms with Stats in the tab bar would switch to the Stats
tab instead of pushing the alarms detail.
Make every row in the menu's List a uniform FullRowButton and drive
pushes from state via .navigationDestination(isPresented:). Add an
opt-in chevron to FullRowButton so navigating rows render the standard
disclosure indicator.
* MoreMenuView: keep Settings as a value-based NavigationLink
Mixing .navigationDestination(isPresented:) with
.navigationDestination(for:) on the same view shadowed the value-based
SettingsRoute registration once SettingsMenuView was on the stack, so
sub-rows like Units and Metrics couldn't push.
Settings sits alone in its section, so it doesn't need the uniform-Button
treatment used in Features and Logging — restore it to a NavigationLink
and route it through the existing .navigationDestination(for:) channel.
* Fix navigation between alarms and menu
* Drive Before-First-Unlock recovery from AppDelegate
Move BFU recovery (Storage.reloadAll) out of MainViewController and into
AppDelegate so it runs even when the home tab's UIHostingController has
not yet materialized — necessary under the SwiftUI App lifecycle, where a
BG-only launch (BGAppRefreshTask, BLE wake, prewarming) may complete and
the device may unlock without MainViewController ever being created.
AppDelegate observes protectedDataDidBecomeAvailable (authoritative signal)
and willEnterForeground (fallback), with a race-guard re-check immediately
after observer registration. Recovery is idempotent via needsBFUReload.
MainViewController now reacts to a new .bfuReloadCompleted notification by
showing the loading overlay and rescheduling tasks; if it is not alive when
the notification fires, its viewDidLoad will later see the already-reloaded
Storage values and schedule tasks correctly on first load.
* Keep MainViewController alive regardless of tab layout
Make MainViewController.shared a strong, long-lived singleton created
once via bootstrap() on first foreground, so the data pipeline, alarms
and background audio run even when Home is moved into the Menu rather
than a tab. Home views reuse the single instance instead of creating
new ones, so the singleton is never displaced.
Defer the one-shot BG graph zoom until the chart has a real frame and
re-render the graph on every appearance, so the curve stays visible
when Home is reached from the Menu or moved between tab bar and Menu.
Restore the one-time telemetry consent prompt that was lost when
SceneDelegate was removed, presenting it from MainTabView on first
appearance for undecided installs.
* Fix issues found in post-migration review
- Speak BG quick action: under the SwiftUI scene lifecycle UIKit
delivers Home Screen quick actions to the window scene delegate and
never calls application(_:performActionFor:). Install a scene
delegate via configurationForConnecting that handles warm taps,
cold-launch shortcut delivery, and mirrors the Live Activity la-tap
URL handling that moves with it.
- Nightscout tab: show a hint instead of a blank page when no URL is
configured, and recreate the web view when the URL or token changes
(the page was loaded once in viewDidLoad and stayed stale until app
restart).
- Stats: resolve MainViewController lazily with a fallback to the
shared instance, so stats work when the tab is built before
bootstrap() has run (cold launch with Statistics as selected tab).
- Remove the unwired NightscoutSettingsViewModel delegate chain.
- Make LineChartWrapper.updateUIView a no-op; MainViewController
already notifies the charts whenever it mutates their data.
* Fix squished Home layout caused by phantom keyboard inset
iOS sometimes replays a stale keyboard frame when the app returns to the
foreground, compressing the Home screen by a keyboard's height until a
rotation forces the safe area to recompute. Home has no text input, so
opt out of keyboard avoidance at both hosting layers.
github-actionsBotand others added 26 commits July 21, 2026 22:37
…Activity and in the remote command views (loopandlearn#719)
* Show active override and temp target with remaining time on the Live Activity and in the remote command views
* Treat Trio's 30 day durations as indefinite and light up the remote command buttons while an override or temp target is active
…#724)
Switching from a Trio site to a Loop site left the old Trio forecast on
the chart. The flip detection in updateDeviceStatusDisplay cleared the
drawn lines and the cone, but the device observer fires right after and
calls updateOpenAPSPredictionDisplay, which redrew the forecast from the
stored predBGs. Those are only cleared when an openaps record without
predBGs arrives, which never happens on a Loop site, so the stale lines
stayed until the app was restarted. Clearing the stored predBGs together
with the drawn data stops the forecast from coming back. The Loop to
Trio direction was already working.
…dlearn#729)
The old chart only drew what the gated update calls pushed into it, but
the Swift Charts model reads the data arrays directly, so Graph Basal,
Graph Bolus, Graph Carbs and Graph Other Treatments were never
consulted. Skip the corresponding data during rebuild when a toggle is
off, and mark the chart dirty when a toggle changes so it redraws on
return to the home screen.
Restore two settings-change behaviors dropped in the migration:
changing the prediction style re-routes the stored predBGs again, and
toggling Show Yesterday's BG reloads the BG window so the overlay
updates right away instead of at the next scheduled fetch.
…rn#733)
When the system cancels a touch (permission alert, incoming call, app
switch) the chart gestures never get their onEnded call, so pinch or
inspect state stays latched and blocks all panning and zooming until the
app is restarted. Clear leaked gesture state when a new touch begins,
reset it when the chart disappears, and rebuild the gesture attachments
on foregrounding while keeping the current viewport.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@aug0211@afshopify@bjorkert@luborjurena@quarktwain@marionbarker@codebymini