feat: long-press to reorder & hide cards on Today & Vitals - #64
Merged
Merged
Conversation
Long-press any Today tile or Vitals card to enter an edit mode where the cards wiggle and can be dragged to reorder; a Done pill exits. Order persists per scope in MetricPrefsStore (UserDefaults) as MetricKey rawValues — a pure display preference that never touches health data, the ring, or any backend. - MetricPrefs gains todayOrder/vitalsOrder ([String], tolerant-decoded, empty = screen default). resolvedOrder() merges saved order with visible metrics so new or never-reordered cards slot into their default position. - Today (LazyVGrid) and Vitals (VStack) refactored from hardcoded card sequences to a data-driven ReorderableForEach over the saved order. - ReorderableForEach (DesignSystem/CardReorder.swift): wiggle + live onDrag/onDrop reorder; inner card disabled while editing so taps can't navigate; long-press attached via simultaneousGesture so it fires alongside each card's own button. NOTE: initial cut — needs further UX before merge (see PR description).
…l, hide/restore Builds on the long-press card reorder: - Keep instant long-press -> jiggle entry (no context menu). - Reduce Motion: no wiggle; reorder still works via drag + VoiceOver. - VoiceOver: per-card Move up / Move down / Move to top + Hide actions. - Feel: spring shuffle, lift-on-pickup (scale + shadow, dimmed in-place copy), exit on tap-outside/scroll. - Home-screen '-' remove badge to hide a card; a 'Hidden' tray (with '+') in edit mode restores it (full hide/restore loop, no Settings changes). - LazyVGrid drop-target tuning (full-cell drop). Limited to CardReorder.swift / TodayView.swift / VitalsView.swift; Settings untouched.
…e minus - Hide now filters the grid by live prefs.isHidden (not the store's cached visibleMetrics snapshot), so the '-' badge / Hidden-tray '+' drop or restore a card immediately. - Removed the scroll-to-exit behavior so you can scroll through all cards in edit mode without it dismissing edit mode. - Moved the '-' remove badge to an overlay above the drag layer so its tap isn't swallowed by the cell's .onDrag.
saksham2001
approved these changes
Jul 9, 2026
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.
What
Long-press any Today tile or Vitals card to enter an edit mode where cards wiggle and can be dragged to reorder (home-screen style); a Done pill exits. Cards can also be hidden (a "–" badge) and restored (a "Hidden" tray). Card order + visibility persist per screen.
Data safety
Order + hidden state are pure display preferences — lists of
MetricKeyraw values saved as JSON inUserDefaults(MetricPrefsStore, alongside the existing hidden-metric + resolution prefs). They never touch the SwiftData health samples, the ring/BLE, or any backend. Tolerant decode → a corrupt/missing value falls back to the default order; no data-loss path.Changes
MetricPrefs:todayOrder/vitalsOrder([String], tolerant-decoded, empty = screen default) +resolvedOrder()merging saved order with currently-visible metrics.ReorderableForEachover the resolved order, filtered by liveprefs.isHiddenso hide/restore reflect immediately.ReorderableForEach(DesignSystem/CardReorder.swift): wiggle + liveonDrag/onDropreorder; inner card disabled while editing so taps can't navigate; long-press viasimultaneousGesture(fires alongside each card's own button); "–" remove badge; VoiceOver move/hide actions;HiddenMetricsTrayrestore tray.UX pass — done ✅
The items flagged as "needs UX before merge" are now implemented:
contentShapedrop targets fix theLazyVGridimprecision.Fixes from device testing
prefs.isHidden(not the store's cachedvisibleMetricssnapshot), so a hidden card drops from the grid immediately..onDrag.Rebased onto current
main(includes the AI-coach rehaul). Tested on-device (physical ring).